Thursday, June 14, 2012

Bug or no bug - that is the question

Or with other words: when something is not what it looks to be - and you have no clue why.

Let me explain: Recently over on Nicks blog Márton mentioned that TRttiMethod.Invoke cannot handle var and out parameters. While I already created a runtime patch for the bug in the QC entry for 2010 and XE I was not sure about the handling of var and out parameters. I remembered I ran into some problem with calling the Invoke routine and Barry gave me the correct hint on how to handle passing values by reference. So I tried it:


program Project1;

{$APPTYPE CONSOLE}

uses
  Rtti;

type
  TTest = class
  public
    procedure CallVar(var i: Integer);
  end;

procedure TTest.CallVar(var i: Integer);
begin
  Inc(i);
end;

var
  test: TTest;
  ctx: TRttiContext;
  t: TRttiType;
  m: TRttiMethod;
  i: Integer;
begin
  test := TTest.Create;
  t := ctx.GetType(TTest);
  m := t.GetMethod('CallVar');
  i := 42;
  m.Invoke(test, [TValue.From<Integer>(i)]);
  Writeln(i);
  test.Free;
  Readln;
end.

It showed 42. First thought: yes, he is right, it does not handle them correctly. Second thought: wait, TValue is not taking any kind of reference to i. It just takes the value and stores it. So I changed the program a bit to check what was in the passed TValue argument.


var
  [...]
  v: TValue;
begin
  [...]
  v := TValue.From<Integer>(i);
  m.Invoke(test, [v]);
  Writeln(v.AsInteger);
  [...]
end.

Output remained 42. I messed around with passing the pointer to i inside the TValue but then the invoke method raised the EInvalidCast exception telling me: 'VAR and OUT arguments must match parameter type exactly'. I knew that this method checks this (not like the Invoke routine mentioned earlier) and passes them correctly. So what was going on? I changed it again:

var
  [...]
  v: TArray<TValue>;
begin
  [...]
  SetLength(v, 1);
  v[0] := TValue.From<Integer>(i);
  m.Invoke(test, v);
  Writeln(v[0].AsInteger);
  [...]
end.

Hooray, it showed the expected 43. What happened here? In the case where it did not work I used the open array constructor. The documentation says that it equivalent to passing a static array filled with the values passed to the open array constructor. Ok, I tested that:

var
  [...]
  v: array[0..0] of TValue;
begin
  [...]
  v[0] := TValue.From<Integer>(i);
  m.Invoke(test, v);
  Writeln(v[0].AsInteger);
  [...]
end.

Guess what? It returns 43. Seems it is not equivalent. Could it be that the code the compiler creates for an open array constructor does not handle the nested record inside of TValue - TValueData where the actual value is stored in - correctly? I was staring at the assembler code but as you know I pretty much suck reading something out there. While I was glad that TRttiMethod.Invoke actually handles var and out parameters correctly I could make a fix for DSharp mocks. But I still have no clue why passing the value with the open array constructor does not keep the value. Any ideas?

Sunday, June 3, 2012

Weak interface references

We all know how painful it can be working with interfaces and reference counting when it comes to circular or cross references.

Vincent Parrett wrote about that a while ago and presented a nice solution.

The only disadvantage about his solution was the special class type (TWeakReferencedObject) you have to inherit from to use a weak reference to. What if you want to use a weak reference to something that already exists and that you cannot change?

That is where my idea comes in.

The Weak<T> type supports assignment from and to T and makes it usable as if you had a variable of T. It has the IsAlive property to check if the reference is still valid and not a dangling pointer. The Target property can be used if you want access to members of the reference.

Let's assume you have that typical parent child relationship where both have a reference to each other. Normally that would cause a memory leak because that cross references would keep both objects alive. Change the parent reference to be a weak reference and both objects get destroyed properly because the child is not keeping the parent alive.

type
  TParent = class(TInterfacedObject, IParent)
  private
    FChild: IChild;
    procedure AddChild(const AChild: IChild);
  public
    destructor Destroy; override;
  end;

  TChild = class(TInterfacedObject, IChild)
  private
    FParent: Weak<IParent>;
  public
    constructor Create(AParent: IParent);

    function GetParent: IParent;
  end;

So how to check if the object of the reference is still valid? That is done by hooking the TObject.FreeInstance method so every object destruction is noticed and if a weak reference for that object exists it gets removed from the internal list where all weak references are stored.

While it works I am aware that this is hacky approach and it might not work if someone overrides the FreeInstance method and does not call inherited. It also is not yet threadsafe. It also might have a small performance impact because of the dictionary access in every FreeInstance call.

But hey, nothing is for free, isn't it? ;)

Thursday, May 31, 2012

Namespaces, open source and you

I have to admit - this will be kind of a rant. 8)

These days I am getting really annoyed when people are using XE2 when they are contributing to open source projects that are supposed to work also for XE or lower - no offense.

Huh, why?

Because XE2 just as every Delphi before automatically adds units to the uses clauses which are - you can see what is coming - are different from how they have been before for ages. Instead of having Forms in the uses of the typical VCL application, now we get Vcl.Forms. Or all the units that get added to a form.

So everyone not using XE2 will be unable to compile the awesome code you just wrote if you forgot to manually fix this unless he does it himself!

Maybe some clever people out there familar with IDE plugins figure out a way to restore the old way units are named when using them in a uses clause - which still works given you add the required namespaces to the project options.

Please share your thoughts. :)

Sunday, April 22, 2012

Creating a delegate at runtime

We know how anonymous methods work in Delphi for a while now. Barry explained how you can use that knowledge to put a delegate into a method pointer and Mason used the enhanced RTTI to get some details about them.

So we know that a delegate type (like TProc<T>) is basically an interface with an Invoke method that has the same signature as the delegate type definition (like procedure(Arg: T)). We also know that there is an TInterfacedObject behind it that is normally compiler created (these classes with $ActRec in their name).

So we basically have all we need to create our own delegate. Before you say: Why are you doing this? You can assign any regular procedure or method to a delegate when the signature is compatible - I will explain that later.

type
  PDelegate = ^IDelegate;
  IDelegate = interface
    procedure Invoke;
  end;

  TDelegate = class(TInterfacedObject, IDelegate)
  private
    FMethod: TMethod;
    procedure Invoke;
  public
    constructor Create(const AMethod: TMethod);
  end;

So we did something similar to what the compiler does when creating this (without the variable capturing of course which we do not need). Now we can put our method into a delegate variable without the built-in type compatibility and using a much more complicated and non type safe way. ;)

var
  proc: TProc<TObject>;
  method: TMethod;
begin
  method.Code := @TForm1.Button1Click;
  method.Data := Self;
  PDelegate(@proc)^ := TDelegate.Create(method);
  proc(Button1);
end;

One detail is missing. The implementation of the Invoke method. This is basically just adjusting the Self pointer and rerouting the call to the object that belongs to the method pointer (I am using my horribly poor x86 asm knowledge here).

procedure TDelegate.Invoke;
asm
  MOV EBX,[EAX].FMethod.Code
  MOV EAX,[EAX].FMethod.Data
  CALL EBX
end;

I guess many of you have more knowledge about that than I have so you might tell me if I made some mistake here. Also someone knows how this should look for x64?

So what is this all about? Why do we need to put a method into a delegate without the built-in assignment?

Remember DSharp multicast events? They work for any method type. But not for anonymous methods. Internally these events are using the ObjAuto unit to create a method pointer. So I can define Event<TNotifyEvent> and add any method that matches the TNotifyEvent signature. Just not anonymous methods that have this signature. So what if I just want to add a simple delegate? Not possible. I would need to define Event<TProc<TObject>> to do so. Internally the TEvent class works with a list of TMethod so I had to convert T which in that case is a anonymous method type (which is an interface you remember) to TMethod and the other way around. To actually call a multicast event there is the Invoke property which is of type T. Since the internally created method pointer is TMethod I needed to stuff that into an anonymous method type. In that case I already had the object which could hold the "magic" interface, the TEvent class itself.

Now there was just a little thing missing. The CreateMethodPointer function from ObjAuto needs a PTypeData for the signature of the method to create. Easy enough for a defined type in case of methods but not so for anonymous methods. So I needed the signature from the Invoke method of the interface and create some TTypeData from it. Important to mention that anonymous methods still do not have the $M+ by default so that does not work for the types defined in SysUtils.pas. You have to define your own types and adding that (so enhanced RTTI get generated for the interface methods). Fortunately Hallvard has explained how these things look and work so I "just" had to use the information from TRttiMethod to create a TTypeData record and fill that with the information needed by CreateMethodPointer.

I have to run some more tests with various signatures to make sure everything works (also add the 64-bit support). Then you will see these changes commited to the svn repository.