其中VB/VBA的String类型,就是COM中的BSTR类型。将BSTR的定义翻译成VB/VBA,就是下面的样子:
Private Type BSTR '标准BSTR结构
dwSize As Long '[4] 字符串数据字节长度前缀
pData() As Integer '[N] 实际数据
wEnd As Integer '[2] 结束符占2字
End Type
可见VB/VBA的String,既有前缀,也有结尾,因此也被称为安全字符串。这与C中的字符串有很大的不同,C依赖Null结束符来判断是否越界,在指针的加持下非常容易越界访问。VB/VBA中的String,无论如何使用都不存在越界的问题。
我们知道String类型其实是一个指针的指针,也即在字符串变量指针指向的内存里存储的是字符串数据指针。通过上述验证代码,可以发现声明1个非定长字符串变量时,仅仅是分配了1个指针所需内存,因此String变量的尺寸其实只有4Bytes。该指针未指向任何位置,也即此时的字符串变量其实是一个空指针。其实想想也是,你什么数据都没给,天知道要给第二个成员多大地儿。
StrPtr函数可以获取字符串数据指针,也即第二个成员的首地址。这个数据指针就存放在声明字符串变量时,分配的内存里,也就是说字符串变量的值是一个指针。这和我们通常动态监视的字符串变量,是不一样的。因为在IDE环境下,实际上给我们看的字符串,是已经转换后的。
字符串变量在被赋值之后,字符串变量里存放的确实是字符串数据指针。这说明字符串通过赋值,完成了所需内存分配、数据复制以及数据指针的填充。事实上,的确如此,背后都是OleAuto32.dll里的那帮API在管理VB/VBA的字符串,有兴趣的朋友可以查阅相关资料。所幸在VB/VBA中无需考虑这些细节,就像拖放窗口一样,都封装好了。
当重新给一个字符串变量赋值,会发生什么?不难发现,重新赋值时数据指针发生了变化。事实上,背后会调用一系列API函数,申请分配新的内存,拷贝数据,然后释放掉原占用内存。
如果在C中,上述行为都得自己实现,但在VB/VBA中你什么都不用做。是不是觉得VB/VBA中的字符串颇有几分好感呢?且慢,这个老好人,总有让你觉得讨厌的时候。
看一下变量,如果Dim strA As String后,未用过得到结果0。
Private Sub Command1_Click()
Dim strA As String
Debug.Print StrPtr(strA)
strA = "aa"
Debug.Print StrPtr(strA)
strA = "bb"
Debug.Print StrPtr(strA)
strA = "c"
Debug.Print StrPtr(strA)
strA = "cd"
Debug.Print StrPtr(strA)
strA = ""
Debug.Print StrPtr(strA)
End Sub
Private Sub Command2_Click()
Dim strA As String
Debug.Print StrPtr(strA)
End Sub
运行Command1和Command2结果:
0
388466684
388467364
388467844
388466364
0
如果"aa"和"bb"长度一致,也有可能变地址,缩短也变地址。另外,看来strA = ""没啥用,还是被分配了地址,Private后,VB6自动收回了strA的地址,在Command2中变成了0。
比较表达式 Str="",算不算好的判断呢?你看背后做了哪些事就会明白:先给""常量分配一个临时内存,拷贝数据(无),然后调用字符串比较函数进行比较,最后返回结果,再释放分配的临时内存。这里面更别提编码转换之类的。可谓是绕了一个大圈子,才换来理解上的直白。
那有没有所谓更好的方法呢?其实从BSTR的结构入手即可。若要与Str=""等效,则用LenB函数进行判断。LenB函数就是直接获取BSTR结构体的Size成员的值。也即:if LenB(Str) then来代替,则简单高效了很多。