Странное поведение TypeInfo анонимными методами

Для фрагмента кода, которому требуется тип «семейство» универсального типа, я пытаюсь использовать 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 также не является гидом. Он имеет повторяющийся набор из тех же 8 байтов, в основном 00. Например (0, 225, 48, 180, 0, 0, 0, 0, 0, 225, 48, 180, 0, 0, 0, 0)

Анонимные методы исключены из TypeInfo или это может быть полезно с некоторыми настройками.

Кроме того, является ли (странно) 6 недокументированной функцией или это может быть любое значение?


person Toon Krijthe    schedule 20.04.2018    source источник


Ответы (1)


На самом деле в этом нет ничего необычного.

Анонимный метод реализован как интерфейс, сгенерированный компилятором, который имеет метод Invoke(), соответствующий той же сигнатуре, что и анонимный метод. Вот почему TTypeKind это tkInterface, а IntfParent это IInterface.

За интерфейсом находится сгенерированный компилятором класс реализации, который содержит захваченные переменные и тело анонимного метода в его Invoke() реализации.

Как внутри реализованы анонимные методы?

IntfFlags — это TIntfFlagsBase, который является Set из TIntfFlag значения перечисления:

TIntfFlag = (ifHasGuid, ifDispInterface, ifDispatch);

Интерфейс ifHasGuid
имеет GUID (глобальный уникальный идентификатор).

ifDispInterface
Интерфейс диспетчеризации.

ifDispatch
Может быть отправлен.

Set — это битовая маска значений. Каждое значение перечисления представлено определенным битом в маске. В пределах TIntfFlagsBase ifHasGuid — это бит 0, ifDispInterface — это бит 1, а ifDispatch — это бит 2. Таким образом, числовое значение 6 (110b) будет включать флаги ifDispInterface и ifDispatch, но не флаг ifHasGuid. Таким образом, IntfGuid не имеет значимого значения, но все же занимает место в TTypeData для целей выравнивания.


Обновление: я тестировал с XE2 и, конечно же, вижу, что IntfFlags установлен на порядковый номер 64 (TIntfFlag(6), как вы видите) вместо порядкового номера 6, как ожидалось. Единственная разница между тем, что вижу я, и тем, что видите вы, заключается в том, что я вижу, что Guid совершенно пусто (все нули).


Обновление: по-видимому, действительно существуют дополнительные флаги для интерфейсов, для которых включена информация о методах (директива {$M+}), или которые представляют анонимные типы методов, которые не представлены в перечислении TIntfFlag. Я подал отчет об ошибке для этого:

RSP-24631: в перечислении System.TypInfo.TIntfFlag отсутствуют флаги

В данном случае TIntfFlag(6) — это флаг анонимных методов.

Из Недокументированный "флаг интерфейса" для IInvokable? :

Действительно кажется, что перечисление TIntfFlag никогда не расширялось со времен Delphi6 (я думаю, что это было, когда был введен интерфейс RTTI) - я могу подтвердить, что, по крайней мере, с XE тип интерфейса с $M+ получает четвертый флаг (назовем его ifHasMethodInfo) установленным .

Если тип является типом анонимного метода... тогда в наборе есть 7-е значение перечисления. Ситуации, когда бит 5 и 6 установлены, мне неизвестны.

...

Я могу подтвердить свои выводы с помощью этого кода:

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.

печатает это:

IInterface [ifHasGuid]
IInvokable [ifMethodInfo]
IFoo [ifHasGuid,ifMethodInfo]
TProc [ifAnonymousMethod]
TFunc [ifAnonymousMethod]
TMyProc [ifMethodInfo,ifAnonymousMethod]
IBar [ifHasGuid,ifMethodInfo,ifAnonymousMethod]
person Remy Lebeau    schedule 21.04.2018
comment
В наборе нет значения 6, он содержит элемент со значением 6, имеет значение 64 (по словам инспектора). Таким образом, значение недействительно. - person Toon Krijthe; 21.04.2018
comment
@ToonKrijthe, чтобы TIntfFlagsBase имел числовое значение 64, TIntfFlags должно было иметь 7 элементов, а это просто не так. Я думаю, что вы неправильно диагностируете проблему. Кроме того, вы все равно не должны обращаться к TI.TypeData напрямую, вместо этого вы должны передавать TI в GetTypeData() - person Remy Lebeau; 21.04.2018
comment
@RemyLebeau .TypeData аналогичен переходу к GetTypeData, это метод для TTypeInfo. Кроме того, он не ошибается в диагностике проблемы. Кажется, что в typeinfo для типа анонимного метода есть некоторые мусорные данные (или некоторые флаги, которые не задокументированы). - person Stefan Glienke; 23.04.2018
comment
К счастью, это не настоящая проблема. Но мне просто интересно почему. Прямо сейчас я думаю, что данные ненадежны для этого типа. - person Toon Krijthe; 24.04.2018
comment
Я обновил свой ответ. В определении TIntfFlag действительно отсутствуют несколько флагов. Я сообщил об этом Embarcadero. - person Remy Lebeau; 05.06.2019