6

Does anyone know a 100% clone of the C/C++ printf for Delphi? Yes, I know the System.Format function, but it handles things a little different.

For example if you want to format 3 to "003" you need "%03d" in C, but "%.3d" in Delphi.

I have an application written in Delphi which has to be able to format numbers using C format strings, so do you know a snippet/library for that?

Thanks in advance!

kroimon
  • 2,062
  • 3
  • 19
  • 23

5 Answers5

16

You could use the wsprintf() function from Windows.pas. Unfortunately this function is not declared correctly in the Windows.pas so here is a redeclaration:

function wsprintf(Output: PChar; Format: PChar): Integer; cdecl; varargs;
  external user32 name {$IFDEF UNICODE}'wsprintfW'{$ELSE}'wsprintfA'{$ENDIF};

procedure TForm1.FormCreate(Sender: TObject);
var
  S: String;
begin
  SetLength(S, 1024); // wsprintf can work only with max. 1024 characters
  SetLength(S, wsprintf(PChar(S), '%s %03d', 'Hallo', 3));
end;
Andreas Hausladen
  • 8,081
  • 1
  • 41
  • 44
  • 3
    Wow ok, I was a little too late answering my own question ;) Thank you, I think this is a much better solution than importing from msvcrt.dll! Will try what one later... – kroimon Mar 18 '10 at 17:49
  • After realizing that the `user32.wsprintf(W|A)` method is unable to handle floating points, I now decided to use `msvcrt._vsnw?printf` for now using your vararg-fix below. Best way would've been a version without external dependencies, but `msvcrt.dll` should be available everywhere I need it. – kroimon Mar 22 '10 at 09:30
9

If you want to let the function look more Delphi friendly to the user, you could use the following:

function _FormatC(const Format: string): string; cdecl;
const
  StackSlotSize = SizeOf(Pointer);
var
  Args: va_list;
  Buffer: array[0..1024] of Char;
begin
  // va_start(Args, Format)
  Args := va_list(PAnsiChar(@Format) + ((SizeOf(Format) + StackSlotSize - 1) and not (StackSlotSize - 1)));
  SetString(Result, Buffer, wvsprintf(Buffer, PChar(Format), Args));
end;

const // allows us to use "varargs" in Delphi
  FormatC: function(const Format: string): string; cdecl varargs = _FormatC;


procedure TForm1.Button1Click(Sender: TObject);
begin
  ShowMessage(FormatC('%s %03d', 'Hallo', 3));
end;
Andreas Hausladen
  • 8,081
  • 1
  • 41
  • 44
  • 1
    By reading the definition of the C/C++-macros va_start, va_list, va_end. – Andreas Hausladen Oct 29 '10 at 07:16
  • Wonderful hack, but seem to have problems (tried on D XE2). Buffer got filled correctly but on SetString first 3 chars of Result (and Buffer too!) got corrupted. Looks like wvsprintf corrupts stack or something like that. – Fr0sT Nov 22 '13 at 13:22
  • Here's quite tricky code that includes asm register magic: http://stackoverflow.com/questions/2305962/delphi-array-of-const-to-varargs/2306776#2306776. Unfortunately it means problems on platforms other than Win32... – Fr0sT Nov 22 '13 at 13:43
3

It's not recommended to use (ws)printf since they are prone to buffer overflow, it would be better to use the safe variants (eg StringCchPrintF). It is already declared in the Jedi Apilib (JwaStrSafe).

Remko
  • 7,214
  • 2
  • 32
  • 52
  • As I didn't want to include Jedi just for this one function, I decided to use `_vsnw?printf` from `msvcrt.dll` which should be safe, too, as it's second parameter is the buffer size... – kroimon Mar 22 '10 at 10:37
2

Well, I just found this one:

function sprintf(S: PAnsiChar; const Format: PAnsiChar): Integer;
    cdecl; varargs; external 'msvcrt.dll';

It simply uses the original sprintf function from msvcrt.dll which can then be used like that:

procedure TForm1.Button1Click(Sender: TObject);
var s: AnsiString;
begin
  SetLength(s, 99);
  sprintf(PAnsiChar(s), '%d - %d', 1, 2);
  ShowMessage(S);
end;

I don't know if this is the best solution because it needs this external dll and you have to set the string's length manually which makes it prone to buffer overflows, but at least it works... Any better ideas?

kroimon
  • 2,062
  • 3
  • 19
  • 23
0

more clean approach without unnecessary type casting

function sprintf(CharBuf: PChar; const Format: PAnsiChar): Integer;
cdecl; varargs; external 'msvcrt.dll';

procedure TForm1.Button1Click(Sender: TObject);
var CharBuf: PChar; 
begin
  CharBuf:=StrAlloc (99);
  sprintf(CharBuf, 'two numbers %d - %d', 1, 2);
  ShowMessage(CharBuf);
  StrDispose(CharBuf);
end;

If you happen to cross compile for Windows CE App. use coredll.dll instead of msvcrt.dll