2010-02-21 9 views
8

Aiutateci! Ho bisogno di questa conversione per scrivere wrapper per alcune intestazioni C per Delphi.Delphi "serie di const" a "varargs"

Per fare un esempio:

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; 

Come posso convertire "serie di const" a "varargs"?

modifica: la funzione PushString è effettivamente all'interno del record (ho fornito un esempio semplificato) e non ho accesso diretto a pushfstring. La chiamata diretta è esclusa.

modifica 2: Scrivo le unità per la libreria LUA per Delphi e il caso è molto importante per me.

Definizione e fornendo tutti i dettagli della questione - ho questa funzione in C:

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

In Delphi ho qualcosa di simile a questo:

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;

e in altre unità, come Lua.pas Io uso solo TLuaState da dtxLua.pas (perché LuaLibrary è ingombrante, dtxLua è il mio involucro), per molte cose utili e interessanti ...

+0

La funzione 'pushfstring' che si sta tentando di chiamare è una funzione esterna. È impossibile "non avere accesso diretto" ad esso perché puoi fare una dichiarazione per esso ovunque tu voglia. Anche se apprezzo il tuo desiderio di chiamare una funzione varargs con un numero sconosciuto di parametri, in realtà non è necessario nel tuo caso perché * puoi * chiamare direttamente 'pushfstring' da qualunque posto tu abbia chiamato' PushString'. –

+0

@Rob - Sospetto che abbia un puntatore a funzione. –

+0

Qual è il prototipo C per 'pushfstring'? –

risposta

12

Sto indovinando che il prototipo per pushfstring è un po 'come questo:

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

Se esso isn' t, ed è invece:

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

... quindi dovrei averti coperto anche tu.

In C, se si deve passare una chiamata da una funzione variadic ad un altro, è necessario utilizzare va_list, va_start e va_end, e chiamare la versione v della funzione. Quindi, se si stesse implementando printf da soli, è possibile utilizzare vsprintf per formattare la stringa - non è possibile chiamare direttamente sprintf e passare lungo l'elenco degli argomenti variadici. Devi usare va_list e amici.

È piuttosto difficile gestire C's va_list da Delphi e tecnicamente non dovrebbe essere eseguito: l'implementazione di va_list è specifica per il runtime del fornitore del compilatore C.

Tuttavia, possiamo provare. Supponiamo di avere un po 'di classe - se ho fatto un record per facilità d'uso:

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; 

Utilizzando questo disco, possiamo costruire manualmente il frame di chiamata prevista per varie chiamate C. La convenzione di chiamata di C su x86 consiste nel passare argomenti da destra a sinistra nello stack, con la cancellazione del chiamante. Ecco lo scheletro di un generico C chiamando di routine:

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; 

Prendendo printf come esempio:

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. 

Chiamando la versione va_list è leggermente più coinvolti, come la posizione del ragionamento va_list deve essere posta attenzione dove si prevede:

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. 

Note:

  • Quanto sopra si aspetta x86 su Windows. Microsoft C, bcc32 (Embarcadero C++) e gcc passano tutti allo va_list allo stesso modo (un puntatore al primo argomento variadico nello stack), secondo i miei esperimenti, quindi dovrebbe funzionare per te; ma non appena l'x86 sull'assunzione di Windows viene interrotta, aspettati che si rompa anche tu.

  • Lo stack è scambiato per facilitare la sua costruzione. Questo può essere evitato con più lavoro, ma passare va_list diventa anche più complicato, in quanto ha bisogno di puntare gli argomenti come se fossero passati in pila. Di conseguenza, il codice deve fare un'ipotesi sulla quantità di stack utilizzata dalla routine chiamata; questo esempio assume 8K, ma potrebbe essere troppo piccolo. Aumentare se necessario.

+0

È possibile migliorare il codice spingendo lo "stack di array" nella pila reale prima dell'istruzione di chiamata? – arthurprs

+0

Barry - Rispetto. Questo è quello di cui avevo bisogno. – HNB

+0

@arthurprs - come ho già detto, costruisco le cose nell'array e poi lo inserisco come pila per rendere le cose facili, comprensibili e flessibili. È molto più difficile sradicare i dettagli della gestione dello stack quando si utilizza lo stack reale. Si può anche fare copia nello stack. Lo lascio come esercizio per il lettore ... :) –

2

Un "array di const "è in realtà una matrice di TVarRec, che è un tipo di variante speciale. Non è compatibile con varargs e dovresti essere in grado di chiamare la funzione varargs direttamente senza un wrapper.

+0

PushString è effettivamente all'interno del record (ho dato un esempio semplificato) e non ho accesso diretto a pushfstring. La chiamata diretta è esclusa. – HNB

4

Il wrapper che si sta tentando di scrivere è possibile in Free Pascal, dal momento che Free Pascal supporta 2 dichiarazioni equvalent per varargs funzioni esterne:

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

Così, invece di

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

si dovrebbe scrivere

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

Aggiornamento: Ho provato lo stesso trucco in Delphi, ma non funziona:

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

Grazie per queste informazioni, bello sapere. – HNB

1

Barry Kelly mi ha spinto a cercare una soluzione senza sostituire la pila ... Ecco la soluzione (probabilmente potrebbe anche utilizzare l'Invoke dall'unità RTTI, invece 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.
Problemi correlati