匿名方法中 TypeInfo 的奇怪行为

7

针对某段需要通用类型的“family”类型的代码,我尝试使用 TypeInfo 来获取所需信息。

class function GetTypeKind<T>:TTypeKind;

对于大多数类型,我都能解决问题。但是匿名方法类型的行为却出乎意料。

我定义了一个匿名方法类型:

TMethodProc = reference to procedure;

我尝试获取类型信息:

MyKind := GetTypeKind<TMethodProc>;

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

  ...
end;

我知道匿名方法背后有一些编译器的魔法。但是我得到了以下结果:

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

标志具有意外的值,TIntfFlag有三个值,因此6是意外的。GUID也不是GUID。它具有重复的相同8字节集,大多数为00。例如(0, 225, 48, 180, 0, 0, 0, 0, 0, 225, 48, 180, 0, 0, 0, 0)
匿名方法是否被排除在TypeInfo之外,或者经过一些调整后是否有用。
此外,奇怪的6是否是未记录的功能,还是可以是任何值?
1个回答

6

这并没有什么特别的。

匿名方法被实现为一个编译器生成的接口,该接口具有与匿名方法相同签名的Invoke()方法。 这就是为什么TTypeKindtkInterfaceIntfParentIInterface

在接口之后是包含捕获变量和匿名方法体的编译器生成的实现类,在其Invoke()实现中。

匿名方法是怎样实现的?

IntfFlags是一个TIntfFlagsBase,它是一个包含SetTIntfFlag枚举值的集合:

TIntfFlag = (ifHasGuid, ifDispInterface, ifDispatch);

ifHasGuid
接口具有全局唯一标识符(GUID)。

ifDispInterface
是一个分发接口。

ifDispatch
可以被分派。

Set 是一个值的位掩码。每个枚举值在掩码中都由特定位表示。在 TIntfFlagsBase 中,ifHasGuid 是第0位、ifDispInterface 是第1位,ifDispatch 是第2位。因此,数字值为6 (110b) 表示启用了 ifDispInterfaceifDispatch 标志,但未启用 ifHasGuid 标志。因此,IntfGuid 没有实际意义,但对于对齐目的仍然占据了 TTypeData 中的空间。


更新:我使用 XE2 进行了测试,确实看到 IntfFlags 被设置为序数64 (TIntfFlag(6),与您看到的相同),而不是预期的序数6。我看到和您看到之间唯一的差异是,我看到 Guid 完全为空(全部为零)。


更新:显然,对于启用了方法信息({$M+}指令)的接口,或者表示匿名方法类型的接口,存在其他标志未在 TIntfFlag 枚举中表示。我已经为此提交了一个错误报告:

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

在这种情况下,TIntfFlag(6) 是匿名方法的标志。

来源:未记录的 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]

该集合没有值为6,其中包含一个值为6的元素,检查器显示它的值为64。因此,该值无效。 - Toon Krijthe
@ToonKrijthe 如果要使 TIntfFlagsBase 的数值为64,则 TIntfFlags 需要有7个元素,但事实并非如此。我认为您对问题的诊断有误。另外,您也不应该直接访问 TI.TypeData,而应该将 TI 传递给 GetTypeData() - Remy Lebeau
2
@RemyLebeau。.TypeData与传递给GetTypeData相同,它是TTypeInfo上的一个方法。此外,他没有误诊问题。在匿名方法类型的typeinfo中似乎存在一些垃圾数据(或一些未记录的标志)。 - Stefan Glienke
幸运的是这不是一个真正的问题。 但我只是好奇为什么会这样。 现在我认为这种类型的数据是不可靠的。 - Toon Krijthe
我已经更新了我的回答。TIntfFlag 定义确实缺少几个标志。我已向 Embarcadero 报告了此问题。 - Remy Lebeau

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接