Delphi - 调侃调用方式

技术交流,DH讲解.

本随笔,就自己一心得+笔记,而自己水平有限,所以本文也可能是水文.哈哈
讲解前先来个表格:有条件的朋友可以看加密解密(第三版)一书,哈哈,我书还是买了一些的.

方式 传值方向 传值位置 谁来平衡堆栈 备注
_cdecl 从右到左 直接压栈 调用者 默认C++的
_stdcall 从右到左 直接压栈 函数本身 Win32 API
register 从左到右 adc寄存器然后压栈 函数本身 Delphi默认的
pascal 从左到右 直接压栈 函数本身  
safecall       同stdcall
不同调用有什么区别呢?
function TestStdcall(a,b:Integer):Integer ;stdcall;
var
  c:Integer;
begin
  c:=a+b;
  if c>2*a then
    Dec(c,b)
  else
    Dec(c,a);
  Result:=c;
end;

function TestCdecl(a,b:Integer):Integer ;cdecl;
var
  c:Integer;
begin
  c:=a+b;
  if c>2*a then
    Dec(c,b)
  else
    Dec(c,a);
  Result:=c;
end;

function TestRegister(a,b:Integer):Integer ;
var
  c:Integer;
begin
  c:=a+b;
  if c>2*a then
    Dec(C, B)
  Else
    Dec(C, A);
  Result:= C;
End;
{$R *.dfm}

Procedure TForm4.FormCreate(Sender: TObject);
Var
  C: Integer;
Begin
  C:= TestStdcall(5, 6);
  ShowMessage(IntToStr(C));
  C:= TestCdecl(5, 6);
  ShowMessage(IntToStr(C));
  C:= TestRegister(5, 6);
  ShowMessage(IntToStr(C));
End;

代码都一样,我们来看看执行时候有什么不一样的地方呢?
第一个TestStdcall:
image
我们看见先压6再压5,也就是从右到左压栈.函数内部:
image
先将栈里面的参数取出来放到ecx和edx中,最后清除栈Ret 8;
stdcall很明显了.

接下来看cdecl调用方式吧.
image
也是先压6再压5,但是我们看到最后面add esp,8,这个就是平衡栈了,因为我们压入了2个Integer,8个字节.
函数内部:
image
还是先从栈里面去参数到寄存器,最后直接ret咯.

接下来是Delphi默认的方式:
image
将5传入eax,6传入edx,它这里虽然现传的6,但是我们要注意寄存器的顺序应该是 eax,edx,ecx,如果还有多余的参数再压栈.
函数内部:
image
晕这个例子没有选好,因为参数没有用到栈,所以不存在栈平衡,我改一下...
image
从压栈的顺序我们再一次看出来了,是从左到右的,先压的8.
image
释放参数压栈用的空间,ret 8;

最后改一下,来看看Pascal调用方式,这个是Delphi1.0时候用的:
image
从左到右压栈的.
image
细心的朋友会发现这次没有讲栈里面东西取到寄存器中去,主要因为这次运算太简单了.哈哈,最后自己清除压的栈.

为什么要了解这些?
答案肯定是为了程序能够正常的工作了.这个问题...

如果声明和调用的方式不一样会怎么样?
1 走错路.如果只是从左到右和从右到左弄错了,程序能运行,只是参数a被当成参数b来用,也就是结果可能不对.
2 走上不归路.因为我们知道堆栈里面保存了函数的返回地址这些,但是如果我们调用方式不一样就可能造成堆栈被破坏了,程序无法正常返回,就会报错了.

ok,个人理解.

posted @ 2010-01-25 12:12  HuangJacky  阅读(1039)  评论(0编辑  收藏  举报
AdminLogin