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.
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'. –
@Rob - Sospetto che abbia un puntatore a funzione. –
Qual è il prototipo C per 'pushfstring'? –