Windows中增加StringCbPrintf2函数以使用资源字符串

Windows中增加StringCbPrintf2函数以使用资源字符串

StringCbPrintf函数(旧版本的printf、wprintf等函数)是字符串格式化函数,使用该函数可以将一组字符串和数值按指定的格式进行显示。但该函数的字符串参数不能使用资源字符串ID,这对于编写国际化软件不太友好。本文介绍如何增加一个StringCbPrintf2格式化显示函数,使格式化函数能接受资源字符串ID。

在《Windows菜单本地化方法》一文中提到过,软件的本地化主要是字符串本地化,常规的做法是通过修改资源消息表和字符串表来实现的。在《使用FormatMessage函数实现多语言消息》一文中介绍过消息表的使用方法,资源消息表的字符串是用于格式化显示的,但消息表不能替代字符串表,字符串表同样不能替代消息表。增加一个StringCbPrintf2格式化显示函数,使格式化函数能接受资源字符串ID,这样就不需要消息表,减少了很多的麻烦。

1. 安全字符串的格式化显示函数

如果使用C/C++语言,就不需要了解本节内容。因为本文代码是使用Masm64语言写的,而Masm64中并没有安全字符串函数,所以需要自己编写安全字符串函数,这其实是很简单的事,可以有多种方法,以下介绍使用Strsafe.lib库编写安全字符串的格式化显示函数的方法。

先要获取两个文件Strsafe.lib和Strsafe.h,本人在Microsoft Visual Studio的安装目录中获取,该两个文件分别位于SDK\ScopeCppSDK\vc15\SDK\lib和SDK\ScopeCppSDK\vc15\SDK\include\shared目录内。

Strsafe.lib是静态库,它只有安全字符串的工作函数,安全字符串函数是通过调用工作函数来实现的(具本见Strsafe.h)。使用Strsafe.lib库来编写Masm64的安全字符串函数,需要一个包含文件StrSafe.inc,列出Strsafe.lib库中的工作函数。

StrSafe.inc文件的内容如下:

 includelib StrSafe.lib
;---工作函数---
 externdef StringLengthWorkerA:PROC
 externdef StringLengthWorkerW:PROC
 externdef UnalignedStringLengthWorkerW:PROC
 externdef StringExValidateSrcA:PROC
 externdef StringExValidateSrcW:PROC
 externdef StringValidateDestA:PROC
 externdef StringValidateDestAndLengthA:PROC
 externdef StringValidateDestW:PROC
 externdef StringValidateDestAndLengthW:PROC
 externdef StringExValidateDestA:PROC
 externdef StringExValidateDestAndLengthA:PROC
 externdef StringExValidateDestW:PROC
 externdef StringExValidateDestAndLengthW:PROC
 externdef StringCopyWorkerA:PROC
 externdef StringCopyWorkerW:PROC
 externdef StringVPrintfWorkerA:PROC
 externdef StringVPrintfWorkerW:PROC
 externdef StringGetsWorkerA:PROC
 externdef StringGetsWorkerW:PROC
 externdef StringExHandleFillBehindNullA:PROC
 externdef StringExHandleFillBehindNullW:PROC
 externdef StringExHandleOtherFlagsA:PROC
 externdef StringExHandleOtherFlagsW:PROC

以下是StringCbVPrintfW和StringCbPrintfW函数的例子。其它函数的写法类同,具体见Strsafe.h中的宏函数。


;=======================================================
;格式化字符串
;入: pszDest=接收缓冲区地址
;    cbDest=接收缓冲区长度(以WORD为单位)
;    pszFormat=格式化按制串地址
;    pArgList=插入值列表数组地址
;出: RAX=S_OK: 成功
;=======================================================
StringCbVPrintfW proc pszDest:QWORD,cbDest:QWORD,\
                      pszFormat:QWORD,pArgList:QWORD
 invoke StringValidateDestW,pszDest,cbDest,STRSAFE_MAX_CCH ;查检参数的安全性
 cmp eax,S_OK
 jnz ss_er    ;不安全
 invoke StringVPrintfWorkerW,pszDest,cbDest,NULL,pszFormat,pArgList
 ret
ss_er:
 mov rcx,pszDest
 test rcx,rcx
 jz ss_0
 cmp cbDest,0
 jle ss_0
 xor dx,dx
 mov [rcx],dx
ss_0:
 ret
StringCbVPrintfW endp
;=======================================================
;格式化字符串
;入: pszDest=接收缓冲区地址
;    cbDest=接收缓冲区长度(以WORD为单位)
;    pszFormat=格式化按制串地址
;    ArgList=插入值列表(可变参数)
;出: RAX=S_OK: 成功
;=======================================================
StringCbPrintfW proc pszDest:QWORD,cbDest:QWORD,\
                     pszFormat:QWORD,ArgList:QWORD
 invoke StringCbVPrintfW,rcx,rdx,r8,ADDR ArgList
 ret
StringCbPrintfW endp

注意,在StringCbPrintfW函数中的ArgList参数为可变参数,在《Masm64中函数可变参数VARARG的实现方法》一文中曾经讲过,在Masm64中可变参数类型使用QWORDS自定义宏,但如果函数的参数个数为4个或4个以上,则变可参数会存入到栈中,这种情况下可以不使用QWORDS自定义宏,而直接使用QWORD类型即可。

在StringCbVPrintfW函数中使用了两个函数StringValidateDestW和StringVPrintfWorkerW,这两个函数为Strsafe.lib库中的工作函数。

字符串格式化函数有两种形式,函数名带"V"的(如StringCbVPrintfW)函数的格式化插入参数为QWORD型数组地址,函数名不带"V"的(如StringCbPrintfW)函数的格式化插入参数为QWORD型值列表。

2. 需要的几个基本函数

所需的基本函数多数已在《Windows菜单本地化方法》一文中介绍过,这里新增3个函数Rsrc_GetStringToMemA、Rsrc_GetStringToMemW和Printf2_Free,一并列出。

;=============================================================
;装载一个指定语言的字符串资源---Unicode
;入: hInst=包含字符串资源的实例句柄
;         =0: 当前进程
;    uId=字符串资源ID
;    uLangId=语言ID
;    pBuf=字符接收缓冲区地址
;    zSize=字符接收缓冲区长度(以WORD为单位)
;          如果缓冲区太小,则返回的串被截断。
;         =0: 只返回所需内存数
;出: RAX=字符串的字符个数,不包括结尾符NULL。
;        如果zSize=0,则为所需内存数,包括结尾符NULL,
;        (以WORD为单位)。
;       =0: 未发现
;=============================================================
Rsrc_LoadStringExW proc hInst:QWORD,uId:QWORD,uLangId:QWORD,\
                        pBuf:QWORD,zSize:DWORD
 test r9,r9  ;pBuf
 jnz ss_1
 mov zSize,0
 jmp ss_2
ss_1:
 xor eax,eax
 mov [r9],ax   ;pBuf初始化
ss_2:
;--------------
; 搜索组
;--------------
;---将字符串ID转换为组序号---
 and rdx,0ffffh
 shr rdx,4    ;/16
 inc rdx
 mov r8,rdx
 invoke FindResourceExW,hInst,RT_STRING,r8,uLangId
 test rax,rax
 jz ss_0
 invoke LoadResource,hInst,rax     ;装入字符串组数组
 invoke LockResource,rax           ;锁定内存(不需要解锁)
;-----------------
;在组内搜索字符串
;-----------------
 mov rdx,uId
 and edx,0fh   ;该字符串在组内的序号
 jz ss_ok      ;组首地址
ss_lp1:
 xor rcx,rcx
 mov cx,[rax]  ;Unicide串的字符个数
 inc ecx       ;长度单元的WORD
 shl ecx,1
 add rax,rcx   ;指向下一个串
 dec edx
 jnz ss_lp1
ss_ok:
 xor r9,r9
 mov r9w,[rax]
 test r9w,r9w
 jz ss_no
;---复制字符串---
 cmp zSize,0
 jz ss_retn
 add rax,2
 mov edx,zSize
 invoke StringCbCopyNW,pBuf,rdx,rax,r9 ;串复制
 mov rax,rcx
 shr rax,1
 ret
ss_retn:  ;返回所需内存长度
 xor rcx,rcx
 mov cx,[rax]
 mov rax,rcx
 inc rax
 ret
ss_no:    ;未发现
 xor rax,rax
 mov rdx,pBuf
 test rdx,rdx
 jz ss_0
 mov [rdx],ax
ss_0:
 ret
Rsrc_LoadStringExW endp
;===========================================================
;装载一个指定语言的字符串资源---ANSI
;入: hInst=包含字符串资源的实例句柄
;         =0: 当前进程
;    uId=字符串资源ID
;    uLangId=语言ID
;    pBuf=字符接收缓冲区地址
;    zSize=字符接收缓冲区字节长度
;         =0: 只返回所需内存数
;出: RAX=字符串的字节长度,不包括结尾符NULL。
;        如果 zSize=0,则为所需内存字节长度,包括结尾符NULL。
;       =0: 未发现或缓冲区溢出
;===========
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值