2010-02-21 12 views
8

Bitte helfen Sie! Ich brauche diese Konvertierung, um Wrapper für einige C-Header für Delphi zu schreiben.Delphi "Array von Const" zu "Varargs"

Als Beispiel:

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; 

Wie kann ich "Array von const" auf "varargs" konvertieren?

bearbeiten: Funktion PushString befindet sich tatsächlich im Datensatz (ich gab ein vereinfachtes Beispiel), und ich habe keinen direkten Zugriff auf PushString. Direktruf ist ausgeschlossen.

bearbeiten 2: Ich schreibe die Einheiten für LUA-Bibliothek für Delphi und der Fall ist ziemlich wichtig für mich.

angeben und alle Details der Angelegenheit Bereitstellung - Ich habe diese Funktion in C:

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

In Delphi Ich habe so etwas wie dies:

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;

und in anderen Einheiten wie Lua.pas i nur TLuaState von dtxLua.pas verwenden (da LuaLibrary sperrig ist, ist dtxLua meine Wrapper), für viele nützliche und coole Sachen ...

+0

Die Funktion 'pushfstring', die Sie aufrufen möchten, ist eine externe Funktion. Es ist unmöglich, "keinen direkten Zugriff darauf" zu haben, weil Sie eine Deklaration dafür machen können, wo Sie wollen. Obwohl ich Ihren Wunsch, eine varargs-Funktion mit einer unbekannten Anzahl von Parametern aufzurufen, zu schätzen weiß, brauchen Sie das in Ihrem Fall eigentlich nicht, weil Sie 'pushfstring' direkt von dort aus aufrufen können, wo Sie' PushString' genannt hätten. –

+0

@Rob - Ich vermute, er hat einen Funktionszeiger. –

+0

Was ist der C-Prototyp für 'Pushfstring'? –

Antwort

12

Ich vermute, dass der Prototyp für pushfstring etwas wie folgt aus:

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

Wenn es isn‘ t, und ist stattdessen:

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

... dann sollte ich Sie auch abgedeckt haben.

In C, wenn Sie auf einen Anruf von einer variadische Funktion zum anderen weitergeben, sollten Sie va_list, va_start und va_end, und rufen Sie die v Version der Funktion verwenden. Wenn Sie also printf selbst implementieren, können Sie vsprintf verwenden, um die Zeichenfolge zu formatieren - Sie können sprintf nicht direkt aufrufen und die Variadic-Argumentliste weiterleiten. Sie müssen va_list und Freunde verwenden.

Es ist ziemlich umständlich C die va_list von Delphi, zu handhaben und technisch es nicht getan werden soll - die Durchführung von va_list zu dem C-Compiler Hersteller Laufzeit spezifisch ist.

Allerdings können wir es versuchen. Angenommen, wir ein wenig Klasse haben - obwohl ich es ein Rekord für Benutzerfreundlichkeit gemacht:

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; 

diesen Datensatz verwenden, können wir manuell den Aufrufrahmen konstruieren für verschiedene C Anrufe erwartet. Die Aufrufkonvention von c auf x86 besteht darin, Argumente von rechts nach links auf dem Stapel zu übergeben, wobei der Aufrufer aufräumt. Hier ist das Skelett einer generischen C-Aufrufroutine:

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; 

Unter printf als Beispiel:

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. 

aufrufen va_list Version ist etwas mehr beteiligt, als die va_list Argument der Stelle platziert werden muss sorgfältig wo es wird erwartet:

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. 

Hinweise:

  • Das obige erwartet x86 unter Windows. Microsoft C, bcc32 (Embarcadero C++) und gcc alle übergeben va_list in der gleichen Weise (ein Zeiger auf das erste variadic Argument auf dem Stapel), nach meinen Experimenten, so sollte es für Sie arbeiten; aber sobald die x86 unter Windows-Annahme kaputt ist, erwarte, dass dies möglicherweise auch bricht.

  • Der Stapel wird ausgetauscht, um seine Konstruktion zu erleichtern. Dies kann mit mehr Arbeit vermieden werden, aber das Übergeben va_list wird auch schwieriger, da es auf die Argumente zeigen muss, als ob sie auf dem Stapel übergeben wurden. Als eine Konsequenz muss der Code eine Annahme darüber machen, wie viel Stapel die aufgerufene Routine verwendet; In diesem Beispiel wird 8K angenommen, dies ist jedoch möglicherweise zu klein. Erhöhen Sie bei Bedarf.

+0

Es ist möglich, den Code zu verbessern, indem Sie den "Array-Stack" vor der Aufrufanweisung auf den realen Stack schieben? – arthurprs

+0

Barry - Respekt. Das habe ich gebraucht. – HNB

+0

@arthurprs - Wie ich bereits erwähnt habe, konstruiere ich Dinge im Array und platziere sie dann als Stapel, um Dinge einfach, verständlich und flexibel zu machen. Es ist viel schwieriger, die Details der Stapelverwaltung wegzuspulen, wenn Sie den echten Stapel verwenden. Das Kopieren im Stapel könnte auch durchgeführt werden. Ich überlasse es als eine Übung für den Leser ... :) –

2

Eine „Reihe von const "ist eigentlich ein Array von TVarRec, das eine spezielle Variante ist. Es ist nicht mit Varargs kompatibel, und Sie sollten die varargs-Funktion direkt aufrufen können, ohne dass ein Wrapper dahinter steht.

+0

Funktion PushString befindet sich tatsächlich im Datensatz (ich gab ein vereinfachtes Beispiel), und ich habe keinen direkten Zugriff auf Pushstring. Direktruf ist ausgeschlossen. – HNB

4

Der Wrapper Sie ist möglich, in Free Pascal zu schreiben versuchen, da Free Pascal für 2 Vorbehalte, Erklärungen unterstützt varargs externe Funktionen:

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

so statt

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

Sie sollten schreiben

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

Update: Ich habe den gleichen Trick in Delphi versucht, aber es funktioniert nicht:

//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

Danke für diese Informationen, schön zu wissen. – HNB

1

Barry Kelly hat mich inspiriert, um eine Lösung zu suchen, ohne den Stapel zu ersetzen ... Hier ist die Lösung (wahrscheinlich auch die Invoke von der rtti Einheit nutzen könnte, statt 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.