前回検証記事で、Win32apiを使った変数のアドレス交換を紹介した。
thom.hateblo.jp
ただし、この方法には受け渡しする変数がVariant型でなければならないという制約がある。
そこで、Variant以外の変数でも対応できるように作ろうとしたのがこちら。。
Declare Function RtlMoveMemory Lib "kernel32" _ (ByVal Destination As Long, ByVal Source As Long, ByVal Length As Long) As Long Sub MemSwap(ByVal arg1Ptr As Long, ByVal arg2Ptr As Long) Dim tmp As Variant Dim t As Long: t = VarPtr(tmp) RtlMoveMemory t, arg1Ptr, 4 'メモリ番地を示すLong型は4バイト RtlMoveMemory arg1Ptr, arg2Ptr, 4 RtlMoveMemory arg2Ptr, t, 4 End Sub
呼び出しは、以下のようにVarPtr関数で最初からアドレスを渡す。
Sub TestStringSwap() Dim a As String, b As String a = String(10 ^ 8, "★") b = String(10 ^ 8, "☆") For i = 1 To 999 Call MemSwap(VarPtr(a), VarPtr(b)) Next Debug.Print Left(a, 1) End Sub
こちらは一応動作した。
ポイントは、最初からアドレスをByVal指定でMemSwapへ渡していることである。
MemSwapからAPI関数へもByVal指定である。ByRefにすると参照の参照みたいなおかしな事になるためである。
次にオブジェクトでもやってみる。
Sub TestObjSwap() Dim a As Worksheet, b As Worksheet Set a = Sheets(1) Set b = Sheets(2) For i = 1 To 999 Call MemSwap(VarPtr(a), VarPtr(b)) Next Debug.Print a.Name End Sub
これも問題なく、aがSheet2に書き換わる。
さて、これでうまくいったかに思えたが、ここから先にワナが仕掛けられていた。
まず、TestStringSwap、TestObjSwapともに、F8キーによるステップ実行で、Excelがクラッシュする。
For文で999回も繰り返して問題ないのに、F8だとRtlMoveMemory内の処理で一発アウト。
DoEventsを挟んでみたり、色々試してみたけれど、どうもステップ実行したときだけがNGらしい。
不可解である。
まだ問題はある。
固有型に対応したは良いが、今度はVariant型のときにSwapされなくなるのだ。
それでもString型であれば、呼び出し時にVarPtrではなくStrPtrを使うことで、変数がVariant型でも対応できるようになる。
なぜかクラッシュもしなくなる。
ただ、TestObjSwapのようにオブジェクト型の場合はお手上げである。
ObjPtrでもうまくいかない。
さらに、固有型でも配列の場合はうまくいかない。
もう少し何とかなりそう(なって欲しい)のだが、行き詰まってしまった。
従って、今のところ用途は限定される。
ゴールはそこに見えているのに、道は行き止まり。そもそもたどり着ける道があるのかも不明。
まったくVBAってのは世話が焼ける言語である。