1、VBA數組底層結構:
VBA的數組在底層是SafeArray:
'https://docs.microsoft.com/zh-cn/windows/win32/api/oaidl/ns-oaidl-safearraybound?redirectedfrom=MSDN
Type?SafeArrayBound
cElements As Long '// 該維的長度
lLbound As Long ' // 該維的數組存取的下限,一般為0
End Type
'https://docs.microsoft.com/zh-cn/windows/win32/api/oaidl/ns-oaidl-safearray?redirectedfrom=MSDN
Type SafeArray
cDims As Integer ' // 數組的維度
fFeatures As Integer '
cbElements As Long ' // 數組元素的字節大小
cLocksas As Long '
pvDataas As Long ' // 數組的數據指針
????rgsabound(0)?As?SafeArrayBound
End Type
如果要取數組的地址,需要用到API函數:
Public Declare Function VarPtrArray Lib "msvbvm60.dll" Alias "VarPtr" (ByRef Var() As Any) As Long
VarPtrArray 返回的并不是SafeArray的地址,我們可以進行測試:
Sub TestArray()
Dim Arr() As Byte
ReDim Arr(3) As Byte
Dim sa As SafeArray
CopyMemory VarPtr(sa), VarPtrArray(Arr), Len(sa)
Printf "sa.cDims = %d, sa.cbElements = %d", sa.cDims, sa.cbElements
End Sub
輸出:
sa.cDims = -27936, sa.cbElements = 3994212
從輸出可以看出,我們預計的sa.cDims應該=1和sa.cbElements=1并沒有出現,顯然VarPtrArray(Arr)獲取到的還不是Arr的地址。
像c語言這樣的語言,是有指針的,VBA數組的底層實現應該是使用了一個指針來引用SafeArray結構,而VarPtrArray(Arr)獲取到的應該是指針的地址。
Sub TestArray()
Dim Arr() As Byte
ReDim Arr(3) As Byte
Dim ptr As Long '保存[Arr指針]的地址
CopyMemory VarPtr(ptr), VarPtrArray(Arr), 4
Dim sa As SafeArray
CopyMemory VarPtr(sa), ptr, Len(sa)
Printf "sa.cDims = %d, sa.cbElements = %d", sa.cDims, sa.cbElements
End Sub
輸出
sa.cDims = 1, sa.cbElements = 1
從輸出可以看出,預計的sa.cDims應該=1和sa.cbElements=1出現了。
進一步測試,從pvDataas 中提取數據,看看獲取的數據是否能正確:
Sub TestArray2()
Dim Arr() As Byte
ReDim Arr(3) As Byte
Dim ptr As Long '保存[Arr指針]的地址
Arr(0) = &H88
Arr(1) = &H21
Arr(2) = &H27
Arr(3) = &H99
CopyMemory VarPtr(ptr), VarPtrArray(Arr), 4
Dim sa As SafeArray
CopyMemory VarPtr(sa), ptr, Len(sa)
Dim b(3) As Byte
Dim i As Long
For i = 0 To 3
CopyMemory VarPtr(b(i)), sa.pvDataas + sa.cbElements * i, sa.cbElements
Printf "b(%d) = 0x%x ", i, b(i)
Next
End Sub
輸出:
b(0) = 0x88
b(1) = 0x21
b(2) = 0x27
b(3) = 0x99
完全正確。
2、改變SafeArray的pvDataas地址會有什么情況:
既然知道了數組的內存結構,那我們就嘗試把pvDataas改變看看會怎么樣:
Sub TestArray2()
Dim Arr() As Byte
ReDim Arr(0) As Byte
Dim ptr As Long '保存[Arr指針]的地址
CopyMemory VarPtr(ptr), VarPtrArray(Arr), 4
Dim lValue As Long
lValue = &HABCDEF99
Dim plValue As Long
plValue = VarPtr(lValue)
'修改pvDataas指向lValue
CopyMemory ptr + 12, VarPtr(plValue), 4
'修改Arr的SafeArrayBound為4
Dim cElements As Long
cElements = 4
CopyMemory ptr + 16, VarPtr(cElements), 4
Dim sa As SafeArray
CopyMemory VarPtr(sa), ptr, Len(sa)
Printf "sa.pvDataas = 0x%x, plValue = 0x%x", sa.pvDataas, plValue
Dim i As Long
For i = 0 To 3
Printf "Arr(%d) = 0x%x ", i, Arr(i)
Next
End Sub
輸出:
sa.pvDataas = 0x36ecbc, plValue = 0x36ecbc
Arr(0) = 0x99
Arr(1) = 0xef
Arr(2) = 0xcd
Arr(3)?=?0xab?
定義了一個容量為1的byte數組,但是我把pvDataas的值修改成了1個Long變量的地址,同時也改變了他的SafeArrayBound為4,從輸出可以看到,這個數組已經變成了一個容量為4的byte數組。
但是End Sub后,我的電腦測試Excel崩潰,這個和前面講到的String類型里的情況差不多,到底是什么原因?
3、ReDim Preserve做了什么
我們經常會用ReDim Preserve來改變數組的容量,當然一般都是擴大。擴大的話要更多的內存空間來保存數據,所以應該是要重新分配內存,測試:
Sub TestArray()
Dim Arr() As Byte
ReDim Arr(3) As Byte
Dim ptr As Long '保存[Arr指針]的地址
Dim sa As SafeArray
CopyMemory VarPtr(ptr), VarPtrArray(Arr), 4
CopyMemory VarPtr(sa), ptr, Len(sa)
Printf "ptr = 0x%x, sa.pvDataas= 0x%x, sa.cDims = %d, sa.cbElements = %d, sa.rgsabound(0).cElements = %d", ptr, sa.pvDataas, sa.cDims, sa.cbElements, sa.rgsabound(0).cElements
ReDim Preserve Arr(4) As Byte
CopyMemory VarPtr(ptr), VarPtrArray(Arr), 4
CopyMemory VarPtr(sa), ptr, Len(sa)
Printf "ptr = 0x%x, sa.pvDataas= 0x%x, sa.cDims = %d, sa.cbElements = %d, sa.rgsabound(0).cElements = %d", ptr, sa.pvDataas, sa.cDims, sa.cbElements, sa.rgsabound(0).cElements
End Sub
輸出:
ptr = 0x16597108, sa.pvDataas= 0x167c3a40, sa.cDims = 1, sa.cbElements = 1, sa.rgsabound(0).cElements = 4
ptr = 0x16597108, sa.pvDataas= 0x167c3790, sa.cDims = 1, sa.cbElements = 1, sa.rgsabound(0).cElements = 5
pvDataas已經改變,重新分配了內存空間。
但是,如果是減小呢?
我本來猜測只要修改sa.rgsabound(0).cElements的值就可以達到減小容量的目的,可測試卻同樣重新分配了內存:
Sub TestArray()
Dim Arr() As Byte
ReDim Arr(3) As Byte
Dim ptr As Long '保存[Arr指針]的地址
Dim sa As SafeArray
CopyMemory VarPtr(ptr), VarPtrArray(Arr), 4
CopyMemory VarPtr(sa), ptr, Len(sa)
Printf "ptr = 0x%x, sa.pvDataas= 0x%x, sa.cDims = %d, sa.cbElements = %d, sa.rgsabound(0).cElements = %d", ptr, sa.pvDataas, sa.cDims, sa.cbElements, sa.rgsabound(0).cElements
ReDim Preserve Arr(1) As Byte
CopyMemory VarPtr(ptr), VarPtrArray(Arr), 4
CopyMemory VarPtr(sa), ptr, Len(sa)
Printf "ptr = 0x%x, sa.pvDataas= 0x%x, sa.cDims = %d, sa.cbElements = %d, sa.rgsabound(0).cElements = %d", ptr, sa.pvDataas, sa.cDims, sa.cbElements, sa.rgsabound(0).cElements
End Sub
輸出:
ptr = 0x167d5068, sa.pvDataas= 0x167c3a50, sa.cDims = 1, sa.cbElements = 1, sa.rgsabound(0).cElements = 4
ptr = 0x167d5068, sa.pvDataas= 0x167c37f0, sa.cDims = 1, sa.cbElements = 1, sa.rgsabound(0).cElements = 2
本文使用 文章同步助手 同步? ,關注xyjvba,查看更多