のプロトタイプpushfstring
は次のようなものだと思います。
void pushfstring(const char *fmt, va_list args);
そうでなく、代わりにある場合:
void pushfstring(const char *fmt, ...);
...それなら、あなたにもカバーしてもらいましょう。
C では、ある可変個引数関数から別の関数に呼び出しを渡す必要がある場合はva_list
、va_start
とを使用し、関数のバージョンをva_end
呼び出す必要があります。v
したがって、自分で実装している場合は、文字列をフォーマットするためにprintf
使用できます。直接呼び出して可変引数リストを渡すことはできません。友達と使う必要があります。vsprintf
sprintf
va_list
Delphi からC を処理するのは非常に厄介va_list
であり、技術的には行うべきではありません。 の実装はva_list
、C コンパイラ ベンダーのランタイムに固有のものです。
ただし、試すことはできます。ちょっとしたクラスがあるとします - 使いやすいようにレコードにしましたが:
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;
このレコードを使用して、さまざまな C 呼び出しに必要な呼び出しフレームを手動で構築できます。x86 での C の呼び出し規約は、引数をスタック上で右から左に渡し、呼び出し元がクリーンアップすることです。一般的な C 呼び出しルーチンのスケルトンを次に示します。
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;
例printf
として:
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.
バージョンの呼び出しは、引数の場所を期待される場所に慎重に配置する必要があるためva_list
、少し複雑です。va_list
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.
ノート:
上記は、Windows 上の x86 を想定しています。私の実験によると、 Microsoft C、bcc32 (Embarcadero C++)、および gcc はすべてva_list
同じ方法 (スタック上の最初の可変引数へのポインター) で渡されるため、うまくいくはずです。しかし、Windows 上の x86 の仮定が破られるとすぐに、これも壊れる可能性があると予想してください。
スタックは、その構築を容易にするために交換されます。これは、より多くの作業を行うことで回避できますがva_list
、引数がスタックに渡されたかのように引数を指す必要があるため、渡すのも難しくなります。結果として、コードでは、呼び出されたルーチンが使用するスタックの量を想定する必要があります。この例では 8K を想定していますが、これは小さすぎる可能性があります。必要に応じて増やしてください。