Strange behaviour of TypeInfo by anonymous methods
Asked Answered
S

1

7

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?

Stinkstone answered 20/4, 2018 at 22:33 Comment(0)
C
6

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]
Churchy answered 21/4, 2018 at 1:22 Comment(5)
The set has not value 6, it contains an element with value 6, it has value 64 (according to the inspector). So the value is not valid.Stinkstone
@ToonKrijthe for TIntfFlagsBase to have a numeric value of 64, TIntfFlags would need to have 7 elements, and that is simply not the case. I think you are misdiagnosing the issue. Also, you shouldn't be accessing TI.TypeData directly anyway, you should be passing TI to GetTypeData() insteadChurchy
@RemyLebeau .TypeData is the same as passing to GetTypeData, its a method on TTypeInfo. Also he is not misdiagnosing the issue. There seems to be some garbage data in the typeinfo for an anonymous method type (or some flags that are not documented).Scanlon
Fortunately it is not a real issue. But I'm just curious about the why. Right now I think the data is unreliable for this type.Stinkstone
I have updated my answer. There are indeed several flags missing from the TIntfFlag definition. I have reported it to Embarcadero.Churchy

© 2022 - 2024 — McMap. All rights reserved.