Я предполагаю, что прототип pushfstring
несколько, как это:
void pushfstring(const char *fmt, va_list args);
Если ISN» t, и вместо этого:
void pushfstring(const char *fmt, ...);
... тогда я также должен был вас покрыть.
В C, если вы должны пройти по вызову от одного VARIADIC функции к другой, вы должны использовать va_list
, va_start
и va_end
, и вызвать версию функции v
. Итак, если вы сами внедрили printf
, вы можете использовать vsprintf
для форматирования строки - вы не можете напрямую позвонить sprintf
и пройти по списку вариационных аргументов. Вы должны использовать va_list
и друзей.
Это довольно неудобно для обработки C's va_list
из Delphi, и технически это не должно быть сделано - реализация 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. Соглашение о вызове C на x86 заключается в передаче аргументов справа налево в стеке, при этом очистка вызывающего абонента. Вот скелет родовых 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.
Calling версии 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.
Примечания:
Вышеупомянутый ожидает x86 в Windows. Microsoft C, bcc32 (Embarcadero C++) и gcc все пропускают va_list
таким же образом (указатель на первый вариационный аргумент в стеке), согласно моим экспериментам, поэтому он должен работать для вас; но как только ошибка x86 в Windows будет нарушена, ожидайте, что это тоже сломается.
Стек сворачивается, чтобы облегчить его строительство. Этого можно избежать при большей работе, но передача va_list
также становится более сложной, так как она должна указывать на аргументы, как если бы они были переданы в стек. Как следствие, код должен сделать предположение о том, сколько стека использует вызываемая процедура; этот пример предполагает 8K, но это может быть слишком мало. При необходимости увеличивайте.
Функция 'pushfstring', которую вы пытаетесь вызвать, является внешней функцией. Невозможно «не иметь прямого доступа» к нему, потому что вы можете сделать объявление для него в любом месте. Хотя я ценю ваше желание вызвать функцию varargs с неизвестным количеством параметров, на самом деле вам это не нужно, потому что вы * можете * напрямую вызывать 'pushfstring', где бы вы ни называли' PushString'. –
@Rob - Я подозреваю, что у него есть указатель на функцию. –
Что такое прототип C для 'pushfstring'? –