VB6拾遗:字符串与Unicode

标签: , , , , , ,

字符串是如此重要,以至于几乎没有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
赞赏

微信赞赏支付宝赞赏

随机文章:

  1. OpenWrt安装CIFS客户端挂载网络驱动器
  2. 不用循环计算1到100的和
  3. VB6拾遗:数组的内部实现
  4. Perl常用的内置特殊变量
  5. VBS ByRef和ByVal参数

一条评论 发表在“VB6拾遗:字符串与Unicode”上

  1. sany说道:

    你好,请问从WORD复制文本到VB6的text控件过程中VB做了什么?最近做一个数据库查询功能,从WORD复制名称到text能正常显示(无空格或乱码),但无法查询到数据;而直接用输入法输入或复制纯文本却可以查到结果。

sany 留下回复