2008-11-18 29 views
16

通常,在Delphi一个将声明一个函数的方法,使用“常量的数组”参数个数可变。然而,对于C语言编写的代码的兼容性,还有可以添加到函数声明中的很多未知的“可变参数”指令(我认识到这一边读鲁迪的优秀“Pitfalls of convering”文件)。带有'varargs'的函数如何检索堆栈的内容?

举个例子,一个人可能在C函数,声明如下:

void printf(const char *fmt, ...) 

在Delphi中,这将成为:

procedure printf(const fmt: PChar); varargs; 

我的问题是:我要如何才能到达当实现一个用'varargs'指令定义的方法时,堆栈的内容是什么?我希望为此存在一些工具,比如va_start(),va_arg()和va_end()函数的Dephi翻译,但我无法在任何地方找到它。

请帮忙! PS:请不要在讨论'为什么'或'const'数组的讨论中出现 - 我需要这个在Xbox游戏中为函数编写类C补丁(请参阅Delphi Xbox模拟器项目' Dxbx'在sourceforge上查看详情)。

回答

18

OK,我看到了澄清你的问题意味着你需要实现在Delphi一个C导入。在这种情况下,你需要自己实现可变参数。

所需的基本知识是x86上的C调用约定:堆栈向下增长,C将参数从右向左推进。因此,一个指向最后声明的参数的指针,在它最后声明的参数的大小增加后,将指向尾参数列表。从那时起,只需要读取参数并将指针递增一个合适的大小以便更深入地进入堆栈。 32位模式下的x86堆栈通常是4字节对齐的,这也意味着字节和字是以32位整数的形式传递的。

总之,这里是一个演示程序,说明如何读出的数据的辅助记录。请注意,Delphi似乎以非常奇怪的方式传递扩展类型;但是,您可能不必担心这一点,因为10字节的浮点数通常在C中不被广泛使用,甚至在最新的MS C,IIRC中都没有实现。

{$apptype console} 

type 
    TArgPtr = record 
    private 
    FArgPtr: PByte; 
    class function Align(Ptr: Pointer; Align: Integer): Pointer; static; 
    public 
    constructor Create(LastArg: Pointer; Size: Integer); 
    // Read bytes, signed words etc. using Int32 
    // Make an unsigned version if necessary. 
    function ReadInt32: Integer; 
    // Exact floating-point semantics depend on C compiler. 
    // Delphi compiler passes Extended as 10-byte float; most C 
    // compilers pass all floating-point values as 8-byte floats. 
    function ReadDouble: Double; 
    function ReadExtended: Extended; 
    function ReadPChar: PChar; 
    procedure ReadArg(var Arg; Size: Integer); 
    end; 

constructor TArgPtr.Create(LastArg: Pointer; Size: Integer); 
begin 
    FArgPtr := LastArg; 
    // 32-bit x86 stack is generally 4-byte aligned 
    FArgPtr := Align(FArgPtr + Size, 4); 
end; 

class function TArgPtr.Align(Ptr: Pointer; Align: Integer): Pointer; 
begin 
    Integer(Result) := (Integer(Ptr) + Align - 1) and not (Align - 1); 
end; 

function TArgPtr.ReadInt32: Integer; 
begin 
    ReadArg(Result, SizeOf(Integer)); 
end; 

function TArgPtr.ReadDouble: Double; 
begin 
    ReadArg(Result, SizeOf(Double)); 
end; 

function TArgPtr.ReadExtended: Extended; 
begin 
    ReadArg(Result, SizeOf(Extended)); 
end; 

function TArgPtr.ReadPChar: PChar; 
begin 
    ReadArg(Result, SizeOf(PChar)); 
end; 

procedure TArgPtr.ReadArg(var Arg; Size: Integer); 
begin 
    Move(FArgPtr^, Arg, Size); 
    FArgPtr := Align(FArgPtr + Size, 4); 
end; 

procedure Dump(const types: string); cdecl; 
var 
    ap: TArgPtr; 
    cp: PChar; 
begin 
    cp := PChar(types); 
    ap := TArgPtr.Create(@types, SizeOf(string)); 
    while True do 
    begin 
    case cp^ of 
     #0: 
     begin 
     Writeln; 
     Exit; 
     end; 

     'i': Write(ap.ReadInt32, ' '); 
     'd': Write(ap.ReadDouble, ' '); 
     'e': Write(ap.ReadExtended, ' '); 
     's': Write(ap.ReadPChar, ' '); 
    else 
     Writeln('Unknown format'); 
     Exit; 
    end; 
    Inc(cp); 
    end; 
end; 

type 
    PDump = procedure(const types: string) cdecl varargs; 
var 
    MyDump: PDump; 

function AsDouble(e: Extended): Double; 
begin 
    Result := e; 
end; 

function AsSingle(e: Extended): Single; 
begin 
    Result := e; 
end; 

procedure Go; 
begin 
    MyDump := @Dump; 

    MyDump('iii', 10, 20, 30); 
    MyDump('sss', 'foo', 'bar', 'baz'); 

    // Looks like Delphi passes Extended in byte-aligned 
    // stack offset, very strange; thus this doesn't work. 
    MyDump('e', 2.0); 
    // These two are more reliable. 
    MyDump('d', AsDouble(2)); 
    // Singles passed as 8-byte floats. 
    MyDump('d', AsSingle(2)); 
end; 

begin 
    Go; 
end. 
2

我发现this(从guy我们知道:))

为了写这个东西正常,你将需要使用简易工况法,德尔福的内置 汇编和恶意代码的调用在ASM序列。希望你有一个很好的想法,你需要做什么。如果 卡住了,那么.basm组中的某个帖子可能会有所帮助。

1

德尔福不会让你实现一个可变参数程序。它只适用于导入使用它的外部cdecl函数。

由于可变参数是基于cdecl调用约定,你基本上需要自己重新实现它在Delphi中,使用装配和/或各种指针操作的。

+0

不,如果调用者传递零作为最后一个参数,则参数列表仅为零终止。您引用的页面如此说。 printf函数不需要有一个零来终止列表,因为它可以根据格式字符串计算出有多少个参数。 – 2008-11-18 15:14:18

+0

那么,我的错误。我会相应地编辑。 – 2008-11-18 21:03:16