2013-05-01 24 views
9

VBA(またはVB6)で配列参照をコピーする方法はありますか?VBAで配列参照をコピーする

VBAでは、配列は値型です。 1つの配列変数を別の配列変数に割り当てると、配列全体がコピーされます。私は2つの配列変数を同じ配列を指すようにしたい。おそらくいくつかのAPIメモリ関数や関数を使用して、これを達成する方法はありますか?実際には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. 

ちなみに、方法の複数のパラメータに同じ配列変数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) 

しかし、これはまだ、単純に1を許可していません。既存の基準と同じ範囲で新しい基準を作成する。

+0

私はあなたの質問への答えを知っていませんが、私は解決策に非常に興味があります...あなたの配列を保持シングルトンクラスを作成し、クラスを経由して参照を返すことはできますか? – Marshall

+0

関数またはプロパティから配列を返すことは、値によっても動作し、配列の新しいコピーを返します。これは実際に私が取り組もうとしている真の問題です。 –

答えて

12

両方の変数がVariant型の場合は、とすることができます。

理由は次のとおりです。バリアント型はそれ自体がラッパーです。 Variantの実際のビット内容は16バイトです。最初のバイトは、現在格納されている実際のデータタイプを示します。値はVbVarType列挙型に正確に対応します。つまり、Variantが現在Long値を保持している場合、最初のバイトは0x03vbLongの値になります。第2のバイトはいくつかのビットフラグを含む。例の場合、バリアントに配列が含まれている場合、このバイトの0x20のビットが設定されます。

残りの14バイトの使用は、格納されるデータタイプによって異なります。どのアレイタイプでも、配列のアドレスが含まれています。あなたが直接あなたが実際には配列へ参照を上書きしているRtlMoveMemoryを使用して一つの変形のを上書きする場合は意味

。これは実際に動作します!

警告:配列変数が有効範​​囲外になると、VBランタイムは実際の配列要素に含まれていたメモリを再利用します。今まで説明したVariant CopyMemoryテクニックを使用して配列参照を手動で複製した場合、その結果、両方のバリアントが範囲外になり、プログラムがクラッシュすると、ランタイムは同じメモリを2回再利用しようとします。これを避けるには、変数を有効範囲外にする前に、変数を再度上書きすることによって、参照の1つを除いて手動で「消去」する必要があります(0など)。

例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 

例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 
+3

+1同様の行に沿って、バリアントでない配列は、SAFEARRAY構造体であり、さまざまなメンバとデータへのポインタを含みます。 (vbランタイムvarptrarray()exportは、vba配列SAFEARRAYヘッダへのポインタを返します) –

+2

@AlexK。ブリリアント!私は[Automation array manipulation API](http://msdn.microsoft.com/en-us/library/windows/desktop/ms221145(v = vs.85).aspx)を認識していませんでした。私は、VB [A]ランタイムがこのAPIを使って配列を実装していると推測しているので、いつも探しているVBランタイムの内部構造が突然表示されます。 –

1

何このソリューションについて...

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 

コンセプトは、CodeProjectの記事からhere

+0

それでも、参照ではなく値がコピーされます。参照が正常にコピーされた場合、 'mySecondValueType = 42'を設定すると' myValueType'の値も変更されます。 –

+0

私はそれを動作させる方法を見つけました。私の答えを見てください。 –

0

を来て、wraperを作成することについて何?このクラスモジュール '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 

そして、標準モジュールで:

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 

はこのヘルプをしていますか?

+0

これは良い考えですが、組み込みの 'Collection'オブジェクトはすでにこれを提供しています。そして、あなたが失うのは、配列に特有の深く埋め込まれた言語機能です。 –

関連する問題