t-hom’s diary

主にVBAネタを扱っているブログ…とも言えなくなってきたこの頃。

VBA 関数呼び出しの際、Variant型の仮引数に別型の実引数を参照渡しするとメモリアドレスが変わってしまう事象

以下の記事で、ByRef x As VariantにLongやStringなどの別の型を渡すとメモリアドレスが変わるとの紹介が。
mmyymmdd.hatenablog.com

前回私が書いた記事もそこがネックだった。
thom.hateblo.jp

さて、この事象について、色々試していたら原因が見えてきたので、仮説であるが紹介。

まず次のようなコードを作成した。

Sub test()
    Dim varArg As Variant: varArg = Array(1, 2, 3)
    Dim intArg As Integer: intArg = 1
    Dim lngArg As Long: lngArg = 60000
    Dim strArg As String: strArg = "Hello"
    Dim objArg As Object: Set objArg = Sheets(1)
    
    Debug.Print "-----------初期アドレス------------"
    Debug.Print "varArg:"; VarPtr(varArg)
    Debug.Print "intArg:"; VarPtr(intArg)
    Debug.Print "lngArg:"; VarPtr(lngArg)
    Debug.Print "strArg:"; VarPtr(strArg)
    Debug.Print "objArg:"; VarPtr(objArg)
    
    Call ShowPtr(varArg, intArg, lngArg, strArg, objArg)
End Sub

Sub ShowPtr(ByRef varArg As Variant, ByRef intArg As Integer, ByRef lngArg As Long _
                    , ByRef strArg As String, ByRef objArg As Object)
    Debug.Print "-----------型一致参照------------"
    Debug.Print "varArg:"; VarPtr(varArg)
    Debug.Print "intArg:"; VarPtr(intArg)
    Debug.Print "lngArg:"; VarPtr(lngArg)
    Debug.Print "strArg:"; VarPtr(strArg)
    Debug.Print "objArg:"; VarPtr(objArg)
    
    Call ShowPtr2(varArg, intArg, lngArg, strArg, objArg)
End Sub

Sub ShowPtr2(ByRef varArg As Variant, ByRef intArg As Variant, ByRef lngArg As Variant _
                    , ByRef strArg As Variant, ByRef objArg As Variant)
    Debug.Print "-----------Variant参照------------"
    Debug.Print "varArg:"; VarPtr(varArg)
    Debug.Print "intArg:"; VarPtr(intArg)
    Debug.Print "lngArg:"; VarPtr(lngArg)
    Debug.Print "strArg:"; VarPtr(strArg)
    Debug.Print "objArg:"; VarPtr(objArg)
End Sub

私の環境では実行結果はこうなった。

-----------初期アドレス------------
varArg: 1373348 
intArg: 1373278 
lngArg: 1373272 
strArg: 1373268 
objArg: 1373264 
-----------型一致参照------------
varArg: 1373348 
intArg: 1373278 
lngArg: 1373272 
strArg: 1373268 
objArg: 1373264 
-----------Variant参照------------
varArg: 1373348 
intArg: 1373068 
lngArg: 1373052 
strArg: 1373036 
objArg: 1373020 

ShowPtrプロシージャでのByRefは、渡すデータと型を一致させており、参照アドレスも変わらない。
ところがShowPtr2プロシージャでVariantで参照させた場合は参照先のアドレスが変わってしまう。

ローカルウインドウで確認すると、ShowPtrでは型がそのままだが、ShowPtr2ではVariant/Integerなどに変わっていた。

たとえばInteger型は2バイト、Long型は4バイトであるが、Variant型はさらにサイズが大きくなる。
ということは、最初に用意した2バイトないし4バイトのアドレスには収まりきらず、必然的に別の領域を確保し直しているようだ。
ただし、元の領域へのポインタを保持しており、内容を書き換えた際に元のアドレスにも反映させることで参照を実現しているものと思われる。

先ほどのコードをシンプルにして実験してみた。

Sub test()
    Dim intArg As Integer: intArg = 1
    Debug.Print intArg
    Debug.Print "intArg:"; VarPtr(intArg)
    Call ShowPtr2(intArg)
    Debug.Print intArg
    Debug.Print "intArg:"; VarPtr(intArg)
End Sub

Sub ShowPtr2(ByRef intArg As Variant)
    Debug.Print "lngArg:"; VarPtr(lngArg)
    intArg = 2
End Sub

結果はこのように表示された。

 1
intArg: 1373362 
lngArg: 1373176 
 2 
intArg: 1373362 

つまり、ShowPtr2でVarPtr(IntArg)としたときは、Variant変数自体のアドレス1373176が表示されるが、Variant変数は内部で137362へのポインタを保持しているということだろう。

これで一応、なぜByRefなのにアドレスが変わるかの答えになったかと思う。

8/9追記

Variantのサイズについて、補足ツイートいただいた。

このコードで調べられるらしい。

Type st
    v As Variant
End Type
Sub hoge()
    Dim st As st
    MsgBox Len(st)
End Sub

さらにmmYYmmddさんによると、64bitと32bitでVariantのサイズが変わるとのこと。

当ブログは、amazon.co.jpを宣伝しリンクすることによってサイトが紹介料を獲得できる手段を提供することを目的に設定されたアフィリエイト宣伝プログラムである、 Amazonアソシエイト・プログラムの参加者です。