Tôi đoán rằng nguyên mẫu cho pushfstring
hơi như thế này:
void pushfstring(const char *fmt, va_list args);
Nếu không phải tên' t và thay vào đó là:
void pushfstring(const char *fmt, ...);
... thì tôi cũng nên đề cập đến bạn.
Trong C, nếu bạn phải vượt qua trên một cuộc gọi từ một chức năng variadic khác, bạn nên sử dụng va_list
, va_start
và va_end
, và gọi phiên bản v
của hàm. Vì vậy, nếu bạn tự mình thực hiện printf
, bạn có thể sử dụng vsprintf
để định dạng chuỗi - bạn không thể gọi trực tiếp sprintf
và chuyển qua danh sách đối số variadic. Bạn cần sử dụng va_list
và bạn bè.
Thật khó xử lý khi xử lý C's va_list
từ Delphi, và về mặt kỹ thuật không nên thực hiện - việc triển khai va_list
dành riêng cho thời gian chạy của nhà cung cấp trình biên dịch C.
Tuy nhiên, chúng tôi có thể thử. Giả sử chúng ta có một lớp học nhỏ - mặc dù tôi đã làm cho nó một kỷ lục để dễ sử dụng:
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;
Sử dụng hồ sơ này, chúng tôi có thể tự xây dựng khung gọi dự kiến cho các cuộc gọi C khác nhau. Quy ước gọi điện của C trên x86 là chuyển đối số từ phải sang trái trên ngăn xếp, với người gọi dọn dẹp. Dưới đây là bộ xương của một C generic gọi thói quen:
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;
Lấy printf
làm ví dụ:
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 phiên bản va_list
là hơi tham gia nhiều hơn, như vị trí các va_list
lập luận của cần phải được đặt cẩn thận nơi dự kiến:
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.
Ghi chú:
Điều trên mong đợi x86 trên Windows. Microsoft C, bcc32 (Embarcadero C++) và gcc tất cả vượt qua va_list
theo cùng một cách (một con trỏ đến đối số variadic đầu tiên trên ngăn xếp), theo các thử nghiệm của tôi, vì vậy nó sẽ làm việc cho bạn; nhưng ngay sau khi giả định x86 trên Windows bị hỏng, mong đợi điều này có thể sẽ bị phá vỡ.
Ngăn xếp được đổi chỗ để dễ dàng với việc xây dựng. Điều này có thể tránh được với nhiều công việc hơn, nhưng việc vượt qua va_list
cũng trở nên phức tạp hơn, vì nó cần phải chỉ ra các đối số như thể chúng được truyền trên stack. Kết quả là, mã cần tạo một giả định về mức độ sử dụng thường xuyên của stack; ví dụ này giả định 8K, nhưng điều này có thể quá nhỏ. Tăng nếu cần.
Chức năng 'pushfstring' bạn đang cố gọi là chức năng bên ngoài. Không thể "không có quyền truy cập trực tiếp" vào nó bởi vì bạn có thể khai báo nó ở bất cứ đâu bạn muốn. Mặc dù tôi đánh giá cao mong muốn của bạn gọi hàm varargs với số tham số không xác định, bạn không thực sự cần nó trong trường hợp của bạn vì bạn * có thể * gọi trực tiếp 'pushfstring' từ bất cứ nơi nào bạn đã gọi là' PushString'. –
@Rob - Tôi nghi ngờ anh ta có một con trỏ hàm. –
Nguyên mẫu C cho 'pushfstring' là gì? –