• 标 题:关于Delphi中参数的传递和函数值的返回
  • 作 者:RoBa
  • 时 间:004-08-26,14:41
  • 链 接:http://bbs.pediy.com

关于Delphi中参数的传递和函数值的返回

前言:

高手们应该早知道了,不屑于写出来而已。真正的高手一个比一个潜的深,只剩下偶这样的小菜写些菜文给更小的菜。高手看时还请捂好大牙,多多指点。

不知各位小菜同胞对破解DELPHI程序有什么看法,反正我的感觉就一个字:怪。各位最先遇到的问题恐怕都是:我下了GetDlgItemInt、GetDlgItemText、GetWindowText....怎么什么也断不下来,甚至连Hmemcpy都不起作用?呵呵,从这里就能看出宝蓝的那批人成心想跟M$对着干,非搞出些新鲜的东东不可。

这回我们就来看看DLEPHI中对函数(过程)参数的传递是如何进行的。

我们知道WinAPI采用的调用约定是StdCall,也就是调用一个函数Func(arg1,agr2,agr3,arg4),你需要push arg4,push arg3,push arg2,push arg1,call Func 。在VC++里也是这种形式,所以一个函数有几个参数,可以非常直观地看出来。可是在DELPHI中就很奇怪了,在一个CALL前面你可能一个PUSH也看不到,怎么回事呢?听我慢慢道来。

DELPHI中的调用约定有StdCall,Cdecl,Safecall,Pascal和Register等几种方式,而DELPHI的默认方式是Register(为什么不是Pascal?)Register方式就是尽可能地使用寄存器来传递参数,减少堆栈的操作来提高速度。具体情况是怎样呢,看个例子先:

在FORM上放一个BUTTON,双击写代码如下:

代码:
function add1(a:Integer):Integer;    //一个参数 begin      add1:=a+a; end; function add2(a,b:Integer):Integer;    //两个参数 begin      add2:=a+b; end; function add3(a,b,c:Integer):Integer;    //三个参数 begin      add3:=a+b+c; end; function add4(a,b,c,d:Integer):Integer;    //四个参数 begin      add4:=a+b+c+d; end; function add5(a,b,c,d,e:Integer):Integer;  //五个参数 begin      add5:=a+b+c+d+e; end; function add6:Integer;        //加入一些局部变量 var local1,local2,local3,local4,local5:Integer; begin      local1:=1;      local2:=2;      local3:=3;      local4:=4;      local5:=5;      add6:=local1+local2+local3+local4+local5; end; function add7(a,b,c,d,e:Integer):Integer;  //利用result来返回 begin      result:=a+b+c+d+e; end; function add8(a,b,c,d,e:Integer):Integer;StdCall;//StdCall调用方式 begin      add8:=a+b+c+d+e; end; procedure TForm1.Button1Click(Sender: TObject); var a,b,c,d,e:Integer;     s1,s2,s3,s4,s5,s6,s7,s8,s:Integer; begin      a:=1; b:=2; c:=3; d:=4; e:=5;      s1:=add1(a);      s2:=add2(a,b);      s3:=add3(a,b,c);      s4:=add4(a,b,c,d);      s5:=add5(a,b,c,d,e);      s6:=add6;      s7:=add7(a,b,c,d,e);      s8:=add8(a,b,c,d,e);      s:=s1+s2+s3+s4+s5+s6+s7+s8;      //必须要有这么几句      MessageDlg(IntToStr(s),mtConfirmation,[mbOK],0);  //不然编译器根本不去处理返回值 end;

用DEDE反一下看看,这个Button1Click的内容:
代码:
004403EC   55                     push    ebp 004403ED   8BEC                   mov     ebp, esp 004403EF   83C4D8                 add     esp, -$28    ;空出地方放局部变量 004403F2   53                     push    ebx 004403F3   56                     push    esi 004403F4   57                     push    edi 004403F5   33C9                   xor     ecx, ecx 004403F7   894DD8                 mov     [ebp-$28], ecx 004403FA   33C0                   xor     eax, eax 004403FC   55                     push    ebp * Possible String Reference to: '関-?腽_^[嬪]? | 004403FD   68E9044400             push    $004404E9 ***** TRY | 00440402   64FF30                 push    dword ptr fs:[eax]  ;这是DELPHI的例行公事 00440405   648920                 mov     fs:[eax], esp    ;据我观察只要调用VCL库的都要SEH 00440408   BB01000000             mov     ebx, $00000001  ;a:=1 0044040D   BE02000000             mov     esi, $00000002  ;b:=2 00440412   BF03000000             mov     edi, $00000003  ;c:=3 00440417   C745FC04000000         mov     dword ptr [ebp-$04], $00000004  ;d:=4 0044041E   C745F805000000         mov     dword ptr [ebp-$08], $00000005  ;e:=5 可以看出DELPHI的确不一样,把EBX,ESI,EDI能用的寄存器全都用上了,实在不行了才用[ebp-xx], 从下面的分析中也能看出这一点,DELPHI在能用寄存器时决不用堆栈。 00440425   8BC3                   mov     eax, ebx    ;这是add1的参数啦,不用PUSH的 * Reference to : TForm1.Proc_00440360() | 00440427   E834FFFFFF             call    00440360    ;CALL add1 {   00440360   03C0                   add     eax, eax   00440362   C3                     ret      ;这样的确很快哟 } 0044042C   8945F4                 mov     [ebp-$0C], eax  ;s1:=add1(a) 0044042F   8BD6                   mov     edx, esi    ;add2的参数EDX=2 00440431   8BC3                   mov     eax, ebx    ;add2的参数EAX=1 * Reference to : TForm1.Proc_00440364() | 00440433   E82CFFFFFF             call    00440364    ;CALL add2 {   00440364   03D0                   add     edx, eax     00440366   8BC2                   mov     eax, edx   00440368   C3                     ret } 00440438   8945F0                 mov     [ebp-$10], eax  ;s2:=add2(a,b) 0044043B   8BCF                   mov     ecx, edi    ;add3的参数ECX=3 0044043D   8BD6                   mov     edx, esi    ;EDX=2 0044043F   8BC3                   mov     eax, ebx    ;EAX=1 * Reference to : TForm1.Proc_0044036C() | 00440441   E826FFFFFF             call    0044036C    ;CALL add3 {   0044036C   03D0                   add     edx, eax   0044036E   03CA                   add     ecx, edx   00440370   8BC1                   mov     eax, ecx   00440372   C3                     ret } 00440446   8945EC                 mov     [ebp-$14], eax  ;s3:=add3(a,b,c) 00440449   8B45FC                 mov     eax, [ebp-$04]  ;[EBP-4]=4 0044044C   50                     push    eax      ;终于看见PUSH了噢 0044044D   8BCF                   mov     ecx, edi    ;ECX=3 0044044F   8BD6                   mov     edx, esi    ;EDX=2 00440451   8BC3                   mov     eax, ebx    ;EAX=1 * Reference to : TForm1.Proc_00440374() | 00440453   E81CFFFFFF             call    00440374    ;CALL add4 {   00440374   55                     push    ebp   00440375   8BEC                   mov     ebp, esp  ;这是C里面的方式啦   00440377   03D0                   add     edx, eax   00440379   03CA                   add     ecx, edx   0044037B   034D08                 add     ecx, [ebp+$08];[EBP+8]本来是第一个参数的   0044037E   8BC1                   mov     eax, ecx  ;这里[EBP+8]是第四个参数   00440380   5D                     pop     ebp   00440381   C20400                 ret     $0004 } 00440458   8945E8                 mov     [ebp-$18], eax  ;s4:=add4(a,b,c,d) 0044045B   8B45FC                 mov     eax, [ebp-$04]  ;[EBP-4]=4 0044045E   50                     push    eax      ;注意:先压进去的是第四个参数 0044045F   8B45F8                 mov     eax, [ebp-$08]  ;[EBP-8]=5 00440462   50                     push    eax      ;再压进第五个参数,Pascal从左至右 00440463   8BCF                   mov     ecx, edi    ;ECX=3 00440465   8BD6                   mov     edx, esi    ;EDX=2 00440467   8BC3                   mov     eax, ebx    ;EAX=1 * Reference to : TForm1.Proc_00440384() | 00440469   E816FFFFFF             call    00440384    ;CALL add5(a,b,c,d,e) 0044046E   8945E4                 mov     [ebp-$1C], eax  ;s5=add5(a,b,c,d,e) * Reference to : TForm1.Proc_00440398() | 00440471   E822FFFFFF             call    00440398    ;add6 看看DLEPHI怎么处理局部变量 {   00440398   53                     push    ebx   00440399   56                     push    esi   0044039A   B801000000             mov     eax, $00000001   0044039F   BA02000000             mov     edx, $00000002   004403A4   B903000000             mov     ecx, $00000003   004403A9   BB04000000             mov     ebx, $00000004   004403AE   BE05000000             mov     esi, $00000005;哈哈,果然不出所料   004403B3   03D0                   add     edx, eax  ;它用上了一切能用的寄存器   004403B5   03CA                   add     ecx, edx  ;各位可以试试加上十来个局部变量   004403B7   03D9                   add     ebx, ecx  ;看它能坚持到几时   004403B9   03F3                   add     esi, ebx   004403BB   8BC6                   mov     eax, esi   004403BD   5E                     pop     esi   004403BE   5B                     pop     ebx   004403BF   C3                     ret } 00440476   8945E0                 mov     [ebp-$20], eax 00440479   8B45FC                 mov     eax, [ebp-$04] 0044047C   50                     push    eax 0044047D   8B45F8                 mov     eax, [ebp-$08] 00440480   50                     push    eax 00440481   8BCF                   mov     ecx, edi 00440483   8BD6                   mov     edx, esi 00440485   8BC3                   mov     eax, ebx * Reference to : TForm1.Proc_004403C0()        ;我想看看用result是不是有不同 | 00440487   E834FFFFFF             call    004403C0    ;其实和add5一样的,不写了 0044048C   8945DC                 mov     [ebp-$24], eax 0044048F   8B45F8                 mov     eax, [ebp-$08] 00440492   50                     push    eax      ;PUSH 5 00440493   8B45FC                 mov     eax, [ebp-$04] 00440496   50                     push    eax      ;PUSH 4 00440497   57                     push    edi      ;PUSH 3 00440498   56                     push    esi      ;PUSH 2 00440499   53                     push    ebx      ;PUSH 1 * Reference to : TForm1.Proc_004403D4() | 0044049A   E835FFFFFF             call    004403D4  ;这个眼熟的吧,从右至左的StdCall方式 {   004403D4   55                     push    ebp   004403D5   8BEC                   mov     ebp, esp   004403D7   8B4508                 mov     eax, [ebp+$08]   004403DA   03450C                 add     eax, [ebp+$0C]   004403DD   034510                 add     eax, [ebp+$10]   004403E0   034514                 add     eax, [ebp+$14]   004403E3   034518                 add     eax, [ebp+$18]   004403E6   5D                     pop     ebp   004403E7   C21400                 ret     $0014    ;我还是觉得这样好看一些 } * Reference to Form1 | 0044049F   8B5DF4                 mov     ebx, [ebp-$0C] 004404A2   035DF0                 add     ebx, [ebp-$10] 004404A5   035DEC                 add     ebx, [ebp-$14] 004404A8   035DE8                 add     ebx, [ebp-$18] 004404AB   035DE4                 add     ebx, [ebp-$1C] 004404AE   035DE0                 add     ebx, [ebp-$20] 004404B1   035DDC                 add     ebx, [ebp-$24] 004404B4   03D8                   add     ebx, eax    ;加起来

........下面的不写了,还值得一提的是在最后DELPHI总要弄出两个RET来,跳来跳去的,也算是DELPHI的特色吧。

上面讲的是自己定义的函数,要是用VCL库的东东,有时候更加莫名其妙一些。看例子:

建一个FORM,放一个BUTTON,一个EDIT,代码如下:
代码:
procedure TForm1.Button1Click(Sender: TObject); begin      MessageDlg(edit1.text,mtConfirmation,[mbOK],0); end;

呵呵太简单了是不是,用DEDE反下:(只写了关键部分)
代码:
004417BE   6A00                   push    $00    ;这是下面MessageDlg的第四个参数,找到没 004417C0   8D55FC                 lea     edx, [ebp-$04];??这是什么?? * Reference to control TForm1.Edit1 : TEdit | 004417C3   8B83C8020000           mov     eax, [ebx+$02C8]  ;这是下面GetText的参数TControl吧                 ;看上面的Reference * Reference to: controls.TControl.GetText(TControl):TCaption; | 004417C9   E8D619FEFF             call    004231A4  ;得到EDIT的文本 004417CE   8B45FC                 mov     eax, [ebp-$04];这是参数一要显示的字串放入EAX 004417D1   668B0D00184400         mov     cx, word ptr [$00441800];这应该是参数二mtConfirmation 004417D8   B203                   mov     dl, $03  ;这是参数三[mbOK] * Reference to: Dialogs.Proc_00441380 | 004417DA   E8A1FBFFFF             call    00441380  ;这个是MessageDlg 004417DF   33C0                   xor     eax, eax

如果按照上面的分析,看到GetText这里应该只有一个参数就是放入EAX的那个[ebx+02c8],从参考也可以看到这就是EDIT1,可是函数的返回值呢?刚执行完这个CALL后EAX中是没有的,mov eax,[ebp-04]后才出现了,返回值原来在[ebp-4]中。再向上找有一个莫名其妙的lea edx,[ebp-04],按照我上面的分析这应该表示GetText的第二个参数。可是GetText只有一个参数呀。
这种需要返回一个比较大的结构的函数,在VC中常用的方法是把一个指针当参数传递过去,而DLEPHI中我猜是不是做成一个隐藏的参数,像上面的GetText表面上看是返回一个TCaption,实际这个并不是放在EAX里返回来的。

总结一下:DELPHI对参数的传递是尽可能多地利用寄存器,一般第一个参数用EAX,第二个参数用EDX,第三个参数用ECX,多于三个参数的时候,对多出来的参数按照从左至右的PASCAL方式来压栈。
对于函数的返回值,有时尽管声明中说它的返回值是TCaption之类等,实际上并没有在EAX中返回,而是在保存一个隐藏的参数中,等需要时再复制过来。(这一点是猜想而已,如果哪位高人知道的话还请指点。反正我以前都是糊里糊涂地跟,结果出来就算了。其实仔细一分析还有点意思。)