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? ;)