views:

168

answers:

1

I've recently experimented extensively with interfaces and D2010 RTTI. I don't know at runtime the actual type of the interface; although I will have access to it's qualified name using a string.

Consider the following:

program rtti_sb_1;
{$APPTYPE CONSOLE}
uses
  SysUtils, Rtti, TypInfo, mynamespace in 'mynamespace.pas';
var
  ctx:                  TRttiContext;
  InterfaceType:        TRttiType;
  Method:               TRttiMethod;
  ActualParentInstance: IParent;
  ChildInterfaceValue:  TValue;
  ParentInterfaceValue: TValue;
begin
  ctx := TRttiContext.Create;
  // Instantiation
  ActualParentInstance := TChild.Create as IParent;
  {$define WORKAROUND}
  {$ifdef WORKAROUND}
  InterfaceType := ctx.GetType(TypeInfo(IParent));
  InterfaceType := ctx.GetType(TypeInfo(IChild));
  {$endif}
  // Fetch interface type
  InterfaceType := ctx.FindType('mynamespace.IParent');
  // This cast is OK and ChildMethod is executed
  (ActualParentInstance as IChild).ChildMethod(100);
  // Create a TValue holding the interface
  TValue.Make(@ActualParentInstance, InterfaceType.Handle, ParentInterfaceValue);
  InterfaceType := ctx.FindType('mynamespace.IChild');
  // This cast doesn't work
  if ParentInterfaceValue.TryCast(InterfaceType.Handle, ChildInterfaceValue) then begin
    Method := InterfaceType.GetMethod('ChildMethod');
    if (Method <> nil) then begin
      Method.Invoke(ChildInterfaceValue, [100]);
    end;
  end;
  ReadLn;
end.

The contents of mynamespace.pas is as follows:

{$M+}
IParent = interface
  ['{2375F59E-D432-4D7D-8D62-768F4225FFD1}']
  procedure ParentMethod(const Id: integer);
end;
{$M-}
IChild = interface(IParent)
  ['{6F89487E-5BB7-42FC-A760-38DA2329E0C5}']
  procedure ChildMethod(const Id: integer);
end;
TParent = class(TInterfacedObject, IParent)
public
  procedure ParentMethod(const Id: integer);
end;
TChild = class(TParent, IChild)
public
  procedure ChildMethod(const Id: integer);
end;

For completeness, the implementation goes as

procedure TParent.ParentMethod(const Id: integer);
begin
  WriteLn('ParentMethod executed. Id is ' + IntToStr(Id));
end;
procedure TChild.ChildMethod(const Id: integer);
begin
  WriteLn('ChildMethod executed. Id is ' + IntToStr(Id));
end;

The reason for {$define WORKAROUND} may be found in this post.

Question: is there any way for me to make the desired type cast using RTTI? In other words: is there a way for me to invoke IChild.ChildMethod from knowing 1) the qualified name of IChild as a string, and 2) a reference to the TChild instance as a IParent interface? (After all, the hard-coded cast works fine. Is this even possible?) Thanks!

+2  A: 

This looks like a pretty ugly instance of lazy coding in RTTI.pas. In the ConvIntf2Intf function that takes care of interface casts within TValue, it explicitly only checks to see if you're casting to IInterface. Any other interface will return false automatically. It could easily extract the GUID (if your interface has one) and attempt a QueryInterface call, but it doesn't do that for whatever reason. I'd report this one to QC.

Mason Wheeler
Man, that makes so much sense! I really couldn't understand why this didn't work. To the rescue once again! :) (+1) I'll report it to QC in the morning.
conciliator
Morning came quicker than expected... It's now reported under QC #85339.
conciliator