Proc StringCbPrintfA:
Arguments @pszDest, @cbDest, @pszFormat
Local @cchDest, @vaList
Uses ebx, esi, edi
mov eax D@cbDest | mov D@cchDest eax
call StringValidateDestA 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 StringVPrintfWorkerA 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 StringValidateDestA:
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 StringLengthWorkerA D@pszDest, D@cchDest, edi
mov D@hr eax
Else
mov D$edi 0
End_If
.End_If
mov eax D@hr
EndP
Proc StringLengthWorkerA:
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 B$eax
On ecx = 0, jmp L1>
inc edi
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 StringVPrintfWorkerA:
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._vsnprintf' 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@pszDest | add eax D@ccchMax | mov B$eax 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: B$ 0 #MAX_SIZE]
[MAX_SIZE 30]
C_call StringCbPrintfA pszDest, MAX_SIZE, {B$ "%s %d + %d = %d.", 0}, {B$ "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
One question...
I´m a bit unconfortable with this M$ safestring function, since it does not return the length of the passed string. I wonder if it wouldn´t be better to use SetLastError to handle the HRESULT error cases, and make the function returns the lenght of the string if sucess or FALSE/NULL if the function fails. Then, in cases of failure the user should use the function GetLastError to retrieve it. Since the error is n a HRESULT form, then simply uses functions like ReportWinError (http://masm32.com/board/index.php?topic=3421.0) to retrieve the proper message.
What do you guys think ?
Although StringCbPrintfEx (http://msdn.microsoft.com/en-us/library/windows/desktop/ms647513%28v=vs.85%29.aspx) can be used to retrieve the passed size of the string, i think it is a bit unnecessary port instead simply only to make the function return the proper size on sucess or NULL/FALSE on fail.
A replacement to include the output of the len in eax and yet retrieve information about errors can be done as below:
Proc StringCbPrintfA:
Arguments @pszDest, @cbDest, @pszFormat
Local @cchDest, @iret, @vaList
Uses ebx, esi, edi
mov D@iret 0
mov eax D@cbDest | mov D@cchDest eax
call StringValidateDestA 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
lea ebx D@iret
call StringVPrintfWorkerA D@pszDest, D@cchDest, ebx, D@pszFormat, D@vaList
mov D@vaList 0
If eax <> &S_OK
call 'KERNEL32.SetLastError' eax | mov D@iret 0
End_If
.Else
call 'KERNEL32.SetLastError' eax | mov D@iret 0
.End_If
mov eax D@iret
EndSTD