标题: VB6拾遗:字符串与Unicode
作者: Demon
链接: https://demon.tw/programming/vb6-repick-string-unicode.html
版权: 本博客的所有文章,都遵守“署名-非商业性使用-相同方式共享 2.5 中国大陆”协议条款。
字符串是如此重要,以至于几乎没有VB程序不使用字符串的。
与VBS一样,VB的字符串内部是用BSTR实现的,详见《VBS字符串的内部实现》,这里不赘述。
主要想说一下VB在调用Windows API时自动进行的Unicode/ANSI转换:
Declare Function CharUpper Lib "user32.dll" Alias "CharUpperA" ( _ ByVal lpsz As String) As String Sub Main() Dim s As String s = "https://demon.tw" CharUpper s Debug.Print s End Sub
生成的汇编代码如下:
00401701 mov edx, ___vba@09A675C0 ; UNICODE "https://demon.tw" 00401706 lea ecx, [ebp-14] ; Unicode字符串地址 00401709 call @__vbaStrCopy ; MSVBVM60.__vbaStrCopy 0040170E push dword ptr [ebp-14] ; 00401711 lea eax, [ebp-18] ; ANSI字符串地址 00401714 push eax ; 00401715 call ___vbaStrToAnsi ; Unicode转ANSI 0040171A push eax 0040171B call ___vba@09A67578 ; 调用CharUpperA 00401720 mov edx, eax ; eax为CharUpperA函数返回值 00401722 lea ecx, [ebp-1C] ; 保存返回值的ANSI字符串地址 00401725 call @__vbaStrMove ; MSVBVM60.__vbaStrMove 0040172A call @__vbaSetSystemError ; MSVBVM60.__vbaSetSystemError 0040172F push dword ptr [ebp-18] ; 00401732 lea eax, [ebp-14] ; 00401735 push eax ; 00401736 call @__vbaStrToUnicode ; ANSI转Unicode
VB6是Windows 98那个年代的古董,那时Windows内核还是ANSI的,而VB6字符串却是Unicode的。为了方便人们调用API,当Declare的函数参数为String或者Any时,VB会自作聪明的在调用前将字符串转成ANSI。
在Unicode内核的现在,这样的设定未免太愚蠢了,因为大部分A版API只不过是W版API的简单封装,内部先把ANSI字符串转成Unicode,然后调用对应的W版函数。也就是说,在VB调用ANSI版API之前将Unicode转成ANSI,而ANSI版API内部把ANSI转成Unicode调用对应的函数,然后又把Unicode转成ANSI作为返回值,最后VB再次将ANSI转成Unicode。
要想提高效率,就要想办法避免这些频繁的Unicode/ANSI转换,比如说可以使用W版的函数:
Declare Function CharUpper Lib "user32.dll" Alias "CharUpperW" ( _ ByVal lpsz As Long) As Long Sub Main() Dim s As String s = "https://demon.tw" CharUpper StrPtr(s) Debug.Print s End Sub
注意函数的Declare的变化,参数和返回值的类型不再是String,而是Long,表示传递的是字符串的指针,所以要在调用函数时自己用StrPtr获取字符串的地址。
生成的汇编代码如下,是不是简洁很多?
00401691 mov edx, ___vba@0BF103C0 ; UNICODE "https://demon.tw" 00401696 lea ecx, [ebp-14] 00401699 call @__vbaStrCopy ; MSVBVM60.__vbaStrCopy 0040169E push dword ptr [ebp-14] ; 004016A1 call @__vba@0BF104A0 ; MSVBVM60.VarPtr 004016A6 push eax 004016A7 call ___vba@0BF10378 ; CharUpperW 004016AC call ___vbaSetSystemError ; MSVBVM60.__vbaSetSystemError 004016B1 push 004016BF 004016B6 lea ecx, [ebp-14] 004016B9 call ___vbaFreeStr ; MSVBVM60.__vbaFreeStr
除了单纯的字符串之外,嵌套在用户定义类型中的字符串也会自动进行Unicode/ANSI转换:
Public Declare Sub ZeroMemory Lib "KERNEL32" Alias "RtlZeroMemory" _ (dest As Any, ByVal numBytes As Long) Type Blog Name As String Url As String End Type Sub Main() Dim b As Blog b.Name = "Demon's Blog" b.Url = "https://demon.tw" ZeroMemory b, LenB(b) End Sub
生成的汇编代码如下:
00401951 mov edx, ___vba@0478BDA0 ; UNICODE "Demon's Blog" 00401956 lea ecx, [ebp-18] 00401959 call ___vbaStrCopy ; [MSVBVM60.__vbaStrCopy 0040195E mov edx, ___vba@0478BDC4 ; UNICODE "https://demon.tw" 00401963 lea ecx, [ebp-14] 00401966 call ___vbaStrCopy ; [MSVBVM60.__vbaStrCopy 0040196B push 8 0040196D lea eax, [ebp-18] 00401970 push eax ; /Arg3 => offset LOCAL.6 00401971 lea eax, [ebp-20] ; | 00401974 push eax ; |Arg2 => offset LOCAL.8 00401975 push ___vba@0478BD14 ; |Arg1 = Project1.___vba@0478BD14 0040197A call @__vbaRecUniToAnsi ; \MSVBVM60.__vbaRecUniToAnsi 0040197F push eax 00401980 call ___vba@0478BD58 ; ZeroMemory 00401985 call @__vbaSetSystemError ; [MSVBVM60.__vbaSetSystemError 0040198A lea eax, [ebp-20] 0040198D push eax ; /Arg3 => offset LOCAL.8 0040198E lea eax, [ebp-18] ; | 00401991 push eax ; |Arg2 => offset LOCAL.6 00401992 push ___vba@0478BD14 ; |Arg1 = Project1.___vba@0478BD14 00401997 call @__vbaRecAnsiToUni ; \MSVBVM60.__vbaRecAnsiToUni 0040199C lea eax, [ebp-20] 0040199F push eax ; /Arg2 => offset LOCAL.8 004019A0 push ___vba@0478BD14 ; |Arg1 = Project1.___vba@0478BD14 004019A5 call ___vbaRecDestructAnsi ; \MSVBVM60.__vbaRecDestructAnsi 004019AA push 004019CC 004019AF lea eax, [ebp-20] 004019B2 push eax ; /Arg2 => offset LOCAL.8 004019B3 push ___vba@0478BD14 ; |Arg1 = Project1.___vba@0478BD14 004019B8 call ___vbaRecDestructAnsi ; \MSVBVM60.__vbaRecDestructAnsi 004019BD lea eax, [ebp-18] 004019C0 push eax ; /Arg2 => offset LOCAL.6 004019C1 push ___vba@0478BD14 ; |Arg1 = Project1.___vba@0478BD14 004019C6 call ___vbaRecDestruct ; \MSVBVM60.__vbaRecDestruct
可以看到调用ZeroMemory前后的__vbaRecUniToAnsi和__vbaRecAnsiToUni。
要避免自动用户定义类型的Unicode/ANSI转换,可以使用VarPtr函数:
Sub Main() Dim b As Blog b.Name = "Demon's Blog" b.Url = "https://demon.tw" ZeroMemory ByVal VarPtr(b), LenB(b) End Sub
生成的汇编代码如下:
赞赏00401901 mov edx, ___vba@0088C160 ; UNICODE "Demon's Blog" 00401906 lea ecx, [ebp-18] 00401909 call @__vbaStrCopy ; [MSVBVM60.__vbaStrCopy 0040190E mov edx, ___vba@0088C184 ; UNICODE "https://demon.tw" 00401913 lea ecx, [ebp-14] 00401916 call @__vbaStrCopy ; [MSVBVM60.__vbaStrCopy 0040191B lea eax, [ebp-18] 0040191E push eax ; /Arg1 => offset LOCAL.6 0040191F call ___vba@0088B2DC ; \MSVBVM60.VarPtr 00401924 mov dword ptr [ebp-1C], eax 00401927 push 8 00401929 push dword ptr [ebp-1C] 0040192C call ___vba@0088C118 ; ZeroMemory 00401931 call ___vbaSetSystemError ; [MSVBVM60.__vbaSetSystemError 00401936 push 0040194A 0040193B lea eax, [ebp-18] 0040193E push eax ; /Arg2 => offset LOCAL.6 0040193F push ___vba@0088C0D4 ; |Arg1 = Project1.___vba@0088C0D4 00401944 call ___vbaRecDestruct ; \MSVBVM60.__vbaRecDestruct
微信赞赏支付宝赞赏
随机文章:
你好,请问从WORD复制文本到VB6的text控件过程中VB做了什么?最近做一个数据库查询功能,从WORD复制名称到text能正常显示(无空格或乱码),但无法查询到数据;而直接用输入法输入或复制纯文本却可以查到结果。