#delphi #assembly #delphi-2007
#delphi #сборка #delphi-2007
Вопрос:
Я пытаюсь разработать некоторый код для выполнения общих вызовов методов по его имени. Например, кто-то из Интернета отправляет мне текст как ‘TTest.MethodTest.Param1.Param2’, и я нахожу класс и вызываю его метод по его имени с параметрами. Хорошо, я сделал это, я получил кое-какой код от Андреаса Хаусладена, сделал небольшие корректировки, чтобы работать там, где мне нужно. Но реализация ExecuteAsyncCall заключалась в создании функций cdecl, мне нужно изменить его код для работы с конвенционными методами pascal.
Вот пример кода, если кто-то захочет протестировать. Кто-нибудь может мне помочь? Я учусь решать это, но для меня это сложно.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
published
{ Public declarations }
procedure Test(AString: string; AInteger: Integer); cdecl;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
function CopyVarRec(const Data: TVarRec): TVarRec;
begin
if (Data.VPointer <> nil) and
(Data.VType in [vtString, vtAnsiString, vtWideString,
{$IFDEF UNICODE}vtUnicodeString,{$ENDIF} vtExtended,
vtCurrency, vtInt64, vtVariant, vtInterface]) then
begin
Result.VType := Data.VType;
Result.VPointer := nil;
{ Copy and redirect TVarRec data to prevent conflicts with other threads,
especially the calling thread. Otherwise reference counted types could
be freed while this asynchron function is still executed. }
case Result.VType of
vtAnsiString: AnsiString(Result.VAnsiString) := AnsiString(Data.VAnsiString);
vtWideString: WideString(Result.VWideString) := WideString(Data.VWideString);
{$IFDEF UNICODE}
vtUnicodeString: UnicodeString(Result.VUnicodeString) := UnicodeString(data.VUnicodeString);
{$ENDIF UNICODE}
vtInterface : IInterface(Result.VInterface) := IInterface(Data.VInterface);
vtString : begin New(Result.VString); Result.VString^ := Data.VString^; end;
vtExtended : begin New(Result.VExtended); Result.VExtended^ := Data.VExtended^; end;
vtCurrency : begin New(Result.VCurrency); Result.VCurrency^ := Data.VCurrency^; end;
vtInt64 : begin New(Result.VInt64); Result.VInt64^ := Data.VInt64^; end;
vtVariant : begin New(Result.VVariant); Result.VVariant^ := Data.VVariant^; end;
end;
end
else
Result := Data;
end;
function ExecuteAsyncCall(AProc: Pointer; MethodData: TObject; const AArgs: array of const): Integer;
var
I: Integer;
V: ^TVarRec;
ByteCount: Integer;
FArgs: array of TVarRec;
FProc: function: Integer register;
begin
FProc := AProc;
SetLength(FArgs, 1 Length(AArgs));
// insert "Self"
FArgs[0].VType := vtObject;
FArgs[0].VObject := MethodData;
for I := 0 to High(AArgs) do
FArgs[I 1] := CopyVarRec(AArgs[I]);
ByteCount := Length(FArgs) * SizeOf(Integer) $40;
{ Create a zero filled buffer for functions that want more arguments than
specified. }
asm
xor eax, eax
mov ecx, $40 / 8
@@FillBuf:
push eax
push eax
// push eax
dec ecx
jnz @@FillBuf
end;
for I := High(FArgs) downto 0 do // cdecl => right to left
begin
V := @FArgs[I];
case V.VType of
vtInteger: // [const] Arg: Integer
asm
mov eax, V
push [eax].TVarRec.VInteger
end;
vtBoolean, // [const] Arg: Boolean
vtChar: // [const] Arg: AnsiChar
asm
mov eax, V
xor edx, edx
mov dl, [eax].TVarRec.VBoolean
push edx
end;
vtWideChar: // [const] Arg: WideChar
asm
mov eax, V
xor edx, edx
mov dx, [eax].TVarRec.VWideChar
push edx
end;
vtExtended: // [const] Arg: Extended
asm
add [ByteCount], 8 // two additional DWORDs
mov eax, V
mov edx, [eax].TVarRec.VExtended
movzx eax, WORD PTR [edx 8]
push eax
push DWORD PTR [edx 4]
push DWORD PTR [edx]
end;
vtCurrency, // [const] Arg: Currency
vtInt64: // [const] Arg: Int64
asm
add [ByteCount], 4 // an additional DWORD
mov eax, V
mov edx, [eax].TVarRec.VCurrency
push DWORD PTR [edx 4]
push DWORD PTR [edx]
end;
vtString, // [const] Arg: ShortString
vtPointer, // [const] Arg: Pointer
vtPChar, // [const] Arg: PChar
vtObject, // [const] Arg: TObject
vtClass, // [const] Arg: TClass
vtAnsiString, // [const] Arg: AnsiString
{$IFDEF UNICODE}
vtUnicodeString, // [const] Arg: UnicodeString
{$ENDIF UNICODE}
vtPWideChar, // [const] Arg: PWideChar
vtVariant, // const Arg: Variant
vtInterface, // [const]: IInterface
vtWideString: // [const] Arg: WideString
asm
mov eax, V
push [eax].TVarRec.VPointer
end;
end;
end;
Result := FProc;
asm // cdecl => we must clean up
add esp, [ByteCount]
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
ExecuteAsyncCall(Self.MethodAddress('Test'), Self, ['Test ', 1])
end;
procedure TForm1.Test(AString: string; AInteger: Integer);
begin
ShowMessage(AString IntToStr(AInteger));
end;
end.
Att.
Obs: Я работаю над Delphi 2007
Комментарии:
1. Стоит отметить, что такого рода материалы являются идеальным вариантом использования для расширенного RTTI Delphi 2010. Он поставляется с динамическими процедурами вызова, которые заботятся о соглашении о вызове и других деталях реализации для вас; все, что вам нужно, это параметры и ссылка RTTI на метод.
2. Да, @Mason Wheeler :/ , но у меня нет лицензии Delphi 2010.
3. В этом коде произошла утечка памяти.
CopyVarRec
выделяет память, но никто ее не освобождает. В любом случае вам не нужно создавать копию этих параметров. Вы можете просто переслать их напрямую. Кроме того, я действительно не думаю, что вам нужно беспокоиться о соглашении о вызовах pascal в любом случае. Я не видел, чтобы оно использовалось с 1995 года, и это было для функций DLL Windows 3.1. То, что вы должны обработать, — это функции stdcall и register. Stdcall довольно прост; зарегистрироваться будет сложно.4. Я просто пытаюсь изменить этот код для работы с соглашением pascal, чтобы не было необходимости переводить все функции моей системы на cdecl. Я попытаюсь внести изменения, о которых вы сказали, чтобы проверить результаты, tks.
5. @SacI — вы уверены, что не имеете в виду
__fastcall
соглашение? Это соглашение, которое Delphi (современный объектный Паскаль) использует по умолчанию.
Ответ №1:
Соглашение о вызовах pascal передает параметры слева направо, тогда как cdecl передает их справа налево. Чтобы учесть это различие, просто измените порядок, в котором параметры помещаются в стек:
for I := High(FArgs) downto 0 do // cdecl => right to left
for I := 0 to High(FArgs) do // pascal => left to right
Далее, Self
параметр метода передается последним вместо первого в соглашении pascal. Конечным результатом является то, что в обоих соглашениях, Self
является последним параметром, помещенным в стек. Вы можете добавить его в конец вашего FArgs
массива, но если бы это был мой код, я бы просто вставил его вручную после цикла основного аргумента (что также позволило бы полностью исключить массив второго аргумента):
asm
push [MethodData]
end;
Наконец, в соглашении pascal получатель очищает стек, тогда как в cdecl его очищает вызывающий. Удалите этот код:
asm // cdecl => we must clean up
add esp, [ByteCount]
end;
// pascal => do nothing
Код также допускает вызов функций с меньшим количеством параметров, чем ожидает целевая функция. Он выделяет 40-байтовый буфер и заполняет его нулями. Однако это не будет работать с функцией pascal. Функция pascal всегда извлекает из стека одинаковое количество параметров, поэтому, если вы укажете неправильное количество параметров при ее вызове, вы в конечном итоге уничтожите стек, когда функция вернется. Удалите блок ассемблера под комментарием:
{ Create a zero filled buffer for functions that want more arguments than
specified. }
asm
...
end;
Вы ничего не можете сделать, чтобы проверить, получили ли вы правильное количество параметров. Все, что вы можете сделать, это убедиться, что указатель стека при возврате из функции такой же, каким он был до того, как вы начали вводить параметры.
Ответ №2:
Я согласен, но я думаю, что Self нужно нажимать последним:
http://docwiki.embarcadero.com/RADStudio/en/Program_Control
// insert "Self"
for I := 0 to High(AArgs) do
FArgs[I] := CopyVarRec(AArgs[I]);
FArgs[High(AArgs) 1].VType := vtObject;
FArgs[High(AArgs) 1].VObject := MethodData;
Но я не верю, что этот код можно использовать, и это приведет к сбою:
1) все параметры всех методов должны быть вариантами
2) неправильное количество параметров
3) неправильный тип (или порядок) параметров
Я думаю, вам нужно найти другое решение.
Комментарии:
1. «1) все параметры всех методов должны быть вариантами» В соглашении pascal?