VBAの終了タイミングと終了処理の記述 サンプルコード
「VBAの終了タイミングと終了処理の記述」で紹介した内容のサンプルコードをまとめています。
Private Type IID Data1 As Long Data2 As Integer Data3 As Integer Data4(0 To 7) As Byte End Type ' インスタンスのデータ Private Type MyClassData vtblPtr As LongPtr RefCount As Long #If Win64 Then ' 64ビット版かどうか Padding As Long #End If End Type ' 仮想関数テーブルのデータ Private Type IUnknownVtbl QueryInterface As LongPtr AddRef As LongPtr Release As LongPtr End Type Private Const S_OK As Long = 0 Private Const E_NOINTERFACE As Long = &H80004002 Private Const E_POINTER As Long = &H80004003 Private Declare PtrSafe Sub CopyMemory Lib "kernel32.dll" Alias "RtlMoveMemory" _ (ByRef Destination As Any, ByRef Source As Any, ByVal Length As LongPtr) Public Declare PtrSafe Function GetProcessHeap Lib "kernel32.dll" () As LongPtr Public Declare PtrSafe Function HeapAlloc Lib "kernel32.dll" _ (ByVal hHeap As LongPtr, ByVal dwFlags As Long, ByVal dwBytes As LongPtr) As LongPtr Public Declare PtrSafe Function HeapFree Lib "kernel32.dll" _ (ByVal hHeap As LongPtr, ByVal dwFlags As Long, ByVal lpMem As LongPtr) As Boolean Public Declare PtrSafe Function CoTaskMemAlloc Lib "ole32.dll" _ (ByVal cb As LongPtr) As LongPtr Public Declare PtrSafe Sub CoTaskMemFree Lib "ole32.dll" _ (ByVal pv As LongPtr) ' VBA実行中は自前インスタンスが入り続ける変数 Dim m_unk As IUnknown ' 変数に関数アドレスを代入するために用いる関数 Private Function GetAddressOf(ByVal func As LongPtr) As LongPtr GetAddressOf = func End Function ' MyClassData と IUnknownVtbl のサイズを合わせたデータを指すポインターを返す Private Function CreateInstanceMemory() As LongPtr Dim p As LongPtr, d As MyClassData, v As IUnknownVtbl ' MyClassData と IUnknownVtbl のサイズを合わせたデータを作成 p = CoTaskMemAlloc(Len(d) + Len(v)) If p <> 0 Then ' 最初の参照カウントは必ず 1 とする d.RefCount = 1 ' MyClassData の直後に IUnknownVtbl を置くので p に MyClassData のサイズを加えたアドレスをセットする d.vtblPtr = p + Len(d) ' 割り当てたメモリブロックの先頭を MyClassData のデータで埋める Call CopyMemory(ByVal p, d, Len(d)) ' 仮想関数テーブルの作成 v.QueryInterface = GetAddressOf(AddressOf My_QueryInterface) v.AddRef = GetAddressOf(AddressOf My_AddRef) v.Release = GetAddressOf(AddressOf My_Release) ' 仮想関数テーブルを p + Len(d) の部分にコピー Call CopyMemory(ByVal d.vtblPtr, v, Len(v)) End If CreateInstanceMemory = p End Function ' HRESULT STDMETHODCALLTYPE QueryInterface(THIS_ REFIID refiid, LPVOID FAR* ppv) ' 別のインターフェイスへ変換するのをリクエストするときに呼び出される関数 ' (ppv は念のため NULL チェックを入れるため ByVal で定義) Private Function My_QueryInterface(ByVal This As LongPtr, ByRef refiid As IID, ByVal ppv As LongPtr) As Long Debug.Print "My_QueryInterface" If ppv = 0 Then Debug.Print " E_POINTER" My_QueryInterface = E_POINTER Exit Function End If ' IID_IUnknown: {00000000-0000-0000-C000-000000000046} かどうか確認 If refiid.Data1 = 0 And refiid.Data2 = 0 And refiid.Data3 = 0 And _ refiid.Data4(0) = &HC0 And refiid.Data4(1) = 0 And _ refiid.Data4(2) = 0 And refiid.Data4(3) = 0 And _ refiid.Data4(4) = 0 And refiid.Data4(5) = 0 And _ refiid.Data4(6) = 0 And refiid.Data4(7) = &H46 Then ' IID_IUnknown の場合は ppv が指すポインターの先に This のアドレス(This の値)をコピー Debug.Print " IID_IUnknown" Call CopyMemory(ByVal ppv, This, Len(This)) ' さらに参照カウントを増やす Call My_AddRef(This) My_QueryInterface = S_OK Exit Function End If ' IID_IUnknown 以外はサポートしない Debug.Print " E_NOINTERFACE" My_QueryInterface = E_NOINTERFACE End Function ' ULONG STDMETHODCALLTYPE AddRef(THIS) ' 参照カウントを増やす際に呼び出される関数 Private Function My_AddRef(ByVal This As LongPtr) As Long Dim d As MyClassData ' インスタンスのデータを一旦 d にコピーし、 ' 参照カウントを増やしたら書き戻す Call CopyMemory(d, ByVal This, Len(d)) d.RefCount = d.RefCount + 1 Debug.Print "My_AddRef: new RefCount ="; d.RefCount Call CopyMemory(ByVal This, d, Len(d)) ' 戻り値は参照カウント My_AddRef = d.RefCount End Function ' ULONG STDMETHODCALLTYPE Release(THIS) ' 参照カウントを減らす際に呼び出される関数(0 になったら破棄) Private Function My_Release(ByVal This As LongPtr) As Long Dim d As MyClassData ' インスタンスのデータを一旦 d にコピーし、 ' 参照カウントを減らしたら書き戻す Call CopyMemory(d, ByVal This, Len(d)) d.RefCount = d.RefCount - 1 Debug.Print "My_Release: new RefCount ="; d.RefCount Call CopyMemory(ByVal This, d, Len(d)) ' 参照カウントが 0 になったら CoTaskMemFree で破棄する If d.RefCount = 0 Then Call CoTaskMemFree(This) ' 終了関数を呼び出す Call OnExit End If ' 戻り値は参照カウント My_Release = d.RefCount End Function ' OnExit プロシージャが呼び出されるように ' 自前 IUnknown インスタンスを変数にセットする Public Sub SetExitHandler() Dim p As LongPtr ' インスタンスを作成 p = CreateInstanceMemory() If p = 0 Then Exit Sub Dim unk As IUnknown ' unk を p が指すインスタンスに設定 Call CopyMemory(unk, p, Len(p)) ' m_unk にセット(内部で My_AddRef が呼び出される) Set m_unk = unk End Sub ' VBA終了時の処理を記述 Public Sub OnExit() Debug.Print "OnExit called" End Sub