Com*_*sNo 3 delphi assembly delphi-2010 basm
这个问题基于之前的问题,但这仅仅是FYI.
我已经设法让它工作了,但是,我找到了一些我不清楚的东西,所以如果有人能解释以下行为,那就太棒了.
我有以下课程:
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;
Run Code Online (Sandbox Code Playgroud)
以及以下两个程序:
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;
Run Code Online (Sandbox Code Playgroud)
为了测试工作版本,需要调用以下内容:
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;
Run Code Online (Sandbox Code Playgroud)
并且为了测试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;
Run Code Online (Sandbox Code Playgroud)
最后一个问题:为什么CallObjMethNOTWorking不工作,而CallObjMethWorking是什么?我猜测编译器如何对待TMethod有一些特别之处...但由于我的汇编知识有限,我无法理解.
如果有人能向我解释一下,我将非常感谢,谢谢!
HenrickHellström对他的回答是正确的,我注意到你的问题是用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;
Run Code Online (Sandbox Code Playgroud)
有几个解释性说明:
1)ASM声明:在Delphi XE2 x64中没有pascal和asm代码的混合,因此编写汇编代码的唯一方法是在一个由一个asm..end 块组成的例程中,no begin..end.请注意,begin..end32位asm代码周围也会产生影响.具体来说,您正在强制生成堆栈帧,并让编译器生成函数参数的本地副本.(如果你首先使用程序集,你可能不希望编译器这样做.)
2)调用约定:在Win64上,只有一个调用约定.事情就像register并且stdcall实际上毫无意义; 微软的Win64调用约定完全相同.它本质上是这样的:参数传递中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字节对齐(对于ESPMacOSX32,btw.).这通常会导致类似于以下内容的sub rsp, ##; call $$; add rsp, ##构造:##将是要调用函数的(QWORD)参数的总和,以及用于对齐的可选8字节RSP.需要注意的是对齐RSP的CALL网站导致RSP = ###8h在函数入口(因为CALL把堆栈上的返回地址),因此假设没有与弄乱RSP你这样做之前,你可以期望它是.
在提供的示例中,SSE2 MOVDQA指令用于测试对齐RSP.(XMM5用作目标寄存器,因为它可以自由修改但不能包含任何函数参数数据).
4)假设
此处的代码假定编译器不插入要更改的代码RSP.可能存在这种情况可能不正确的情况,因此请注意做出这种假设.
5)异常处理 Win64中的异常处理有点复杂,应该由编译器正确完成(上面的示例代码不会这样做).为了让编译器能够这样做,理想情况下,您的代码应该使用新的BASM指令/伪指令.PARAMS,.PUSHNV并且.SAVENV如Allen Bauer所述.鉴于正确(错误)的情况,否则会发生不好的事情.
| 归档时间: |
|
| 查看次数: |
1800 次 |
| 最近记录: |