Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strange behaviour of TypeInfo by anonymous methods

For a piece of code that needs the type "family" of a generic type, I try to use the TypeInfo to retrieve the required information.

class function GetTypeKind<T>:TTypeKind;

For most types I can figure this out. But the anonymous method type behaves unexpected.

I have an anonymous method type defined as:

TMethodProc = reference to procedure;

And I try to get the type information:

MyKind := GetTypeKind<TMethodProc>;

class function GetTypeKind<T>:TTypeKind;
var 
  TI: PTypeInfo;
begin
  TI := TypeInfo(T);

  ...
end;

I know there is some compiler magic behind the anonymous methods. But I get the following result:

TI.TypeData.IntfParent == IInterface
TI.TypeData.IntfFlags == [(out of bounds)6]

The flags have an unexpected value, TIntfFlag has three values so a 6 is unexpected. The GUID is also not a guid. It has a repeating set of the same 8 bytes, mostly 00. For example (0, 225, 48, 180, 0, 0, 0, 0, 0, 225, 48, 180, 0, 0, 0, 0)

Are anonymous methods excluded from TypeInfo or can it be useful with some tweaks.

Also, is the (strange) 6 an undocumented feature, or can it be any value?

like image 471
Toon Krijthe Avatar asked Apr 20 '18 22:04

Toon Krijthe


1 Answers

There is nothing really unusual about this.

An anonymous method is implemented as a compiler-generated interface that has an Invoke() method matching the same signature as the anonymous method. That is why the TTypeKind is tkInterface and the IntfParent is IInterface.

Behind the interface is a compiler-generated implementation class that contains the captured variables, and the body of the anonymous method within its Invoke() implementation.

How are anonymous methods implemented under the hood?

The IntfFlags is a TIntfFlagsBase, which is a Set of TIntfFlag enum values:

TIntfFlag = (ifHasGuid, ifDispInterface, ifDispatch);

ifHasGuid
Interface has a GUID (Globally Unique Identifier).

ifDispInterface
Is a dispatch interface.

ifDispatch
Can be dispatched.

A Set is a bitmask of values. Each enum value is represented by a specific bit in the mask. Within TIntfFlagsBase, ifHasGuid is bit 0, ifDispInterface is bit 1, and ifDispatch is bit 2. So, a numeric value of 6 (110b) would be the ifDispInterface and ifDispatch flags enabled, but not the ifHasGuid flag. As such, the IntfGuid has no meaningful value, but still takes up space in the TTypeData for alignment purposes.


Update: I tested with XE2, and sure enough, I see IntfFlags is set to ordinal 64 (TIntfFlag(6), like you see) instead of ordinal 6, as expected. The only difference between what I see and what you see is that I see the Guid is completely empty (all zeros).


Update: apparently, there are indeed additional flags present for interfaces that have method info enabled (the {$M+} directive), or that represent anonymous method types, which are not represented in the TIntfFlag enum. I have filed a bug report for that:

RSP-24631: System.TypInfo.TIntfFlag enum is missing flags

In this case, TIntfFlag(6) is the flag for anonymous methods.

From Undocumented "Interface flag" for IInvokable?:

It seems indeed as if the TIntfFlag enum was never extended since Delphi6 (I think that was when interface RTTI was introduced) - I can confirm that at least since XE an interface type with $M+ gets a fourth flag (lets call it ifHasMethodInfo) set.

If the type is an anonymous method type ... then there is a 7th enum value in the set. The situations where bit 5 and 6 are set are unknown to me.

...

I can confirm my findings with this code:

uses
  SysUtils,
  Rtti;

type
  TIntfFlagEx = (ifHasGuid, ifDispInterface, ifDispatch, ifMethodInfo, ifUnknown, ifUnknown2, ifAnonymousMethod);
  TIntfFlagsEx = set of TIntfFlagEx;

  {$M+}
  IFoo = interface
    ['{35CFB4E2-4A13-48E9-8026-C1558001F4B7}']
    procedure Main;
  end;
  {$M-}

  {$M+}
  IBar = interface(TProc)
    ['{AB2FEC1A-339F-4E58-B3DB-EC7B734F461B}']
  end;
  {$M-}

  {$M+}
  TMyProc = reference to procedure;
  {$M-}

procedure PrintIntf(typeInfo: Pointer);
var
  context: TRttiContext;
  rttiInterface: TRttiInterfaceType;
  flags: TIntfFlagsEx;
begin
  rttiInterface := context.GetType(typeInfo) as TRttiInterfaceType;
  flags := TIntfFlagsEx(rttiInterface.IntfFlags);
  Writeln(rttiInterface.Name, ' ', TValue.From(flags).ToString);
end;

begin
  PrintIntf(TypeInfo(IInterface));
  PrintIntf(TypeInfo(IInvokable));
  PrintIntf(TypeInfo(IFoo));
  PrintIntf(TypeInfo(TProc));
  PrintIntf(TypeInfo(TFunc<Integer>));
  PrintIntf(TypeInfo(TMyProc));
  PrintIntf(TypeInfo(IBar));
  Readln;
end.

prints this:

IInterface [ifHasGuid]
IInvokable [ifMethodInfo]
IFoo [ifHasGuid,ifMethodInfo]
TProc [ifAnonymousMethod]
TFunc [ifAnonymousMethod]
TMyProc [ifMethodInfo,ifAnonymousMethod]
IBar [ifHasGuid,ifMethodInfo,ifAnonymousMethod]
like image 52
Remy Lebeau Avatar answered Nov 20 '22 03:11

Remy Lebeau