Расширенная RTTI информация классовИсточник: gunsmoker Hallvard Vassbotn
Расширенная RTTI информация классовКак я упоминал ранее, Delphi (начиная с версии 7) поддерживает генерацию расширенной RTTI информации о методах класса - через компиляцию класса в режиме Я сумел написать свои собственные определения и подпрограммы, которые выдёргивают и сохраняют расширенную RTTI информацию классов в формат, удобный для внешнего использования. Как обычно, моё тестовое приложение будет дампить тестовый класс, воссоздавая его псевдо-объявление. Мы уже привыкли раскапывать внутренние структуры RTTI, так что давайте лишь поверхностно пробежимся по новому коду. Итак, для начала у нас есть новые определения записей, описывающих приблизительную раскладку по памяти внутренних структур RTTI, генерируемых компилятором - выцарапанные из "официального" источника: type PReturnInfo = ^TReturnInfo; TReturnInfo = packed record Version: Byte; CallingConvention: TCallConv; ReturnType: PPTypeInfo; ParamSize: Word; end; PParamInfo = ^TParamInfo; TParamInfo = packed record Flags: TParamFlags; ParamType: PPTypeInfo; Access: Word; Name: ShortString; end;Как найти начала этих структур - это немного сложный вопрос. Помните статью Под капотом published методов? В то время я не знал про расширенную RTTI информацию и директиву $MethodInfo , поэтому написал:
Как вы можете видеть выше, таблица published методов теперь имеет типКак оказалось сейчас, поле Size в самом деле используется для вставки дополнительных записей (TReturnInfo и TParamInfo ) прямо за полем Name записи TPublishedMethod : type PPublishedMethod = ^TPublishedMethod; TPublishedMethod = packed record Size: Word; Address: Pointer; Name: {packed} ShortString; end;Чтобы найти и декодировать сигнатуру метода, нам необходимо определить число дополнительных байт, указанных в поле Size . Мы скоро увидим код для этого.
Далее у нас есть структуры, которые могут хранить декодированную RTTI информацию одного класса, включая информацию по методам секций public/published со всеми их параметрами и возвращаемыми типами: type // Просто-используемые структуры фиксированного размера PClassInfo = ^TClassInfo; TClassInfo = record UnitName: string; Name: string; ClassType: TClass; ParentClass: TClass; MethodCount: Word; Methods: array of TMethodSignature; end;Это определение должно быть в большой степени само-документирующимся. Как вы можете видеть, мы использовали ту же запись TMethodSignature , которую мы использовали для интерфейсов. Ладно, теперь мы более-менее готовы к написанию кода для конвертирования информации типа класса в наши структуры выше. Это подразумевает испачкать наши руки итерацией по всем public/published методам и дополнительной RTTI информации. После нескольких неудачных попыток и подсматриваний в ObjAuto , я пришёл к такому коду: function ClassOfTypeInfo(P: PPTypeInfo): TClass; begin Result := nil; if Assigned(P) and (P^.Kind = tkClass) then Result := GetTypeData(P^).ClassType; end; procedure GetClassInfo(ClassTypeInfo: PTypeInfo; var ClassInfo: TClassInfo); // Конвертирует raw структуры RTTI в наши user-friendly структуры var TypeData: PTypeData; i, j: integer; MethodInfo: PMethodSignature; PublishedMethod: PPublishedMethod; MethodParam: PMethodParam; ReturnRTTI: PReturnInfo; ParameterRTTI: PParamInfo; SignatureEnd: Pointer; begin Assert(Assigned(ClassTypeInfo)); Assert(ClassTypeInfo.Kind = tkClass); // Класс TypeData := GetTypeData(ClassTypeInfo); ClassInfo.UnitName := TypeData.UnitName; ClassInfo.ClassType := TypeData.ClassType; ClassInfo.Name := TypeData.ClassType.ClassName; ClassInfo.ParentClass := ClassOfTypeInfo(TypeData.ParentInfo); ClassInfo.MethodCount := GetPublishedMethodCount(ClassInfo.ClassType); SetLength(ClassInfo.Methods, ClassInfo.MethodCount); // Методы PublishedMethod := GetFirstPublishedMethod(ClassInfo.ClassType); for i := Low(ClassInfo.Methods) to High(ClassInfo.Methods) do begin // Метод MethodInfo := @ClassInfo.Methods[i]; MethodInfo.Name := PublishedMethod.Name; MethodInfo.Address := PublishedMethod.Address; MethodInfo.MethodKind := mkProcedure; // Предположим процедуру по умолчанию // Возвращаемое значение и соглашение вызова ReturnRTTI := Skip(@PublishedMethod.Name); SignatureEnd := Pointer(Cardinal(PublishedMethod) + PublishedMethod.Size); if Cardinal(ReturnRTTI) >= Cardinal(SignatureEnd) then begin MethodInfo.CallConv := ccReg; // Предположим register MethodInfo.HasSignatureRTTI := False; end else begin MethodInfo.ResultTypeInfo := Dereference(ReturnRTTI.ReturnType); if Assigned(MethodInfo.ResultTypeInfo) then begin MethodInfo.MethodKind := mkFunction; MethodInfo.ResultTypeName := MethodInfo.ResultTypeInfo.Name; end else MethodInfo.MethodKind := mkProcedure; MethodInfo.CallConv := ReturnRTTI.CallingConvention; MethodInfo.HasSignatureRTTI := True; // Считаем параметры ParameterRTTI := Pointer(Cardinal(ReturnRTTI) + SizeOf(ReturnRTTI^)); MethodInfo.ParamCount := 0; while Cardinal(ParameterRTTI) < Cardinal(SignatureEnd) do begin Inc(MethodInfo.ParamCount); // Предполагаем, что будет менее 255 параметров! ;) ParameterRTTI := Skip(@ParameterRTTI.Name); end; // Читаем информацию о параметрах ParameterRTTI := Pointer(Cardinal(ReturnRTTI) + SizeOf(ReturnRTTI^)); SetLength(MethodInfo.Parameters, MethodInfo.ParamCount); for j := Low(MethodInfo.Parameters) to High(MethodInfo.Parameters) do begin MethodParam := @MethodInfo.Parameters[j]; MethodParam.Flags := ParameterRTTI.Flags; if pfResult in MethodParam.Flags then MethodParam.ParamName := 'Result' else MethodParam.ParamName := ParameterRTTI.Name; MethodParam.TypeInfo := Dereference(ParameterRTTI.ParamType); if Assigned(MethodParam.TypeInfo) then MethodParam.TypeName := MethodParam.TypeInfo.Name; MethodParam.Location := TParamLocation(ParameterRTTI.Access); ParameterRTTI := Skip(@ParameterRTTI.Name); end; end; PublishedMethod := GetNextPublishedMethod(ClassInfo.ClassType, PublishedMethod); end; end;Как обычно, мы тестируем код, определяя какой-то глупый класс и используя RTTI для реконструирования его объявления. Вот упрощённый тестовый проект: program TestHVMethodInfoClasses; {$APPTYPE CONSOLE} uses SysUtils, TypInfo, HVMethodSignature in 'HVMethodSignature.pas', HVMethodInfoClasses in 'HVMethodInfoClasses.pas'; procedure DumpClass(ClassTypeInfo: PTypeInfo); var ClassInfo: TClassInfo; i: integer; begin GetClassInfo(ClassTypeInfo, ClassInfo); WriteLn('unit ', ClassInfo.UnitName, ';'); WriteLn('type'); Write(' ', ClassInfo.Name, ' = '); Write('class'); if Assigned(ClassInfo.ParentClass) then Write(' (', ClassInfo.ParentClass.ClassName, ')'); WriteLn; for i := Low(ClassInfo.Methods) to High(ClassInfo.Methods) do WriteLn(' ', MethodSignatureToString(ClassInfo.Methods[i])); WriteLn(' end;'); WriteLn; end; type {$METHODINFO OFF} TNormalClass = class end; TSetOfByte = set of byte; TEnum = (enOne, enTwo, enThree); type {$METHODINFO ON} TMyClass = class public function Test1(const A: string): string; function Test2(const A: string): byte; procedure Test3(R: integer); procedure Test4(R: TObject); procedure Test5(R: TNormalClass); procedure Test6(R: TSetOfByte); procedure Test7(R: shortstring); procedure Test8(R: openstring); procedure Test9(R: TEnum); function Test10: TNormalClass; function Test11: integer; function Test18: shortstring; function Test19: TObject; function Test20: IInterface; function Test21: TSetOfByte; function Test22: TEnum; end; // ... вырезаны реализации-пустышки методов класса TMyClass ... procedure Test; begin DumpClass(TypeInfo(TMyClass)); end; begin try Test; except on E: Exception do WriteLn(E.Message); end; readln; end.И вывод тестового кода: unit TestHVMethodInfoClasses; type TMyClass = class (TObject) function Test1(A: String): String; function Test2(A: String): Byte; procedure Test3(R: Integer); procedure Test4(R: TObject); procedure Test5(R: TNormalClass); procedure Test6(R: TSetOfByte); procedure Test7(R: ShortString); procedure Test8(R: ShortString); procedure Test9(R: TEnum); function Test10(): TNormalClass; function Test11(): Integer; function Test18(): ShortString; function Test19(): TObject; function Test20(): IInterface; function Test21(): TSetOfByte; function Test22(): TEnum; end;Полный исходный код доступен на CodeCentral. Как отметил мой прилежный читатель, Ralf, вывод этой программы является дословной копией исходного кода. Помимо моей небрежности по не опусканию пустых скобок в функциях: строковые параметры пока не объявлены как Фактически я пытался (до сих пор - не успешно) уговорить |