2010-02-21 28 views
8

Xin vui lòng trợ giúp! Tôi cần chuyển đổi này để viết wrapper cho một số tiêu đề C cho Delphi.Delphi "mảng const" thành "varargs"

Như một ví dụ:

function pushfstring(fmt: PAnsiChar): PAnsiChar; cdecl; varargs; external; 

... 

function PushString(fmt: AnsiString; const args: array of const): AnsiString; 
begin 
    Result := AnsiString(pushfstring(PAnsiString(fmt), args)); // it's incorrect :/ 
end; 

Làm thế nào tôi có thể chuyển đổi "mảng const" thành "varargs"?

chỉnh sửa: chức năng PushString thực sự nằm trong bản ghi (tôi đã đưa ra ví dụ đơn giản) và tôi không có quyền truy cập trực tiếp vào pushfstring. Cuộc gọi trực tiếp bị loại trừ.

chỉnh sửa 2: Tôi viết đơn vị thư viện LUA cho Delphi và trường hợp này khá quan trọng đối với tôi.

Xác định và cung cấp tất cả các chi tiết của vấn đề - Tôi có chức năng này trong C:

LUA_API const char *(lua_pushfstring) (lua_State *L, const char *fmt, ...);

Trong Delphi Tôi có một cái gì đó như thế này:

LuaLibrary.pas

{...} 
interface 
{...} 
function lua_pushfstring(L: lua_State; fmt: PAnsiChar): PAnsiChar; cdecl; varargs; 
implementation 
{...} 
function lua_pushfstring; external 'lua.dll'; // or from OMF *.obj file by $L

dtxLua.pas

uses LuaLibrary; 
{...} 
type 
    TLuaState = packed record 
    private 
    FLuaState: lua_State; 
    public 
    class operator Implicit(A: TLuaState): lua_State; inline; 
    class operator Implicit(A: lua_State): TLuaState; inline; 
    {...} 
    // btw. PushFString can't be inline function 
    function PushFString(fmt: PAnsiChar; const args: array of const): PAnsiChar; 
    //... and a lot of 'wrapper functions' for functions like a lua_pushfstring, 
    // where L: lua_State; is the first parameter 
    end; 
implementation 
{...} 
function TLuaState.PushFString(fmt: PAnsiChar; const args: array of const) 
    : PAnsiChar; 
begin 
    Result := lua_pushfstring(FLuaState, fmt, args); // it's incorrect :/ 
end;

và trong các đơn vị khác như Lua.pas tôi chỉ sử dụng TLuaState từ dtxLua.pas (vì LuaLibrary là cồng kềnh, dtxLua là wrapper của tôi), đối với nhiều điều bổ ích và mát mẻ ...

+0

Chức năng 'pushfstring' bạn đang cố gọi là chức năng bên ngoài. Không thể "không có quyền truy cập trực tiếp" vào nó bởi vì bạn có thể khai báo nó ở bất cứ đâu bạn muốn. Mặc dù tôi đánh giá cao mong muốn của bạn gọi hàm varargs với số tham số không xác định, bạn không thực sự cần nó trong trường hợp của bạn vì bạn * có thể * gọi trực tiếp 'pushfstring' từ bất cứ nơi nào bạn đã gọi là' PushString'. –

+0

@Rob - Tôi nghi ngờ anh ta có một con trỏ hàm. –

+0

Nguyên mẫu C cho 'pushfstring' là gì? –

Trả lời

12

Tôi đoán rằng nguyên mẫu cho pushfstring hơi như thế này:

void pushfstring(const char *fmt, va_list args); 

Nếu không phải tên' t và thay vào đó là:

void pushfstring(const char *fmt, ...); 

... thì tôi cũng nên đề cập đến bạn.

Trong C, nếu bạn phải vượt qua trên một cuộc gọi từ một chức năng variadic khác, bạn nên sử dụng va_list, va_startva_end, và gọi phiên bản v của hàm. Vì vậy, nếu bạn tự mình thực hiện printf, bạn có thể sử dụng vsprintf để định dạng chuỗi - bạn không thể gọi trực tiếp sprintf và chuyển qua danh sách đối số variadic. Bạn cần sử dụng va_list và bạn bè.

Thật khó xử lý khi xử lý C's va_list từ Delphi, và về mặt kỹ thuật không nên thực hiện - việc triển khai va_list dành riêng cho thời gian chạy của nhà cung cấp trình biên dịch C.

Tuy nhiên, chúng tôi có thể thử. Giả sử chúng ta có một lớp học nhỏ - mặc dù tôi đã làm cho nó một kỷ lục để dễ sử dụng:

type 
    TVarArgCaller = record 
    private 
    FStack: array of Byte; 
    FTop: PByte; 
    procedure LazyInit; 
    procedure PushData(Loc: Pointer; Size: Integer); 
    public 
    procedure PushArg(Value: Pointer); overload; 
    procedure PushArg(Value: Integer); overload; 
    procedure PushArg(Value: Double); overload; 
    procedure PushArgList; 
    function Invoke(CodeAddress: Pointer): Pointer; 
    end; 

procedure TVarArgCaller.LazyInit; 
begin 
    if FStack = nil then 
    begin 
    // Warning: assuming that the target of our call doesn't 
    // use more than 8K stack 
    SetLength(FStack, 8192); 
    FTop := @FStack[Length(FStack)]; 
    end; 
end; 

procedure TVarArgCaller.PushData(Loc: Pointer; Size: Integer); 
    function AlignUp(Value: Integer): Integer; 
    begin 
    Result := (Value + 3) and not 3; 
    end; 
begin 
    LazyInit; 
    // actually you want more headroom than this 
    Assert(FTop - Size >= PByte(@FStack[0])); 
    Dec(FTop, AlignUp(Size)); 
    FillChar(FTop^, AlignUp(Size), 0); 
    Move(Loc^, FTop^, Size); 
end; 

procedure TVarArgCaller.PushArg(Value: Pointer); 
begin 
    PushData(@Value, SizeOf(Value)); 
end; 

procedure TVarArgCaller.PushArg(Value: Integer); 
begin 
    PushData(@Value, SizeOf(Value)); 
end; 

procedure TVarArgCaller.PushArg(Value: Double); 
begin 
    PushData(@Value, SizeOf(Value)); 
end; 

procedure TVarArgCaller.PushArgList; 
var 
    currTop: PByte; 
begin 
    currTop := FTop; 
    PushArg(currTop); 
end; 

function TVarArgCaller.Invoke(CodeAddress: Pointer): Pointer; 
asm 
    PUSH EBP 
    MOV EBP,ESP 

    // Going to do something unpleasant now - swap stack out 
    MOV ESP, EAX.TVarArgCaller.FTop 
    CALL CodeAddress 
    // return value is in EAX 
    MOV ESP,EBP 

    POP EBP 
end; 

Sử dụng hồ sơ này, chúng tôi có thể tự xây dựng khung gọi dự kiến ​​cho các cuộc gọi C khác nhau. Quy ước gọi điện của C trên x86 là chuyển đối số từ phải sang trái trên ngăn xếp, với người gọi dọn dẹp. Dưới đây là bộ xương của một C generic gọi thói quen:

function CallManually(Code: Pointer; const Args: array of const): Pointer; 
var 
    i: Integer; 
    caller: TVarArgCaller; 
begin 
    for i := High(Args) downto Low(Args) do 
    begin 
    case Args[i].VType of 
     vtInteger: caller.PushArg(Args[i].VInteger); 
     vtPChar: caller.PushArg(Args[i].VPChar); 
     vtExtended: caller.PushArg(Args[i].VExtended^); 
     vtAnsiString: caller.PushArg(PAnsiChar(Args[i].VAnsiString)); 
     vtWideString: caller.PushArg(PWideChar(Args[i].VWideString)); 
     vtUnicodeString: caller.PushArg(PWideChar(Args[i].VUnicodeString)); 
     // fill as needed 
    else 
     raise Exception.Create('Unknown type'); 
    end; 
    end; 
    Result := caller.Invoke(Code); 
end; 

Lấy printf làm ví dụ:

function printf(fmt: PAnsiChar): Integer; cdecl; varargs; 
    external 'msvcrt.dll' name 'printf'; 

const 
    // necessary as 4.123 is Extended, and %g expects Double 
    C: Double = 4.123; 
begin 
    // the old-fashioned way 
    printf('test of printf %s %d %.4g'#10, PAnsiChar('hello'), 42, C); 
    // the hard way 
    CallManually(@printf, [AnsiString('test of printf %s %d %.4g'#10), 
         PAnsiChar('hello'), 42, C]); 
end. 

Calling phiên bản va_list là hơi tham gia nhiều hơn, như vị trí các va_list lập luận của cần phải được đặt cẩn thận nơi dự kiến:

function CallManually2(Code: Pointer; Fmt: AnsiString; 
    const Args: array of const): Pointer; 
var 
    i: Integer; 
    caller: TVarArgCaller; 
begin 
    for i := High(Args) downto Low(Args) do 
    begin 
    case Args[i].VType of 
     vtInteger: caller.PushArg(Args[i].VInteger); 
     vtPChar: caller.PushArg(Args[i].VPChar); 
     vtExtended: caller.PushArg(Args[i].VExtended^); 
     vtAnsiString: caller.PushArg(PAnsiChar(Args[i].VAnsiString)); 
     vtWideString: caller.PushArg(PWideChar(Args[i].VWideString)); 
     vtUnicodeString: caller.PushArg(PWideChar(Args[i].VUnicodeString)); 
    else 
     raise Exception.Create('Unknown type'); // etc. 
    end; 
    end; 
    caller.PushArgList; 
    caller.PushArg(PAnsiChar(Fmt)); 
    Result := caller.Invoke(Code); 
end; 

function vprintf(fmt: PAnsiChar; va_list: Pointer): Integer; cdecl; 
    external 'msvcrt.dll' name 'vprintf'; 

begin 
    // the hard way, va_list 
    CallManually2(@vprintf, 'test of printf %s %d %.4g'#10, 
     [PAnsiChar('hello'), 42, C]); 
end. 

Ghi chú:

  • Điều trên mong đợi x86 trên Windows. Microsoft C, bcc32 (Embarcadero C++) và gcc tất cả vượt qua va_list theo cùng một cách (một con trỏ đến đối số variadic đầu tiên trên ngăn xếp), theo các thử nghiệm của tôi, vì vậy nó sẽ làm việc cho bạn; nhưng ngay sau khi giả định x86 trên Windows bị hỏng, mong đợi điều này có thể sẽ bị phá vỡ.

  • Ngăn xếp được đổi chỗ để dễ dàng với việc xây dựng. Điều này có thể tránh được với nhiều công việc hơn, nhưng việc vượt qua va_list cũng trở nên phức tạp hơn, vì nó cần phải chỉ ra các đối số như thể chúng được truyền trên stack. Kết quả là, mã cần tạo một giả định về mức độ sử dụng thường xuyên của stack; ví dụ này giả định 8K, nhưng điều này có thể quá nhỏ. Tăng nếu cần.

+0

Có thể cải thiện mã bằng cách đẩy "ngăn xếp mảng" vào ngăn xếp thực trước lệnh gọi? – arthurprs

+0

Barry - Tôn trọng. Đó là những gì tôi cần. – HNB

+0

@arthurprs - như tôi đã đề cập, tôi xây dựng mọi thứ trong mảng và sau đó đặt nó vào làm ngăn xếp để làm mọi thứ dễ dàng, dễ hiểu và linh hoạt. Rất khó để trừu tượng hóa các chi tiết về quản lý ngăn xếp khi bạn đang sử dụng ngăn xếp thực sự. Sao chép trong ngăn xếp cũng có thể được thực hiện. Tôi để nó như một bài tập cho người đọc ... :) –

2

An "mảng của const "thực sự là một mảng của TVarRec, là một loại biến thể đặc biệt. Nó không tương thích với varargs, và bạn thực sự có thể gọi hàm varargs trực tiếp mà không cần một trình bao bọc xung quanh nó.

+0

chức năng PushString thực sự là bên trong hồ sơ (tôi đã cho một ví dụ đơn giản) và tôi không có quyền truy cập trực tiếp vào pushfstring. Cuộc gọi trực tiếp bị loại trừ. – HNB

4

Các wrapper bạn đang cố gắng để viết có thể có trong Free Pascal, vì Free Pascal hỗ trợ 2 tờ khai equvalent cho varargs chức năng bên ngoài:

http://www.freepascal.org/docs-html/ref/refsu68.html

nên thay vì

function pushfstring(fmt: PAnsiChar): PAnsiChar; cdecl; varargs; external; 

bạn nên viết

function pushfstring(fmt: PAnsiChar; Args: Array of const): PAnsiChar; cdecl; external; 

Cập nhật: Tôi đã cố gắng lừa tương tự ở Delphi, nhưng nó không hoạt động:

//function sprintf(S, fmt: PAnsiChar; const args: array of const): Integer; 
//   cdecl; external 'MSVCRT.DLL'; 

function sprintf(S, fmt: PAnsiChar): Integer; 
      cdecl; varargs; external 'MSVCRT.DLL'; 

procedure TForm1.Button1Click(Sender: TObject); 
var 
    S, fmt: Ansistring; 

begin 
    SetLength(S, 99); 
    fmt:= '%d - %d'; 
// sprintf(PAnsiChar(S), PAnsiChar(fmt), [1, 2]); 
    sprintf(PAnsiChar(S), PAnsiChar(fmt), 1, 2); 
    ShowMessage(S); 
end; 
+0

Cảm ơn thông tin này, rất vui được biết. – HNB

1

Barry Kelly đã truyền cảm hứng cho tôi tìm kiếm giải pháp mà không cần thay thế ngăn xếp ... Đây là giải pháp (có thể cũng sử dụng Invoke từ đơn vị rtti, thay vì RealCall_CDecl).

// This function is copied from PascalScript 
function RealCall_CDecl(p: Pointer; 
    StackData: Pointer; 
    StackDataLen: Longint; // stack length are in 4 bytes. (so 1 = 4 bytes) 
    ResultLength: Longint; ResEDX: Pointer): Longint; Stdcall; 
    // make sure all things are on stack 
var 
    r: Longint; 
begin 
    asm 
    mov ecx, stackdatalen 
    jecxz @@2 
    mov eax, stackdata 
    @@1: 
    mov edx, [eax] 
    push edx 
    sub eax, 4 
    dec ecx 
    or ecx, ecx 
    jnz @@1 
    @@2: 
    call p 
    mov ecx, resultlength 
    cmp ecx, 0 
    je @@5 
    cmp ecx, 1 
    je @@3 
    cmp ecx, 2 
    je @@4 
    mov r, eax 
    jmp @@5 
    @@3: 
    xor ecx, ecx 
    mov cl, al 
    mov r, ecx 
    jmp @@5 
    @@4: 
    xor ecx, ecx 
    mov cx, ax 
    mov r, ecx 
    @@5: 
    mov ecx, stackdatalen 
    jecxz @@7 
    @@6: 
    pop eax 
    dec ecx 
    or ecx, ecx 
    jnz @@6 
    mov ecx, resedx 
    jecxz @@7 
    mov [ecx], edx 
    @@7: 
    end; 
    Result := r; 
end; 

// personally created function :) 
function CallManually3(Code: Pointer; const Args: array of const): Pointer; 
var 
    i: Integer; 
    tmp: AnsiString; 
    data: AnsiString; 
begin 
    for i := Low(Args) to High(Args) do 
    begin 
    case Args[i].VType of 
     vtInteger, vtPChar, vtAnsiString, vtWideString, vtUnicodeString: begin 
      tmp := #0#0#0#0; 
      Pointer((@tmp[1])^) := TVarRec(Args[i]).VPointer; 
     end; 
     vtExtended: begin 
      tmp := #0#0#0#0#0#0#0#0; 
      Double((@tmp[1])^) := TVarRec(Args[i]).VExtended^; 
     end; 
     // fill as needed 
    else 
     raise Exception.Create('Unknown type'); 
    end; 

    data := data + tmp; 
    end; 

    Result := pointer(RealCall_CDecl(Code, @data[Length(data) - 3], 
    Length(data) div 4, 4, nil)); 
end; 

function printf(fmt: PAnsiChar): Integer; cdecl; varargs; 
    external 'msvcrt.dll' name 'printf'; 

begin 
    CallManually3(@printf, 
    [AnsiString('test of printf %s %d %.4g'#10), 
     PAnsiChar('hello'), 42, 4.123]); 
end.
Các vấn đề liên quan