VBA数据类型String

2020-07-28 10:04:12 浏览数 (1)

前面说到的指针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字节是如何分配的?通过网上的一些资料和自己猜测:

  1. 变量本身占用4个字节,用VarPtr可获取地址p
  2. VarPtr那个地址p保存的值,指向了字符的地址,p-4地址处保存的是长度信息,4个字节
  3. 另外2个是p-6处的00 88还是字符结尾的00 00?(看BSTR的介绍应该是结尾的00 00,可是p-6的处的00 88是做什么的?有什么用?待研究……)
代码语言:javascript复制
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直接崩溃。

这个到底是什么原因?待继续研究……

0 人点赞