VB6程序中验证ByREF, ByVal, VarPtr, StrPtr, Poke, Peek, DBCS, SBCS, Ansi, Unicode的区别与使用

字符串存储格式影响着编程使用,C格式字符串是以 \0 作为结束符,其长度可以由此计算,而VB格式的字符串其长度存于字符串前缀之中。不仅如此,字符串编码方式 ansi, unicode, widestring, dbcs, sbcs 这些概念也影响着长度计算。下面用VB6程序对它们进行剖析验证。

首先记住下面这个界面,后面要用到这些控件的名称。

VB6程序中验证ByREF, ByVal, VarPtr, StrPtr, Poke, Peek, DBCS, SBCS, Ansi, Unicode的区别与使用_第1张图片

下面Command1的代码,分析了ByRef传递的地址。

解释:首先定义局部字符串并赋值,在 Text1中显示字符串的VarPtr,这是字符变量的在STACK中的地址,然后将字符串作为参数调用下面的过程,过程中用了 ByRef A as string, 在过程中 Text2显示变量A的地址VarPtr。

Private Sub Command1_Click()
    Dim Astring As String
    Astring = "Hello World!"
    Text1.Text = CVar(VarPtr(Astring))
    Call VarTest1(Astring)
    Text6.Text = Astring
End Sub
Sub VarTest1(ByRef A As String)
    Text2.Text = CVar(VarPtr(A))
    A = "It's Changed!"
End Sub

运行上述程序,结果如下。它验证了Byref传递的是字符串变量的地址,相当于C中的别名,变量A是变量Astring的别名,它们有相同的地址。即然地址相同,那对A赋值,Astring会发生同样的变化,因为它们是同一地址。

VB6程序中验证ByREF, ByVal, VarPtr, StrPtr, Poke, Peek, DBCS, SBCS, Ansi, Unicode的区别与使用_第2张图片

 

下面是Command2对varptr和strptr的验证。

Private Sub Command2_Click()
    Dim Astring As String
    Dim vaddr As Long
    Dim caddr As Long
    Dim I As Integer
    
    Astring = "Hello World!"
    vaddr = VarPtr(Astring): Text3.Text = CVar(vaddr)
    Text1.Text = Right$(("0" + Hex$(Peek(vaddr + 3))), 2) _
        + Right$(("0" + Hex$(Peek(vaddr + 2))), 2) _
        + Right$(("0" + Hex$(Peek(vaddr + 1))), 2) _
        + Right$(("0" + Hex$(Peek(vaddr + 0))), 2)
    Text2.Text = Val("&H" + Text1.Text)
    caddr = StrPtr(Astring): Text4.Text = CVar(caddr)
    
    Text5.Text = ""
    For I = 1 To LenB(Astring)
        Text5.Text = Text5.Text + Chr$(Peek(caddr + I - 1))
    Next I
End Sub

运行结果:通过peek得到varptr处存的值是 16进制0716A1CC, 即10进制118923724,与strptr的值是完全一样的,从这个地址直接 peek 出来即是字符串的实际字符。

VB6程序中验证ByREF, ByVal, VarPtr, StrPtr, Poke, Peek, DBCS, SBCS, Ansi, Unicode的区别与使用_第3张图片

下面Command3先在Text1中显示 VarPtr值,然后把这个值以 byval 的方式传送给下面的sub过程,sub过程得到是 varptr 的值,然后用peek计算出varptr下面的内存值,再然后直接将另一字符串"It's changed!"移换掉原来的 "Hello World!"

Private Sub Command3_Click()
    Dim Astring As String
    Dim hAstr As Long
    
    Astring = "Hello World!"
    hAstr = VarPtr(Astring)
    
    Text1.Text = CVar(hAstr)
    
    Call VarTest2(hAstr)
    Text6.Text = Astring
End Sub
Sub VarTest2(ByVal B As Long)
    Dim Bstring As String
    Dim DestAddr As Long
    
    Bstring = "It's Changed!"
    Text2.Text = CVar(B)
    
    DestAddr = Val("&H" + (Right$(("0" + Hex$(Peek(B + 3))), 2) _
        + Right$(("0" + Hex$(Peek(B + 2))), 2) _
        + Right$(("0" + Hex$(Peek(B + 1))), 2) _
        + Right$(("0" + Hex$(Peek(B + 0))), 2)))
    
    Call MoveMemory(DestAddr, StrPtr(Bstring), LenB(Bstring))
End Sub

结果是下面的样子。label贴了没改,不影响程序代码。

VB6程序中验证ByREF, ByVal, VarPtr, StrPtr, Poke, Peek, DBCS, SBCS, Ansi, Unicode的区别与使用_第4张图片

下面的Command4在Text1中显示原字符串

在过程中显示Byval 后字符串的varptr和strptr。如果调用过程前显示 varptr和strptr的话(可以自己补上两句,程序中没写),对比会发现过程中用的源字符串的副本,内容相同,但varptr和strptr不同。

Private Sub Command4_Click()
    Dim Astring As String
    Astring = "Hello World!"
    Text1.Text = Astring
    Call ByvalTest(Astring)
End Sub
Sub ByvalTest(ByVal C As String)
    Text2.Text = CVar(VarPtr(C))
    Text6.Text = CVar(StrPtr(C))
End Sub

下面Command5的代码验证字符串长度存放在前缀的地方

Private Sub Command5_Click()
    Dim Bstring As String
    Dim caddr As Long
    Dim PreString As Long
    Bstring = "123456789"
    caddr = StrPtr(Bstring)
    
    PreString = Val("&H" + (Right$(("0" + Hex$(Peek(caddr - 1))), 2) _
        + Right$(("0" + Hex$(Peek(caddr - 2))), 2) _
        + Right$(("0" + Hex$(Peek(caddr - 3))), 2) _
        + Right$(("0" + Hex$(Peek(caddr - 4))), 2)))
    
    Text1.Text = CVar(PreString)
End Sub

首先定义一个字符串“123456789”,然后得到它的 strptr 于 caddr变量中,以这个值向前按big endian方式peek读取内存中的四个字节,得到的值显示在 Text1中。值是18,不是9,说明在内存中是按unicode存储的。

VB6程序中验证ByREF, ByVal, VarPtr, StrPtr, Poke, Peek, DBCS, SBCS, Ansi, Unicode的区别与使用_第5张图片

下面再说说字符串的 ansi, unicode, dbcs, sbcs, mbcs

mbcs是多字节编码,现在用的是dbcs双字节编码,早期用的是sbcs单字节编码,最早是ansi编码,现在基本上统一到 unicode 的UTF16

是期IBM PC时代是没有汉字的,用的都是ansi码表, 一个字节就代表一个字符。后来使用汉字,就在ansi后面扩展双字节汉字,形成单了节与双字节混合的ansi码表。再后来统一用双字节unicode的UTF16编码。下面引用一段程序验证说明这种情况。

Private Sub Command6_Click()
    Dim MyString, MyLen
    MyString = "中国c"
    ' Where "中" and "国" are DBCS and "c" is SBCS.
    MyLen = Len(MyString): Debug.Print MyLen
    ' Returns 3 - 3 characters in the string. 1+1+1
    MyLen = LenB(MyString): Debug.Print MyLen
    ' Returns 6 - 6 bytes used for Unicode. 2+2+2
    MyLen = LenMbcs(MyString): Debug.Print MyLen
    ' Returns 5 - 5 bytes used for ANSI. 2+2+1
End Sub
Function LenMbcs(ByVal str As String)
    LenMbcs = LenB(StrConv(str, vbFromUnicode))
End Function

字符串是双字节与单字节混合的 “中国c”,用len得到的结果是 1+1+1=3, 表明是3个字符。用lenb得到是unicde值,每个字符,不分单双字节,全部按双字节计算,因此是2+2+2 = 6。用lenb(strconv(str, vbFromUnicode))变换出的是ansi值,汉字是2个字节,英文字母是1个字节,所以值是2+2+1=5。上面验证过了,从字符串前缀peek得到的值字符串“123456789”是18,它是按unicode存储的。

应付字符串的不同编码,VB这是上述处理的,Delphi设了专门的ansistring, string和widestring类型,c本身是\0结尾的直接就出字符串长度,但它要用BString的话也必须计算转换。面对如此烦锁的转换,不同语言之间传递参数时一般不建议使用字符串,VB可以使用byte数组,delphi可以使用pchar,通过直接传送单字节数组地址比字符串更方便。总之,传送地址,由被call程序根据地址取数据是比较方便的。这些内容好理解不好表达,感觉受水平所限没写好没写透。

差点忘了,poke和peek函数我放到下面。vb要用的话,把它们放在一个 module 里。

Public Declare Sub MoveMemory _
        Lib "KERNEL32" _
        Alias "RtlMoveMemory" _
        (ByVal Dest As Any, _
         ByVal Source As Any, _
         ByVal Bytes As Long)
Public Declare Sub MoveString _
        Lib "KERNEL32" _
        Alias "RtlMoveMemory" _
        (Dest As Any, _
         ByVal Source As String, _
         ByVal Bytes As Long)
Public Sub Poke(MemLoc&, B As Byte)
    Call MoveMemory(MemLoc, VarPtr(B), 1)
End Sub
Public Function Peek(MemLoc&) As Byte
    Dim Q As Byte
    Call MoveMemory(VarPtr(Q), MemLoc, 1)
    Peek = Q
End Function

你可能感兴趣的:(MBCS,DBCS,SBCS,Byref,Byval,Varptr,Strptr,peek,poke,VB,字符串)