以下の記事で、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のサイズについて、補足ツイートいただいた。
http://t.co/M4MS6TWtYC
— kumattiザウルス (@kumatti1) 2015年8月8日
>Variant型はさらにサイズが大きくなる。
Type st
v As Variant
End Type
Sub hoge()
Dim st As st
MsgBox Len(st)
End Sub
このコードで調べられるらしい。
Type st v As Variant End Type Sub hoge() Dim st As st MsgBox Len(st) End Sub
さらにmmYYmmddさんによると、64bitと32bitでVariantのサイズが変わるとのこと。