2008-11-18 31 views
16

Thông thường, trong Delphi ta sẽ khai báo một hàm với một số biến số các đối số bằng cách sử dụng phương thức 'mảng của const'. Tuy nhiên, để tương thích với mã được viết bằng C, có một chỉ thị 'varargs' không biết nhiều có thể được thêm vào một khai báo hàm (tôi đã học được điều này trong khi đọc tài liệu 'Pitfalls of convering' tuyệt vời của Rudy).Làm cách nào để một hàm có 'varargs' truy xuất nội dung của ngăn xếp?

Ví dụ, người ta có thể có một hàm trong C, đã tuyên bố như thế này:

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

Trong Delphi, điều này sẽ trở thành:

procedure printf(const fmt: PChar); varargs; 

Câu hỏi của tôi là: Làm thế nào tôi có thể nhận được nội dung của ngăn xếp khi triển khai phương thức được xác định bằng chỉ thị 'varargs'? Tôi có thể mong đợi rằng một số công cụ cho điều này tồn tại, như Dephi dịch các hàm va_start(), va_arg() và va_end(), nhưng tôi không thể tìm thấy nó ở bất cứ đâu.

Vui lòng trợ giúp!

PS: Vui lòng không bỏ qua trong các cuộc thảo luận về 'lý do' hoặc 'mảng const' thay thế - tôi cần điều này để viết các bản vá lỗi giống như C cho các chức năng bên trong trò chơi Xbox (xem dự án giả lập Delphi Xbox ' Dxbx 'trên sourceforge để biết chi tiết).

Trả lời

18

OK, tôi thấy làm rõ trong câu hỏi của bạn có nghĩa là bạn cần phải thực hiện nhập C trong Delphi. Trong trường hợp đó, bạn cần tự triển khai vararg.

Kiến thức cơ bản cần thiết là quy ước gọi C trên x86: ngăn xếp phát triển xuống dưới và C đẩy đối số từ phải sang trái. Vì vậy, một con trỏ đến đối số được khai báo cuối cùng, sau khi nó được tăng lên bởi kích thước của đối số được khai báo cuối cùng, sẽ trỏ đến danh sách đối số đuôi. Từ đó, nó chỉ đơn giản là vấn đề đọc đối số và tăng con trỏ bằng kích thước thích hợp để di chuyển sâu hơn vào ngăn xếp. Các x86 ngăn xếp trong chế độ 32-bit là 4-byte liên kết nói chung, và điều này cũng có nghĩa là byte và các từ được thông qua như số nguyên 32-bit.

Dù sao đi nữa, đây là bản ghi trợ giúp trong chương trình demo cho biết cách đọc dữ liệu. Lưu ý rằng Delphi dường như đang chuyển các kiểu mở rộng theo một cách rất kỳ lạ; Tuy nhiên, bạn có thể sẽ không phải lo lắng về điều đó, vì float 10-byte thường không được sử dụng rộng rãi trong C, và thậm chí không được triển khai trong MS C, IIRC mới nhất.

{$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. 
+1

Điều này có vẻ tuyệt vời! Tôi đã ngạc nhiên khi thấy có thực sự không cần phải sử dụng lắp ráp để có được nội dung đăng ký ESP. Cảm ơn vì điều này - ví dụ tuyệt vời quá! – PatrickvL

+1

Lưu ý rằng mã cần thích ứng nếu nó hoạt động trên x64 - hàm Align đặc biệt cắt bớt các con trỏ đến các giá trị 32 bit. –

2

tôi thấy this (từ một guy chúng tôi biết :))

Để viết công cụ này đúng cách, bạn sẽ cần phải sử dụng BASM, Delphi được xây dựng trong lắp ráp, và mã trình tự gọi trong asm. Hy vọng rằng bạn đã có một ý tưởng tốt về về những gì bạn cần làm. Có lẽ một bài đăng trong nhóm .basm sẽ giúp đỡ nếu bạn gặp khó khăn.

1

Delphi không cho phép bạn triển khai thường trình biến đổi. Nó chỉ hoạt động để nhập các hàm cdecl bên ngoài sử dụng chức năng này.

Vì varargs dựa trên quy ước gọi cdecl, về cơ bản bạn cần tự mình thực hiện lại nó trong Delphi, sử dụng assembly và/hoặc các loại thao tác con trỏ khác nhau.

+0

Không, danh sách các đối số chỉ được kết thúc bằng 0 nếu người gọi vượt không bằng đối số cuối cùng. Trang bạn trích dẫn nói như vậy. Hàm printf không cần có số 0 để kết thúc danh sách vì nó có thể tìm ra số lượng đối số dựa trên chuỗi định dạng. –

+0

Ngay sau đó, sai lầm của tôi. Tôi sẽ chỉnh sửa cho phù hợp. –

Các vấn đề liên quan