Proc StringCbPrintfW:
Arguments @pszDest, @cbDest, @pszFormat
Local @cchDest, @vaList
Uses ebx, esi, edi
mov eax D@cbDest | shr eax 1 | mov D@cchDest eax
call StringValidateDestW D@pszDest, D@cchDest, &NULL, &STRSAFE_MAX_CCH
If eax = &SUCCEEDED
; there is no need to make vaList global. Since it is only a placeholder of the parameters beyond pszFormat
lea eax D@pszFormat | add eax 4 | mov D@vaList eax
call StringVPrintfWorkerW D@pszDest, D@cchDest, &NULL, D@pszFormat, D@vaList
mov D@vaList 0
End_If
; EndSTD macro is just the end of a procedure on a stdcall form. i.e: only a ret value (it is not an "ret N")
EndSTD
Proc StringValidateDestW:
Arguments @pszDest, @cchDest, @pcchDestLength, @cchMax
Local @hr
Uses ebx, edi
mov D@hr &S_OK
mov edi D@pcchDestLength
mov ebx D@cchMax
.If_Or D@cchDest = 0, D@cchDest > ebx
mov D@hr &STRSAFE_E_INVALID_PARAMETER
.End_If
.If edi <> 0
If D@hr = &S_OK
call StringLengthWorkerW D@pszDest, D@cchDest, edi
mov D@hr eax
Else
mov D$edi 0
End_If
.End_If
mov eax D@hr
EndP
Proc StringLengthWorkerW:
Arguments @psz, @cchMax, @pcchLength
Local @hr
Uses ebx, edi, ecx
mov D@hr &S_OK
mov edi D@psz
mov ebx D@cchMax
While ebx <> 0
mov eax edi
movzx ecx W$eax
On ecx = 0, jmp L1>
add edi 2
dec ebx
End_While
L1:
; the string is longer than cchMax
.If ebx = 0
mov D@hr &STRSAFE_E_INVALID_PARAMETER
.End_If
mov edi D@pcchLength
.If edi <> 0
If D@hr = &S_OK
mov eax D@cchMax | sub eax ebx | mov D$edi eax
ELse
mov D$edi 0
End_If
.End_If
mov eax D@hr
EndP
Proc StringVPrintfWorkerW:
Arguments @pszDest, @cchDest, @pcchNewDestLength, @pszFormat, @argList
Local @hr, @ccchMax
Uses ebx, esi, edi
mov D@hr &S_OK
; leave the last space for the null terminator
mov eax D@cchDest | dec eax | mov D@ccchMax eax
C_call 'msvcrt._vsnwprintf' D@pszDest, D@ccchMax, D@pszFormat, D@argList
.If_Or eax <s 0, eax => D@ccchMax
If eax <> D@ccchMax
mov D@hr &STRSAFE_E_INSUFFICIENT_BUFFER
End_If
; need to null terminate the string, and truncate it
mov eax D@ccchMax | mov ecx D@pszDest | lea edx D$ecx+eax*2
add ecx edx | mov W$ecx 0
mov eax D@ccchMax
.End_If
If D@pcchNewDestLength <> 0
mov ebx D@pcchNewDestLength
mov D$ebx eax
End_If
mov eax D@hr
EndP
Example of usage:
[pszDest: W$ 0 #MAX_SIZE]
[MAX_SIZE 30]
C_call StringCbPrintfW pszDest, (MAX_SIZE*2), {U$ "%s %d + %d = %d.", 0}, {U$ "The answer is", 0}, 1, 2, 3
Note:
C_call macro is only a call instruction followed by the stack adjustments "add esp (x*4)", where X is the number of arguments passed through the function