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.
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. –
@Rob - Ich vermute, er hat einen Funktionszeiger. –
Was ist der C-Prototyp für 'Pushfstring'? –