Этот хак не очень полезен на практике, он был сделан под вдохновением от комментария к посту о published методах. Тогда я начал расследование, как это можно сделать. Вспомните, что компилятор сейчас не кодирует сигнатуру метода в RTTI для published методов - сохраняются только адрес и имя метода.
Так что с первого взгляда кажется, что получить эту информацию - невозможно. Но давайте сделаем шаг назад и подумаем о том, как IDE обрабатывает события и published методы в design-time. Если у вас уже объявлено несколько обработчиков событий (реализованных в нескольких published методах формы), то инспектор объектов отфильтрует их и покажет только те методы (в выпадающем списке), сигнатура которых совместима с событием. Как IDE узнаёт, какие методы нужно показывать в списке, а какие - нет?
Ну, поскольку каждый компонент из design-time скомпилирован в пакет и зарегистрирован в IDE, то у IDE есть полный доступ к RTTI компонента (прим.пер.: а доступа к исходному коду компонента может и не быть). Как мы (вероятно) увидим в следующей статье, каждое published свойство-событие (OnClick, OnSelected и т.п.) описывается компилятором с помощью RTTI, которая включает в себя информацию о параметрах события. Из этой информации IDE извлекает число и типы параметров, так что присваиваемый published метод должен иметь это же число параметров тех же типов. IDE также использует эту RTTI информацию для построения правильной сигнатуры метода, когда вы создаёте новый обработчик события.
Но IDE всё ещё не имеет доступа к любой RTTI параметров published методов формы. На самом деле, IDE вообще не имеет доступа к скомпилированному представлению формы. Хотя у неё есть козырь: у IDE есть полный доступ к исходному коду формы. IDE "просто" анализирует код формы-источника и находит методы, которые имеют правильные количество и типы параметров. Этот анализ не является совершенным, и он не всегда может верно вычислить объявления псевдонимов типов, поэтому обычно типы параметров должны использовать дословные копии типов, используемых в объявлении типа события.
Это не слишком нам поможет - ведь у нас нет доступа к исходному коду формы в run-time. Но как отметил вездесущий Аноним в своем комментарии к статье о published методах, существуют декомпиляторы кода Delphi, которые способны определять типы параметров published методов в объявлении формы. Как они делают это? Ну, есть две подсказки - форма обычно содержит перечень published полей; это - ссылки на компоненты, которые потоковая система создаёт автоматически, когда она загружает .DFM. Эти поля имеют RTTI, которая включает тип класса компонента. Кроме того, у формы есть свойство-массив
Components
, содержащий ссылки на все компоненты и элементы управления, принадлежащие форме. При использовании любого из них мы получаем доступ ко всем компонентам, связанных с формой.Эти компоненты обычно имеют одно или несколько свойств-событий, назначенных на методы формы. Если эти события были назначены во время разработки, то методы, на которые они указывают, будут объявлены как published. Все свойства-события компонентов, которые могут быть назначены во время разработки, также должны быть published. Компилятор предоставляет RTTI для таких событий - в том числе информацию о параметрах событий - и, следовательно, параметрах назначенного совместимого published метода, присвоенного событию.
Всё будет немного сложнее для статических декомпиляторов Delphi, но основная цепь информации, которую нужно будет размотать, будет той же самой. Написание декомпилятора выходит за рамки этой статьи (это оставлено в качестве упражнения для читателя :) ), но давайте попробуем написать простой код, который может выяснять параметры всех published методов, которые были назначены на published события компонента.
Базовый алгоритм будет выглядеть примерно так:
- Мы принимаем параметрами
Instance
иTStrings
- Перечисляем все published методы объекта
- Для каждого published метода:
- Для экземпляра
Instance
:- Перечисляем все published события
- Получаем значение каждого свойства-события
- Если адрес published метода равен значению
Code
события, у нас есть связь - Возвращаем RTTI по параметрам типа события – эта же сигнатура используется и для published метода
- Повторяем указанные выше шаги для каждого owned-компонента
Instance
(еслиInstance
- это компонент)
- Для экземпляра
procedure GetPublishedMethodsWithParameters(Instance: TObject; List: TStrings); var i: integer; Method: PPublishedMethod; AClass: TClass; Count: integer; begin List.BeginUpdate; try List.Clear; AClass := Instance.ClassType; while Assigned(AClass) do begin Count := GetPublishedMethodCount(AClass); if Count > 0 then begin List.Add(Format('Published methods in %s', [AClass.ClassName])); Method := GetFirstPublishedMethod(AClass); for i := 0 to Count - 1 do begin List.Add(PublishedMethodToString(Instance, Method)); Method := GetNextPublishedMethod(AClass, Method); end; end; AClass := AClass.ClassParent; end; finally List.EndUpdate; end; end;
GetPublishedMethodsWithParameters
- это высокоуровневый метод, который использует подпрограммы из прошлой статьи, чтобы перечислить все published методы экземпляра объекта (Instance
). Он добавляет строковое представление каждого метода в список TStrings
. Сама конвертация published метода в строку осуществляется функцией PublishedMethodToString
:
function PublishedMethodToString(Instance: TObject; Method: PPublishedMethod): string; var MethodSignature: TMethodSignature; begin if FindPublishedMethodSignature(Instance, Method.Address, MethodSignature) then Result := MethodSignatureToString(Method.Name, MethodSignature) else Result := Format('procedure %s(???);', [Method.Name]); end;Эта функция сначала пытается получить сигнатуру метода, используя
FindPublishedMethodSignature
, и если это ей удаётся, то она использует найденную сигнатуру метода для построения строкового представления, используя MethodSignatureToString
. Скоро мы посмотрим на эти подпрограммы, но пока посмотрим на определение записи для сигнатур методов:
PMethodParam = ^TMethodParam; TMethodParam = record Flags: TParamFlags; ParamName: PShortString; TypeName: PShortString; end; TMethodParamList = array of TMethodParam; PMethodSignature = ^TMethodSignature; TMethodSignature = record MethodKind: TMethodKind; ParamCount: Byte; ParamList: TMethodParamList; ResultType: PShortString; end;Эти определения - мои собственные структуры данных, которыми проще получать доступ к RTTI типов событий без необходимости мучаться с переменной длиной записей. Мои записи дублируют информацию из raw структур RTTI в удобной форме. Сами структуры RTTI, сгенерированные компилятором, доступны через модуль
TypInfo
. Вот соответствующие объявления из модуля TypInfo
:
type TMethodKind = (mkProcedure, mkFunction, mkConstructor, mkDestructor, mkClassProcedure, mkClassFunction, { Obsolete } mkSafeProcedure, mkSafeFunction); TParamFlag = (pfVar, pfConst, pfArray, pfAddress, pfReference, pfOut); TParamFlags = set of TParamFlag; TTypeData = packed record case TTypeKind of /// ... tkMethod: ( MethodKind: TMethodKind; ParamCount: Byte; ParamList: array[0..1023] of Char {ParamList: array[1..ParamCount] of record Flags: TParamFlags; ParamName: ShortString; TypeName: ShortString; end; ResultType: ShortString}); end;Ok. Запись
TTypeData
кодирует тип события (свойство типа указатель на метод) следующим образом. Поле MethodKind
указывает, что это за вид метода – AFAICT, сейчас используются только два значения: mkProcedure
и mkFunction
, соответствуя объявлениям procedure … of object
и function … of object
соответственно. Затем идёт байт, содержащий число параметров метода, ограничивая, таким образом, число параметров в типе события до 255 :-) Затем следует упакованный массив из упакованных записей с информацией о каждом параметре; вид параметра (var
, const
, out
, array of
), имя параметра и его тип. За всеми параметрами следует строка с именем типа, который возвращает метод, если MethodKind
был mkFunction
.Поскольку
ParamName
, TypeName
и ResultType
кодируются как упакованные ShortString
, с которыми очень неудобно работать, выше я объявил записи TMethodParam
и TMethodSignature
. Вот функция GetMethodSignature
, которая конвертирует PPropInfo
события в более-простую-в-использовании TMethodSignature
:
function PackedShortString(Value: PShortstring; var NextField{: Pointer}): PShortString; overload; begin Result := Value; PShortString(NextField) := Value; Inc(PChar(NextField), SizeOf(Result^[0]) + Length(Result^)); end; function PackedShortString(var NextField{: Pointer}): PShortString; overload; begin Result := PShortString(NextField); Inc(PChar(NextField), SizeOf(Result^[0]) + Length(Result^)); end; function GetMethodSignature(Event: PPropInfo): TMethodSignature; type PParamListRecord = ^TParamListRecord; TParamListRecord = packed record Flags: TParamFlags; ParamName: {packed} ShortString; // на самом деле: string[Length(ParamName)] TypeName: {packed} ShortString; // на самом деле: string[Length(TypeName)] end; var EventData: PTypeData; i: integer; MethodParam: PMethodParam; ParamListRecord: PParamListRecord; begin Assert(Assigned(Event) and Assigned(Event.PropType)); Assert(Event.PropType^.Kind = tkMethod); EventData := GetTypeData(Event.PropType^); Result.MethodKind := EventData.MethodKind; Result.ParamCount := EventData.ParamCount; SetLength(Result.ParamList, Result.ParamCount); ParamListRecord := @EventData.ParamList; for i := 0 to Result.ParamCount - 1 do begin MethodParam := @Result.ParamList[i]; MethodParam.Flags := ParamListRecord.Flags; MethodParam.ParamName := PackedShortString(@ParamListRecord.ParamName, ParamListRecord); MethodParam.TypeName := PackedShortString(ParamListRecord); end; Result.ResultType := PackedShortString(ParamListRecord); end;Она использует парочку перегруженных (overload) вспомогательных функций, чтобы получить упакованные
ShortString
и перейти к следующей записи. Мне также пришлось переобъявить запись TParamListRecord
, т.к. вариант в TypInfo
закомментирован. Мы, вероятно, разберём структуры PPropInfo
позже – а в этом контексте достаточно сказать, что мы можем получить из сигнатуры метода типа события интересную информацию и вернуть её в удобном и полезном формате.Теперь у нас есть два несвязанных куска кода: у нас есть код, который перечисляет все published методы, пытаясь сконвертировать их в строковый формат, и у нас есть код, который получает сигнатуру метода по свойству-событию. Теперь нам надо соединить эти два куска кода, чтобы получить что-то полезное. Нам не хватает двух кусочков: поиск свойства-события, которое указывало бы на данный published метод и конвертация сигнатуры метода в строку.
Смотря на высокоуровневый алгоритм, который мы определили выше, нам надо пройтись по всем published событиям. Вот код, который это делает:
function FindEventProperty(Instance: TObject; Code: Pointer): PPropInfo; var Count: integer; PropList: PPropList; i: integer; Method: TMethod; begin Assert(Assigned(Instance)); Count := GetPropList(Instance, PropList); if Count > 0 then try for i := 0 to Count - 1 do begin Result := PropList^[i]; if Result.PropType^.Kind = tkMethod then begin Method := GetMethodProp(Instance, Result); if Method.Code = Code then Exit; end; end; finally FreeMem(PropList); end; Result := nil; end;Этот код получает список всех published свойств, отфильтровывая только свойства-события (
tkMethod
), получает текущее значение свойства и проверяет, не указывает ли оно на заданный адрес. Если да, то мы возвращаем PPropInfo
метода-события, иначе мы возвращаем nil
. Этот код проверяет только один экземпляр, но нам надо проверить все owned-компоненты (если экземпляр класса является наследником TComponent
) – так что давайте сделаем подпрограмму для рекурсивного вызова:
function FindEventFor(Instance: TObject; Code: Pointer): PPropInfo; var i: integer; Component: TComponent; begin Result := FindEventProperty(Instance, Code); if Assigned(Result) then Exit; if Instance is TComponent then begin Component := TComponent(Instance); for i := 0 to Component.ComponentCount - 1 do begin Result := FindEventFor(Component.Components[i], Code); if Assigned(Result) then Exit; end; end; Result := nil; // TODO: проверить published поля end;Эта функция пытается найти свойство-событие, которому присвоен заданный адрес. Она ищет свойство в самом объекте, а также во всех принадлежащих ему компонентах (только для объектов-компонентов).
Здесь мы используем массив
Components
, который имеют все компоненты, чтобы проверить, имеет ли какой-то подкомпонент интересующее нас свойство-событие. Как указываем комментарий, мы могли бы также (или вместо) проверять объекты, на которые указывают published поля. Поскольку RTL не содержит аналогичных легко-используемых функций доступа для перечисления всех published полей, и мы пока не зашли так далеко в нашей серии по раскопке внутренностей компилятора Delphi, то я пока опускаю эту возможность. Кроме того, published поля и массив Components
(в основном) дублируют друг друга.Теперь у нас достаточно служебного кода, чтобы собрать всё вместе. Вот функция
FindPublishedMethodSignature
, которую вызывает функция PublishedMethodToString
выше:
function FindPublishedMethodSignature(Instance: TObject; Code: Pointer; var MethodSignature: TMethodSignature): boolean; var Event: PPropInfo; begin Assert(Assigned(Code)); Event := FindEventFor(Instance, Code); Result := Assigned(Event); if Result then MethodSignature := GetMethodSignature(Event); end;Функция сначала использует рекурсивную
FindEventFor
для поиска PPropInfo
события, которая описывает метод. И если она её находит, то она конвертирует тяжело-используемую PPropInfo
в легко-используемую TMethodSignature
. Наконец, нам осталось написать только код, который конвертирует запись TMethodSignature
в строковое представление метода:
function MethodKindString(MethodKind: TMethodKind): string; begin case MethodKind of mkSafeProcedure, mkProcedure : Result := 'procedure'; mkSafeFunction, mkFunction : Result := 'function'; mkConstructor : Result := 'constructor'; mkDestructor : Result := 'destructor'; mkClassProcedure: Result := 'class procedure'; mkClassFunction : Result := 'class function'; end; end; function MethodParamString(const MethodParam: TMethodParam; ExcoticFlags: boolean = False): string; begin if pfVar in MethodParam.Flags then Result := 'var ' else if pfConst in MethodParam.Flags then Result := 'const ' else if pfOut in MethodParam.Flags then Result := 'out ' else Result := ''; if ExcoticFlags then begin if pfAddress in MethodParam.Flags then Result := '{addr} ' + Result; if pfReference in MethodParam.Flags then Result := '{ref} ' + Result; end; Result := Result + MethodParam.ParamName^ + ': '; if pfArray in MethodParam.Flags then Result := Result + 'array of '; Result := Result + MethodParam.TypeName^; end; function MethodParametesString(const MethodSignature: TMethodSignature): string; var i: integer; MethodParam: PMethodParam; begin Result := ''; for i := 0 to MethodSignature.ParamCount - 1 do begin MethodParam := @MethodSignature.ParamList[i]; Result := Result + MethodParamString(MethodParam^); if i < MethodSignature.ParamCount-1 then Result := Result + '; '; end; end; function MethodSignatureToString(const Name: string; const MethodSignature: TMethodSignature): string; begin Result := Format('%s %s(%s)', [MethodKindString(MethodSignature.MethodKind), Name, MethodParametesString(MethodSignature)]); if Length(MethodSignature.ResultType^) > 0 then Result := Result + ': ' + MethodSignature.ResultType^; Result := Result + ';'; end;Фух! Эта статья получилась ужасно длинной и с кучей кода! Но теперь у нас есть серьёзный (и довольно бесполезный) код для выдирания параметров published методов. Заметьте, что он работает только по экземпляру объекта, который должен иметь published свойство, указывающее на published метод. Хорошие новости: это всегда так для самых интересных published методов – вроде обработчиков событий на
TForm
. Плохие новости: это не будет работать для любых published методов, вызываемых вручную в run-time (поскольку метод не будет присваиваться событию).Если вы всё ещё с нами, то сейчас вы можете написать код для проверки работы:
type {$M+} TMyClass = class; TOnFour = function (A: array of byte; const B: array of byte; var C: array of byte; out D: array of byte): TComponent of object; TOnFive = procedure (Component1: TComponent; var Component2: TComponent; out Component3: TComponent; const Component4: TComponent) of object; TOnSix = function (const A: string; var Two: integer; out Three: TMyClass; Four: PInteger; Five: array of Byte; Six: integer): string of object; TMyClass = class private FOnFour: TOnFour; FOnFive: TOnFive; FOnSix: TOnSix; published function FourthPublished(A: array of byte; const B: array of byte; var C: array of byte; out D: array of byte): TComponent; procedure FifthPublished(Component1: TComponent; var Component2: TComponent; out Component3: TComponent; const Component4: TComponent); function SixthPublished(const A: string; var Two: integer; out Three: TMyClass; Four: PInteger; Five: array of Byte; Six: integer): string; property OnFour: TOnFour read FOnFour write FOnFour; property OnFive: TOnFive read FOnFive write FOnFive; property OnSix: TOnSix read FOnSix write FOnSix; end; function TMyClass.FourthPublished; begin Result := nil; end; procedure TMyClass.FifthPublished; begin end; function TMyClass.SixthPublished; begin end; procedure DumpPublishedMethodsParameters(Instance: TObject); var i : integer; List: TStringList; begin List := TStringList.Create; try GetPublishedMethodsWithParameters(Instance, List); for i := 0 to List.Count - 1 do WriteLn(List[i]); finally List.Free; end; end; procedure Test; var MyClass: TMyClass; begin MyClass := TMyClass.Create; MyClass.OnFour := MyClass.FourthPublished; MyClass.OnFive := MyClass.FifthPublished; MyClass.OnSix := MyClass.SixthPublished; DumpPublishedMethodsParameters(MyClass); end; begin Test; ReadLn; end.После запуска мы получаем:
Published methods in TMyClass function FourthPublished(A: array of Byte; const B: array of Byte; var C: array of Byte; out D: array of Byte): TComponent; procedure FifthPublished(Component1: TComponent; var Component2: TComponent; out Component3: TComponent; const Component4: TComponent); function SixthPublished(const A: String; var Two: Integer; out Three: TMyClass; Four: PInteger; Five: array of Byte; Six: Integer): String;По-моему, это очень похоже! Тестовый код выше немного надуман - например, экземпляр объекта не будет назначать свои свойства-события на свои же собственные методы. Более реалистичный тест будет с формой с многочисленными published событиями, подключенными в design-time. Я загрузил проект
\Demos\RichEdit\RichEdit.dpr
, поставляемый с Delphi 7 (в Delphi 2006 путь будет \Demos\DelphiWin32\VCLWin32\RichEdit\RichEdit.bdsproj
). На главной форме из модуля remain.pas
я добавил мой модуль HVPublishedMethodParams
в предложение uses
и изменил обработчик Help | About:
procedure TMainForm.HelpAbout(Sender: TObject); begin GetPublishedMethodsWithParameters(Self, Editor.Lines); { with TAboutBox.Create(Self) do try ShowModal; finally Free; end;} end;Этот код сдампит все published методы формы в редактор – пытаясь сопоставить их с событиями с RTTI информацией, чтобы узнать сигнатуру методов. Когда я запускаю приложение и выбираю Help | About, редактор заполняется таким текстом:
Published methods in TMainForm procedure SelectionChange(Sender: TObject); procedure FormCreate(Sender: TObject); procedure ShowHint(???); procedure FileNew(Sender: TObject); procedure FileOpen(Sender: TObject); procedure FileSave(Sender: TObject); procedure FileSaveAs(Sender: TObject); procedure FilePrint(Sender: TObject); procedure FileExit(Sender: TObject); procedure EditUndo(Sender: TObject); procedure EditCut(Sender: TObject); procedure EditCopy(Sender: TObject); procedure EditPaste(Sender: TObject); procedure HelpAbout(Sender: TObject); procedure SelectFont(Sender: TObject); procedure RulerResize(Sender: TObject); procedure FormResize(Sender: TObject); procedure FormPaint(Sender: TObject); procedure BoldButtonClick(Sender: TObject); procedure ItalicButtonClick(Sender: TObject); procedure FontSizeChange(Sender: TObject); procedure AlignButtonClick(Sender: TObject); procedure FontNameChange(Sender: TObject); procedure UnderlineButtonClick(Sender: TObject); procedure BulletsButtonClick(Sender: TObject); procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean); procedure RulerItemMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X: Integer; Y: Integer); procedure RulerItemMouseMove(Sender: TObject; Shift: TShiftState; X: Integer; Y: Integer); procedure FirstIndMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X: Integer; Y: Integer); procedure LeftIndMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X: Integer; Y: Integer); procedure RightIndMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X: Integer; Y: Integer); procedure FormShow(Sender: TObject); procedure RichEditChange(Sender: TObject); procedure SwitchLanguage(Sender: TObject); procedure ActionList2Update(Action: TBasicAction; var Handled: Boolean);Это довольно хорошая дословная копия published методов в разделе interface модуля формы. Только метод
ShowHint
не имеет параметров. Это потому, что этот published метод не присвоен событию в design-time. Вместо этого, он назначается и используется во время выполнения:
procedure ShowHint(Sender: TObject); ///… procedure TMainForm.FormCreate(Sender: TObject); begin Application.OnHint := ShowHint; //… end;Объект
TApplication
не публикует какие-либо свои свойства, так что нет и простого способа получения параметров ShowHint
. А между тем, метод ShowHint
, объявленный как published - это ошибка дизайна. По всей логике, этот метод должен быть private.На этом мы завершаем этот интригующий, но AFAICS, бесполезный хак. Теперь вы должны иметь лучшее понимание того, как published методы и published события связаны друг с другом в run-time, и как декомпиляторы Delphi могут совершать некоторые свои волшебные трюки. Мы также показали, как много информации о вашей программе хранится в EXE-файле - вам лучше убедиться, что в именах ваших published методов, событий или их параметров и типов не содержится конфиденциальная информация :-)
Надеюсь, вы наслаждались процессом!
респект) и благодарности за статью)
ОтветитьУдалитьна самом деле не так уж бесполезно.
мне кажется любой синтаксический анализатор командной строки консольного приложения в лучшей своей версии должен использовать похожий подход.
кстати в модуле rtti.pas объявлен такой класс как TRttiMethod, у которого есть
ОтветитьУдалитьfunction GetParameters: TArray; virtual; abstract;
я не проверял что из себя представляет, это какое-то нововведение? я в 2010.
Скажите, можно с помощью этой технологии передать в качестве параметра имя свойства и изменить его в процедуре? примерно так
ОтветитьУдалитьprocedure UniversalSetProperty (ComponentName, PropertyName: string; value: variant);
begin
// здесь происходит магия
// свойству с именем PropertyName компонента с именем ComponentName
// присваивается значение value
end
...
UniversalSetProperty('Label1', 'enabled', true);
UniversalSetProperty('Label2', 'visible', false);
---
Конечно. А как же форма загружается? См. модуль TypInfo, функции типа SetOrdProp, SetStrProp и др.
УдалитьДля получения параметров методов (имена и типы) есть альтернатива:
ОтветитьУдалитьinterface
uses TypInfo;
type
TParamData = record
Flags: TParamFlags;
ParamName: ShortString;
end;
RTTI = ^TParamData;
implementation
procedure GetParamEvent(Info: PTypeInfo; TS: TStrings);
var
Data: PTypeData;
P: RTTI;
N: Integer;
S: string;
S_Type,
S_Return: ^ShortString;
begin
try
if Info^.Kind<>tkMethod then
begin
raise Exception.Create ('Категория типа не соответствует "tkMethod"');
exit;
end;
Data:= GetTypeData(Info);
TS.Add('Имя типа : ' + Info^.Name);
TS.Add('Категория типа : ' + GetEnumName(TypeInfo(TTypeKind), Integer(Info^.Kind)));
TS.Add('Категория метода : '+ GetEnumName(TypeInfo (TMethodKind), Integer (Data^.MethodKind)));
TS.Add('Всего параметров : ' +IntToStr(Data^.ParamCount));
TS.Add('');
P:= RTTI(@(Data^.ParamList));
N:= 1;
//
while N<=Data^.ParamCount do
begin
//
S:= '#'+IntToStr(N)+' - ';
//
if pfVar in P^.Flags then
S:= S+'var ';
//
if pfConst in P^.Flags then
S:= S+'const ';
//
if pfOut in P^.Flags then
S:= S+'out ';
//
S:= S+P^.ParamName+' : ';
//
if pfArray in P^.Flags then
S:= S+' array of ';
//
S_Type:= Pointer(Integer(P) + SizeOf(TParamFlags) + Length(P^.ParamName)+1);
//
S:= S+S_Type^;
//
TS.Add(S);
//
P:= RTTI(Integer(P) +SizeOf(TParamFlags) +
Length(P^.ParamName)+1 +Length(S_Type^)+1);
Inc(N);
end;
//
if Data^.MethodKind = mkFunction then
begin
S_Return:= Pointer(P);
TS.Add('Результат : ' + S_Return^);
end
else //
begin
TS.Add('Результат : Отсутствует');
end;
end;