t-hom’s diary

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

VBA Win32apiを使った変数のアドレス交換(固有型対応版)

前回検証記事で、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ってのは世話が焼ける言語である。

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