Copy an array reference in VBA

匿名 (未验证) 提交于 2019-12-03 02:31:01

问题:

Is there any way to copy an array reference in VBA (or VB6)?

In VBA, arrays are value types. Assigning one array variable to another copies the entire array. I want to get two array variables to point to the same array. Is there any way to accomplish this, perhaps using some API memory functions and/or the VarPtr function, which does in fact return the address of a variable in VBA?

Dim arr1(), arr2(), ref1 As LongPtr arr1 = Array("A", "B", "C")  ' Now I want to make arr2 refer to the same array object as arr1 ' If this was C#, simply assign, since in .NET arrays are reference types: arr2 = arr1  ' ...Or if arrays were COM objects: Set arr2 = arr1  ' VarPtr lets me get the address of arr1 like this: ref1 = VarPtr(arr1)  ' ... But I don't know of a way to *set* address of arr2. 

Incidentally, it is possible to get multiple references to the same array by passing the same array variable ByRef to multiple parameters of a method:

Sub DuplicateRefs(ByRef Arr1() As String, ByRef Arr2() As String)     Arr2(0) = "Hello"     Debug.Print Arr1(0) End Sub  Dim arrSource(2) As String arrSource(0) = "Blah"  ' This will print 'Hello', because inside DuplicateRefs, both variables ' point to the same array. That is, VarPtr(Arr1) == VarPtr(Arr2) Call DuplicateRefs(arrSource, arrSource) 

But this still does not allow one to simply manufacture a new reference in the same scope as an existing one.

回答1:

Yes, you can, if both variables are of type Variant.

Here's why: The Variant type is itself a wrapper. The actual bit content of a Variant is 16 bytes. The first byte indicates the actual data type currently stored. The value corresponds exactly the VbVarType enum. I.e if the Variant is currently holding a Long value, the first byte will be 0x03, the value of vbLong. The second byte contains some bit flags. For exampe, if the variant contains an array, the bit at 0x20 in this byte will be set.

The use of the remaining 14 bytes depends on the data type being stored. For any array type, it contains the address of the array.

That means if you directly overwrite the value of one variant using RtlMoveMemory you have in effect overwritten the reference to an array. This does in fact work!

There's one caveat: When an array variable goes out of scope, the VB runtime will reclaim the memory that the actual array elements contained. When you have manually duplicated an array reference via the Variant CopyMemory technique I've just described, the result is that the runtime will try to reclaim that same memory twice when both variants go out of scope, and the program will crash. To avoid this, you need to manually "erase" all but one of the references by overwriting the variant again, such as with 0s, before the variables go out of scope.

Example 1: This works, but will crash once both variables go out of scope (when the sub exits)

Private Declare PtrSafe Sub CopyMemory Lib "kernel32" _     Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)  Sub CopyArrayRef_Bad()     Dim v1 As Variant, v2 As Variant     v1 = Array(1, 2, 3)     CopyMemory v2, v1, 16      ' Proof:     v2(1) = "Hello"     Debug.Print Join(v1, ", ")      ' ... and now the program will crash End Sub 

Example 2: With careful cleanup, you can get away with it!

Private Declare PtrSafe Sub CopyMemory Lib "kernel32" _     Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)  Private Declare PtrSafe Sub FillMemory Lib "kernel32" _     Alias "RtlFillMemory" (Destination As Any, ByVal Length As Long, ByVal Fill As Byte)  Sub CopyArrayRef_Good()     Dim v1 As Variant, v2 As Variant     v1 = Array(1, 2, 3)     CopyMemory v2, v1, 16      ' Proof:     v2(1) = "Hello"     Debug.Print Join(v1, ", ")      ' Clean up:     FillMemory v2, 16, 0      ' All good! End Sub 


回答2:

What about this solution...

Public Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _                    (Destination As Any, Source As Any, ByVal Length As Long)  Public Sub TRIAL() Dim myValueType As Integer Dim mySecondValueType As Integer Dim memPTR As Long  myValueType = 67 memPTR = VarPtr(mySecondValueType) CopyMemory ByVal memPTR, myValueType, 2 Debug.Print mySecondValueType End Sub 

The concept came from a CodeProject article here



回答3:

And what about to create a wraper? Like this class module 'MyArray' (simplified example):

Private m_myArray() As Variant  Public Sub Add(ByVal items As Variant)     m_myArray = items End Sub  Public Sub Update(ByVal newItem As String, ByVal index As Integer)     m_myArray(index) = newItem End Sub  Public Function Item(ByVal index As Integer) As String     Item = m_myArray(index) End Function 

Then in standard module:

Sub test()     Dim arr1 As MyArray     Dim arr2 As MyArray      Set arr1 = New MyArray     arr1.Add items:=Array("A", "B", "C")      Set arr2 = arr1      arr1.Update "A1", 0      Debug.Print arr1.Item(0)     Debug.Print arr2.Item(0) End Sub 

Does this help?



标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!