前面从网络中收藏加转贴了一部分关于变长参数函数的笔记。反正休闲没事,于是就专门的研究研究了如何用Delphi来实现类似于C语言中Printf这种可以传递任意多个参数的函数。这个实际上来说也不太难,只要俺们都熟悉函数的调用规则,那么就很容易了。实际上这个变长,总体上来说,应该是有迹可循的。那这个迹象在哪里,就是关键点了,也就是说,最主要的是要知道,这个函数到底传递了多少个参数。我们参看C的,printf和Windows的wsPrintf,都知道,实际上他里面都有一个Format的参数类型。有这个类型,就可以根据这个格式参数,来获取里面有多少个%d,%s,%f这样的匹配内容了,于是通过这个,我们就可以知道函数到底传递了多少个参数了。继而,俺们就可以从程序堆栈当中把这些参数一个个的给找出来,然后在俺们的函数中加以处理。那么这时候肯定就有人要问了,为什么参数是在堆栈中。。。。这个。。还是归于函数调用法则来说,最初制定的时候,有规定stdcall,pascal,以及Delphi默认的Register调用都是由程序自动管理堆栈,除此之外,还有一种调用方式就是cdecl这个方式,是由我们自己来平衡堆栈,自己管理的,而动态个数参数的函数,也就必须是这个cdecl的调用方式,这种调用方式,参数是按照从右向左的方式压入堆栈的,这样设计是有道理的,从右往左边压入,那么越早传入的参数就越晚被压入,那么我们就能够知道最有用的实际参数,比如此时如果进入到变长函数,那么我们就可以知道,当前堆栈的栈顶就是我们的现场,那么再网上递归,就是第一个参数了,也就是说[ebp+8]就是当前的第一个参数了。由此可以找到第二个参数,第三个参数。。。。。。
所以,我们一定要在最初的参数中获取后面到底实际传递了多少个变体参数。然后才可以通过堆栈,一个个的找到,进而以函数来处理。
说了这么多来举例实验一下。比如,写一个函数,需要获得传入的数字的和
function GetSum(NumCount: integer{N1,N2: integer}): Integer;cdecl; asm { push ebp mov ebp,esp } mov ecx,[ebp + 8];//先获取第一个参数值,判断传递了几个参数 //然后获取每个参数 mov edx,12 mov eax,0 @@add: add eax,[ebp].edx add edx,4 loop @@add end;
然后调用方式:
type VA_Sum = function(NumCount: integer{N1,N2}): Integer; cdecl varargs; var m: VA_Sum; begin m := GetSum; ShowMessage(inttostr(m(4,3,4,5,6))); end;
在比如,连接字符串函数
function ConStr(NumCount: integer{N1,N2}): string;cdecl; var st1: string; i: Integer; begin asm mov esi,16 end; for i := 0 to NumCount - 1 do begin asm mov edi,[ebp].esi cmp edi,128 jb @@char mov DWORD ptr st1,edi @@Char: add esi,4 end; Result := Result + st1; end; end;
调用方法
type VA_ConStr = function(NumCount: integer{N1,N2}): string; cdecl varargs; var cstr: VA_ConStr; begin cstr := constr; ShowMessage(cstr(3,'Test','不得闲','测试')); end;