在 VBA 中复制数组引用

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16323776/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-11 20:55:21  来源:igfitidea点击:

Copy an array reference in VBA

vbapointersreference

提问by Joshua Honig

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

有没有办法在 VBA(或 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 VarPtrfunction, which does in fact return the address of a variable in VBA?

在 VBA 中,数组是值类型。将一个数组变量分配给另一个会复制整个数组。我想让两个数组变量指向同一个数组。有什么方法可以做到这一点,也许使用一些 API 内存函数和/或VarPtr函数,它实际上返回 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 ByRefto multiple parameters of a method:

顺便说一句,可以通过将相同的数组变量传递ByRef给方法的多个参数来获得对同一数组的多个引用:

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.

但这仍然不允许人们在与现有参考相同的范围内简单地制造新参考。

回答by Joshua Honig

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

是的,如果两个变量都是 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 0x20in this byte will be set.

原因如下: Variant 类型本身就是一个包装器。Variant 的实际位内容是 16 个字节。第一个字节表示当前存储的实际数据类型。该值完全对应于 VbVarType 枚举。即,如果 Variant 当前持有一个 Long 值,则第一个字节将是0x03, 的值vbLong。第二个字节包含一些位标志。例如,如果变量包含一个数组,则0x20该字节中的位将被设置。

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.

剩余 14 个字节的使用取决于存储的数据类型。对于任何数组类型,它都包含数组地址

That means if you directly overwrite the valueof one variant using RtlMoveMemoryyou have in effect overwritten the referenceto an array. This does in fact work!

这意味着如果您使用直接覆盖一个变体的RtlMoveMemory您实际上已经覆盖了数组的引用。这确实有效!

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.

有一个警告:当数组变量超出范围时,VB 运行时将回收实际数组元素包含的内存。当您通过我刚刚描述的 Variant CopyMemory 技术手动复制数组引用时,结果是当两个变体都超出范围时,运行时将尝试回收相同的内存两次,并且程序将崩溃。为避免这种情况,您需要在变量超出范围之前通过再次覆盖变体(例如使用 0)手动“擦除”除一个引用之外的所有引用。

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

示例 1:这有效,但是一旦两个变量都超出范围(子退出时)就会崩溃

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!

示例 2:通过仔细清理,您可以侥幸逃脱!

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

回答by Marshall

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

这个概念来自此处的 CodeProject 文章

回答by dee

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

那么如何创建一个包装器呢?像这个类模块“MyArray”(简化示例):

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?

这有帮助吗?