这个问题基于previous,但这只是我们。
我已经设法让它工作了,但是,我发现了一些我不清楚的东西,所以如果有人能解释以下行为,那就太棒了。
我有以下课程:
type
TMyObj = class
published
procedure testex(const s: string; const i: integer);
end;
procedure TMyObj.testex(const s: string; const i: integer);
begin
ShowMessage(s + IntToStr(i));
end;
以及以下两个程序:
procedure CallObjMethWorking(AMethod: TMethod; const AStrValue: string; const AIntValue: Integer);
begin
asm
PUSH DWORD PTR AIntValue;
PUSH DWORD PTR AStrValue;
CALL AMethod.Code;
end;
end;
procedure CallObjMethNOTWorking(AInstance, ACode: Pointer; const AStrValue: string; const AIntValue: Integer);
begin
asm
MOV EAX, AInstance;
PUSH DWORD PTR AIntValue;
PUSH DWORD PTR AStrValue;
CALL ACode;
end;
end;
为了测试工作版本,需要调用以下内容:
procedure ...;
var
LObj: TMyObj;
LMethod: TMethod;
LStrVal: string;
LIntVal: Integer;
begin
LObj := TMyObj.Create;
try
LMethod.Data := Pointer( LObj );
LMethod.Code := LObj.MethodAddress('testex');
LStrVal := 'The year is:' + sLineBreak;
LIntVal := 2012;
CallObjMethWorking(LMethod, LStrVal, LIntVal);
finally
LObj.Free;
end; // tryf
end;
并且为了测试 NOT 工作版本:
procedure ...;
var
LObj: TMyObj;
LCode: Pointer;
LData: Pointer;
LStrVal: string;
LIntVal: Integer;
begin
LObj := TMyObj.Create;
try
LData := Pointer( LObj );
LCode := LObj.MethodAddress('testex');
LStrVal := 'The year is:' + sLineBreak;
LIntVal := 2012;
CallObjMethNOTWorking(LData, LCode, LStrVal, LIntVal);
finally
LObj.Free;
end; // tryf
end;
最后一个问题:为什么 CallObjMethNOTWorking 工作,而 CallObjMethWorking 是?我猜测编译器如何处理TMethod有一些特别之处...但由于我的汇编知识有限,我无法理解。
如果有人能向我解释一下,我将非常感谢,谢谢!
答案 0 :(得分:5)
HenrickHellström对他的answer是正确的,我注意到你的问题是用Delphi 2010标记的,因此只关注Win32。但是,你可能有兴趣看看如果你转到Win64(Delphi> = XE2)会出现什么情况,所以我在Henrick的代码中添加了一个示例Win64版本:
procedure CallObjMeth(AInstance, ACode: Pointer; const AStrValue: string; const AIntValue: Integer); stdcall;
asm
{$IFDEF CPU386}
MOV EAX, AInstance;
MOV EDX, DWORD PTR AStrValue;
MOV ECX, DWORD PTR AIntValue;
{$IFDEF MACOS}
//On MacOSX32 ESP = #######Ch here
SUB ESP, 0Ch
{$ENDIF}
CALL ACode;
{$IFDEF MACOS}
ADD ESP, 0Ch // restoring stack
{$ENDIF}
{$ENDIF}
{$IFDEF CPUX64}{$IFDEF WIN64} // <- see comments
.NOFRAME //Disable stack frame generation
//MOV RCX, AInstance {RCX} //<- not necessary because AInstance already is in RCX
MOV R10, ACode {RDX}
MOV RDX, AStrValue {R8}
MOV R8D, AIntValue {R9D}
SUB RSP, 28h //Set up stack shadow space and align stack: 4*8 bytes for 4 params + 8 bytes bytes for alignment
{$IFNDEF DO_NOT_TEST_STACK_ALIGNMENT}
MOVDQA XMM5, [RSP] //Ensure that RSP is aligned to DQWORD boundary -> exception otherwise
{$ENDIF}
CALL R10 //ACode
ADD RSP, 28h //Restore stack
{$ENDIF}{$ENDIF}
end;
有几个解释性说明:
1)ASM
语句:在Delphi XE2 x64中没有pascal和asm代码的混合,因此编写汇编代码的唯一方法是在一个由a组成的例程中单asm..end
块,无begin..end
。请注意,32位asm代码周围的begin..end
也会产生影响。具体来说,您正在强制生成堆栈帧,并让编译器生成函数参数的本地副本。 (如果你首先使用程序集,你可能不希望编译器这样做。)
2)调用约定:
在Win64上,只有一个调用约定。 register
和stdcall
之类的内容实际上毫无意义;它完全相同,Microsoft's Win64 calling convention。基本上是这样的:参数在RCX
,RDX
,R8
和R9
寄存器(和/或XMM0-XMM4
中传递,返回RAX/XMM0
中的值通过引用传递大于64位的值。
被叫函数可能会使用:RAX, RCX, RDX, R8-R11, ST(0)-ST(7), XMM0-XMM5, YMM0-YMM5, YMM6H-YMM15H
,并且必须保留RBX, RSI, RDI, RBP, R12-R15, XMM6-XMM15
。在适当的情况下,被调用函数需要发出CLD
/ EMMS
/ VZEROUPPER
指令以将CPU恢复到预期状态。
3)对齐和阴影空间
重要的是,每个函数在堆栈上都有自己的阴影空间,即使没有参数并且无论被调用的函数是否真正触及它,它至少有4个QWORD参数的堆栈空间。此外,在每个函数调用的站点(在每个CALL
语句处),RSP
预期为16字节对齐(对于MacOSX32上的ESP
相同,顺便说一句。)。
这通常会导致类似:sub rsp, ##; call $$; add rsp, ##
构造,其中##将是要调用函数的(QWORD)参数的总和,加上可选的8个字节用于RSP
的对齐。请注意,RSP
站点上CALL
的对齐会在函数输入时导致RSP = ###8h
(因为CALL
会将返回地址放在堆栈上),所以假设没有人对{ {1}}在你做之前,你可以期待它。
在提供的示例中,SSE2 RSP
指令用于测试MOVDQA
的对齐方式。 (RSP
用作目标寄存器,因为它可以自由修改但不能包含任何函数参数数据。)
4)假设
此处的代码假定编译器不插入代码来更改XMM5
。可能存在这种情况可能不正确的情况,因此请注意做出这种假设。
5)异常处理 Win64中的异常处理有点复杂,应该由编译器正确完成(上面的示例代码不会这样做)。为了使编译器能够这样做,理想情况下,您的代码应使用Allen Bauer here概述的新BASM指令/伪指令RSP
,.PARAMS
和.PUSHNV
。鉴于正确(错误)的情况,否则会发生不好的事情。
答案 1 :(得分:4)
Delphi Win32中的默认调用约定是&#34; register&#34;。第一个参数在EAX中传递,第二个参数在EDX中传递,第三个参数在ECX中传递。仅当有三个以上的参数或者传递大于4个字节的值类型时才使用堆栈,但在您的示例中并非如此。
您的第一个CallObjMethWorking过程有效,因为编译器在调用CallObjMethWorking时,已经在EDX中放置了aStrValue,在ECX中放置了aIntValue。但是,由于您没有清理两个推送指令,因此在程序返回时必然会发生不好的事情。
您的代码应如下所示。在这种情况下,stdcall指令是可选的,但最好将它用于这样的事情,以确保您的参数不会丢失,因为您在实际调用之前将寄存器用于其他目的。方法:
procedure CallObjMeth(AInstance, ACode: Pointer; const AStrValue: string; const AIntValue: Integer); stdcall;
asm
MOV EAX, AInstance;
MOV EDX, DWORD PTR AStrValue;
MOV ECX DWORD PTR AIntValue;
CALL ACode;
end;