前面说到的指针Pointer,其实主要是说了取地址函数。得到了变量的地址,只能赋值给Long类型,并没有指针的作用,无法根据这个记录了变量地址的东西来操作变量。
但能得到变量地址,就可以去查看变量的内存布局,从而深入了解类型。
VBA里数值类型Integer、Long那些,内存布局比较简单,无非是1个、2个、4个连续的字节。
而String类型是有点不一样的,有了指针Pointer讲到的取指针函数,我们就可以对String深入了解一下。
深入了解数据类型有什么用?
比如你想用C语言写dll给VBA调用,C语言可是没有String类型的,C的函数该如何去用?这就涉及到了数据类型的内存结构,深入了解才能够使用好。
微软官方文档data-type-summary
代码语言:javascript复制String (variable-length) 10 bytes string length 0 to approximately 2 billion
String (fixed-length) Length of string 1 to approximately 65,400
1、String变长内存结构
10字节是如何分配的?通过网上的一些资料和自己猜测:
- 变量本身占用4个字节,用VarPtr可获取地址p
- VarPtr那个地址p保存的值,指向了字符的地址,p-4地址处保存的是长度信息,4个字节
- 另外2个是p-6处的00 88还是字符结尾的00 00?(看BSTR的介绍应该是结尾的00 00,可是p-6的处的00 88是做什么的?有什么用?待研究……)
Sub TestString()
Dim str As String
str = "a"
'10 官方定义str长度
'4 变量占用
'2 字符长度
'2 字符后面00 00
Dim b(10 - 4 2 2 - 1) As Byte
CopyMemory VarPtr(b(0)), StrPtr(str) - 6, 10
printf "b = % x", b
End Sub
输出:
b = 00 88 02 00 00 00 61 00 00 00
我们能看出,其实从StrPtr开始的位置,字符就是UTF-16的byte,所以VBA里面,byte数组和String的转换很简单,直接赋值就可以。
随便定义1个byte数组,是可以赋值给string的,但是要注意如果需要输出,要保证byte数组符合UTF-16的编码,要不然就可能出现一些乱码了。
代码语言:javascript复制Sub TestByte2Str()
Dim b(11) As Byte
Dim str As String
b(0) = 65
b(4) = 23
b(10) = 67
str = b
Debug.Print str
End Sub
输出:
A C
2、String定长
这个涉及到了字符串缓冲区,没有弄明白,不过用的也少!
3、与其他语言的dll交互
经常接触到的是windows的API调用,在VBA里先声明1个String,并且赋值一个足够的长度,调用之后再根据返回长度来取出需要的字符串。
这个我理解其实就是让API函数往StrPtr那个地址里写入Byte数组。
4、是否可以构建1个String变长的内存区域
即然知道了VBA String类型的内存结构,我就想如果自己用C语言做一个dll,函数传出StrPtr需要的那个地址,赋值给1个str的VarPtr那个地址,是不是程序退出的时候VBA的垃圾回收能释放那个内存?这样就不需要先去初始一个足够的长度的String了。
C代码
代码语言:javascript复制__declspec(dllexport) char* __stdcall RetStrPtr()
{
char* ch = (char*)malloc(10);
ch[0] = 0x00;
ch[1] = 0x88;
ch[2] = 0x02;
ch[3] = 0x00;
ch[4] = 0x00;
ch[5] = 0x00;
ch[6] = 0x61;
ch[7] = 0x00;
ch[8] = 0x00;
ch[9] = 0x00;
return ch;
}
编译:
cl -c 1.c 1.def
link -DLL -out:cdlltest.dll 1.obj
VBA调用
代码语言:javascript复制Public Declare Function RetStrPtr Lib "cdlltest" Alias "_RetStrPtr@0" () As Long
Sub TestCRet()
Dim hdll As Long
hdll = LoadLibrary(ThisWorkbook.Path & "cdllcdlltest.dll")
printf "hdll = 0x%x", hdll
Dim str As String
Dim lStrPtr As Long
lStrPtr = RetStrPtr() 6
CopyMemory VarPtr(str), VarPtr(lStrPtr), 4
printf "str = %s", str
Stop
FreeLibrary hdll
End Sub
输出:
hdll = 0x69b50000
str = a
程序能成功输出str = a,但是执行End Sub后,Excel直接崩溃了。难道是程序结束后,VBA的垃圾回收机制回收这块内存的时候出了问题?
于是尝试在VBA内部用byte数组构建后赋值到VarPtr,结果一样。
代码语言:javascript复制Sub TestString()
Dim str As String
Dim b(9) As Byte
b(1) = &H88
b(2) = &H2
b(6) = &H61
Dim lStrPtr As Long
lStrPtr = VarPtr(b(0)) 6
Printf "强制赋值VarPtr前,StrPtr(str) = 0x%x", StrPtr(str)
' 6 StrPtr指向的字符开始的位置,不包含前面00 88和长度信息4个
CopyMemory VarPtr(str), VarPtr(lStrPtr), 4
Printf "强制赋值VarPtr后,StrPtr(str) = 0x%x", StrPtr(str)
Printf "str = %s", str
Stop
End Sub
输出:
强制赋值VarPtr前,StrPtr(str) = 0x0
强制赋值VarPtr后,StrPtr(str) = 0x1a207876
str = a
Stop之前都正常,但是执行End Sub后,Excel直接崩溃。
这个到底是什么原因?待继续研究……