字符串是如此重要,以至于几乎没有VB6程序不使用字符串的。
与VBS一样,VB的字符串内部是用BSTR实现的,详见《VBS字符串的内部实现》,这里不赘述。
主要想说一下VB在调用Windows API时自动进行的Unicode/ANSI转换:
Private Declare Function CharUpper Lib "user32.dll" Alias "CharUpperA" (ByVal lpsz As String) As String
Sub Main()
Dim s As String
s = "http://manongku.com"
CharUpper s
Debug.Print s
End Sub
Private Sub Command1_Click()
Main
End Sub
生成的汇编代码如下:
00401701 mov edx, ___vba@09A675C0 ; UNICODE "http://manongku.com"
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版的函数:
Private Declare Function CharUpper Lib "user32.dll" Alias "CharUpperW" (ByVal lpsz As Long) As Long
Sub Main()
Dim s As String
s = "http://manongku.com"
CharUpper StrPtr(s)
Debug.Print s
End Sub
Private Sub Command1_Click()
Main
End Sub
注意函数的Declare的变化,参数和返回值的类型不再是String,而是Long,表示传递的是字符串的指针,所以要在调用函数时自己用StrPtr获取字符串的地址。
生成的汇编代码如下,是不是简洁很多?
00401691 mov edx, ___vba@0BF103C0 ; UNICODE "http://manongku.com"
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 = "MaNongKu"
b.Url = "http://manongku.com"
ZeroMemory b, LenB(b)
End Sub
生成的汇编代码如下:
00401951 mov edx, ___vba@0478BDA0 ; UNICODE "MaNongKu"
00401956 lea ecx, [ebp-18]
00401959 call ___vbaStrCopy ; [MSVBVM60.__vbaStrCopy
0040195E mov edx, ___vba@0478BDC4 ; UNICODE "http://manongku.com""
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 = "MaNongKu"
b.Url = "http://manongku.com"
ZeroMemory ByVal VarPtr(b), LenB(b)
End Sub
生成的汇编代码如下:
00401901 mov edx, ___vba@0088C160 ; UNICODE "MaNongKu"
00401906 lea ecx, [ebp-18]
00401909 call @__vbaStrCopy ; [MSVBVM60.__vbaStrCopy
0040190E mov edx, ___vba@0088C184 ; UNICODE "http://manongku.com"
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