Programming Field

VBからCLR(.NET)を利用する その1[準備編]

※ このページにおける「.NET」は「.NET Framework」を指します。旧「.NET Core」である「.NET」の利用については「VBから.NET(旧.NET Core)を利用してみる」をご覧ください。

本サイトでは「VB で機械語を実行する」「VBAの終了タイミングと終了処理の記述」など、VBAを含むVB6.0系でVBの枠を超えた機能の利用や実装をするための方法を紹介していましたが、ここではCLR(Common Language Runtime: 共通言語ランタイム)、あるいは.NETのライブラリを利用するための方法を紹介しています。VBやVBAはCOMベースでプログラムが実行されるため、COM相互運用をサポートした.NETアセンブリであれば直接利用することができますが、System.Xmlなどサポートしていないアセンブリは利用することができません。しかしここで紹介する方法を用いれば、ほぼすべての.NETアセンブリを利用可能になります。

※ 利用できないアセンブリがあるかどうかは未確認です。

VBやVBAでCLR(.NET)を利用するのは、パフォーマンス上ではCLR(.NET)上で動作するプログラムに対して明らかに劣りますが、特にExcel VBAでワークシート上のデータと特定の.NETライブラリとを絡めたい、などといった場合において有用であると考えられます。

なお、今回は以下の3回に分けてその方法を紹介します。

  1. 準備編 - CLR実行環境の構築
  2. 呼び出し編 - アセンブリのロードとクラスインスタンスの生成、およびメソッド(クラスインスタンス/スタティック)の利用
  3. 発展編 - Delegateの利用など

まずはCLRそのものをVB上で利用できるようにする処理を紹介します。

※ 以降、VBのコードは64ビット対応のある「VBA 7.0」を前提に記述します。VBA 7.0はOffice 2010以降のVBAマクロで使用されます。(「64ビット版VBA用の64ビット版COMライブラリを作る」でも少し触れていますので必要に応じて参照してください。)

CLRの初期化

WindowsにおいてCLRは、C/C++などで記述・生成されたネイティブコードからCLR上のドメインやアセンブリ経由で機能を利用できるようするために、いくつかのネイティブ用関数やインターフェイスなどを提供しています。

ランタイムのロードは次の手順で行います。

  1. ICLRMetaHostインスタンスを取得
  2. CLRバージョンを指定してICLRRuntimeInfoインスタンスを取得
  3. ランタイムのホストインスタンスを取得

「ランタイムホスト」が利用できるようになると晴れてCLRの機能が利用できるようになるため、まずはこの手順を進めます。

ICLRMetaHostインスタンスを取得

まずはICLRMetaHostインターフェイスのインスタンスを取得します。このインターフェイスは、システムにインストールされているランタイムを列挙し、指定バージョンに対応するランタイムの操作を行うためのインスタンスを取得する機能を提供しています。

ICLRMetaHostのインスタンスは、.NET Framework バージョン4以上であれば、「mscoree.dll」がエクスポートしている関数「CLRCreateInstance」経由で取得します。

※ バージョン4未満の場合は「補足: 旧バージョンの場合」をご覧ください。

[C/C++]

// (参考のため紹介: 宣言は metahost.h に記述されています)
STDAPI CLRCreateInstance(REFCLSID clsid, REFIID riid, LPVOID* ppInterface);
[VBA 7.0]

Declare PtrSafe Function CLRCreateInstance Lib "mscoree.dll" _
    (ByRef rclsid As Any, ByRef riid As Any, ByRef ppvInterface As IUnknown) As Long

※ 簡単のためGUID系の型を「Any」で定義し、「g(0 To 3) As Long」などの配列も指定できるようにしています。また、ppInterfaceは「IUnknown*」(およびその派生)以外のデータが出力されることは無いため、明示させています。

ICLRMetaHostのインスタンスを得るには、この関数にCLSIDとして「{9280188D-0E8E-4867-B30C-7FA83884E8DE}」(CLSID_CLRMetaHost)、IIDに「{D332DB9E-B9B3-4125-8207-A14884F53216}」(IID_ICLRMetaHost)を指定します。

[VBA 7.0]

    Dim g(0 To 3) As Long
    Dim g2(0 To 3) As Long
    Dim pMetaHost As IUnknown, hr As Long
    Call ParseGUID(g, "{9280188D-0E8E-4867-B30C-7FA83884E8DE}") ' CLSID_CLRMetaHost
    Call ParseGUID(g2, "{D332DB9E-B9B3-4125-8207-A14884F53216}") ' IID_ICLRMetaHost
    hr = CLRCreateInstance(g(0), g2(0), pMetaHost)
    ' (HRESULTは Err.Raise にそのまま渡すことで対応するエラーを発生させることが可能)
    If hr < 0 Then Call Err.Raise(hr)

ICLRMetaHostのメソッドを利用するために…

次はICLRMetaHostにある「GetRuntime」メソッドを呼び出してインスタンスを取得したいところですが、「参照設定」で追加できる「Common Language Runtime Execution Engine」(mscoree.dll 用のタイプライブラリ)には「ICLRMetaHost」(あるいは「CLRMetaHost」)が含まれていません。ICLRMetaHostはIUnknownから直接派生したインターフェイスであるため、型定義の無い状況でメソッドを呼び出すには仮想関数テーブルをうまく用いるしかありません。

仮想関数テーブルを用いたメソッド呼び出しは「VB で機械語を実行する」のように機械語を記述して無理やり呼び出すこともできなくはないですが、より容易に呼び出す方法として oleaut32.dll にある「DispCallFunc」関数を用いることができます。ここではVB向けに以下のように定義します。

[VBA 7.0]

Declare PtrSafe Function DispCallFunc Lib "oleaut32.dll" _
    (ByVal pvInstance As LongPtr, _
    ByVal oVft As LongPtr, _
    ByVal cc As Long, _
    ByVal vtReturn As Integer, _
    ByVal cActuals As Long, _
    ByRef prgvt As Integer, _
    ByRef prgpvarg As LongPtr, _
    ByRef pvargResult As Variant) As Long

[2019/04/25 追記] DispCallFunc の定義が誤っていたため修正しました。(GitHub Issue #1 での指摘より)

IUnknown ベースのインスタンスに対して仮想関数を呼び出したい場合は、「pvInstance」にそのインスタンスのポインター(ObjPtrで取得)、「oVft」に「仮想関数テーブルのインデックス番号にポインターのサイズをかけたもの(= テーブルにおけるバイトオフセット)」を指定し、残りの引数にそのメソッドの定義に基づいた情報とパラメーターを入れていきます。

※ 仮想関数テーブルのインデックス値は、C/C++向けのヘッダーファイル等で調べる必要があります。

この関数を直接利用するのは少々面倒であるため、以下のようなラッパー関数「VBCallAbsoluteObject」を定義してそれを用いることとします。

[VBA 7.0]

' Object に対して IndexForVftable に対応するメソッドを呼び出します。
' - RetTypeにはメソッド本来の戻り値の型を VbVarType (または VT_*)で指定します。
' - Arguments にはメソッドの引数を指定します。
'   指定時の型がそのまま呼び出し時の変換のヒントとして用いられるため、
'   必要に応じて CLng などの明示的な型変換を行ってください。
Public Function VBCallAbsoluteObject(ByVal Object As IUnknown, _
    ByVal IndexForVftable As Integer, _
    ByVal RetType As VbVarType, _
    ParamArray Arguments() As Variant) As Variant
    ' (具体的な実装はページの最後をご覧ください。)
End Function

CLRバージョンを指定してICLRRuntimeInfoインスタンスを取得

前述の機能によりICLRMetaHostのメソッドを自由に呼び出せるようになったため、本題に戻って「ICLRRuntimeInfo」のインスタンスを取得します。ICLRMetaHost::GetRuntime にはCLRのバージョンを引数として受け取るため、今回は「v4.0.30319」を用いて呼び出すこととします。なお、「GetRuntime」は仮想関数テーブルのインデックス番号が「3」となります。

※ バージョン文字列は「v」で始まる .NET のバージョンなどを指定しますが、システムで実際に利用できるバージョンはICLRMetaHostのEnumerateInstalledRuntimesメソッドなどで確認することができます。ここではその説明は省略します。

[VBA 7.0]

' ※ 以下のコードは pMetaHost 取得コードの続きです。

    ' CLRのバージョンを持つ変数
    ' .NET 4系の「v4.0.30319」を用いることとする
    Dim Version As String
    Version = "v4.0.30319"

    Dim pRuntimeInfo As IUnknown
    Call ParseGUID(g, "{BD39D1D2-BA2F-486A-89B0-B4B0CB466891}") ' IID_ICLRRuntimeInfo
    ' ICLRMetaHost::GetRuntime(LPCWSTR, REFIID, void**) [vftable index = 3]
    ' 第1引数の文字列はポインターとして渡す
    ' 第3引数は出力先を pRuntimeInfo とするために VarPtr で渡す
    hr = VBCallAbsoluteObject(pMetaHost, 3, vbLong, _
        StrPtr(Version), VarPtr(g(0)), VarPtr(pRuntimeInfo))
    ' pMetaHost は使わないので解放しておく(任意)
    ' ※ Nothing とすることで半明示的に Release できる
    Set pMetaHost = Nothing
    If hr < 0 Then Call Err.Raise(hr)

ランタイムのホストインスタンスを取得

CLRの情報を提供するICLRRuntimeInfoのインスタンスを取得できたら、ランタイムのホストとして機能を提供する「ICorRuntimeHost」インターフェイスのインスタンスを取得します。これを得ることで、アセンブリのロードなどCLRの各種機能を扱うことができるようになります。

ICorRuntimeHostインターフェイスのインスタンスを取得するには、ICLRRuntimeInfo::GetInterface メソッドを利用します。「GetInterface」は仮想関数テーブルのインデックス番号が「9」となります。

また、これによって得られたインスタンスは、先でも触れていた「参照設定」で「mscoree.dll」を読み込むと使用できるタイプライブラリ「Common Language Runtime Execution Engine」の、「mscoree.CorRuntimeHost」クラスのインスタンスとして扱うことができます。こちらは型定義があり一部を除いてメソッドを呼び出すことができるため、(事前にIDEにて「参照設定」を追加した上で)「CorRuntimeHost」に変換しておきます。

※ タイプライブラリが一覧にない場合は、「<フレームワークのディレクトリ>\mscoree.tlb」(TLBファイル)を手動で読み込みます。「<フレームワークのディレクトリ>」は「%SystemRoot%\Microsoft.NET\Framework\v4.0.30319」(64ビット版の場合は「%SystemRoot%\Microsoft.NET\Framework64\v4.0.30319」)などといった場所になります。

[VBA 7.0]

' ※ 以下のコードは pRuntimeInfo 取得コードの続きです。

    Dim pCorRuntimeHost As IUnknown
    ' ICLRRuntimeInfo::GetInterface(REFCLSID, REFIID, void**) [vftable index = 9]
    Call ParseGUID(g, "{CB2F6723-AB3A-11D2-9C40-00C04FA30A3E}") ' CLSID_CorRuntimeHost
    Call ParseGUID(g2, "{CB2F6722-AB3A-11D2-9C40-00C04FA30A3E}") ' IID_ICorRuntimeHost
    ' 第3引数は出力先を pCorRuntimeHost とするために VarPtr で渡す
    hr = VBCallAbsoluteObject(pRuntimeInfo, 9, vbLong, _
        VarPtr(g(0)), VarPtr(g2(0)), VarPtr(pCorRuntimeHost))
    Set pRuntimeInfo = Nothing
    If hr < 0 Then Call Err.Raise(hr)
    ' クラス mscoree.CorRuntimeHost のインターフェイスは ICorRuntimeHost と一致するため、
    ' より容易にVB上で扱えるように型変換を行う
    Dim host As mscoree.CorRuntimeHost
    Set host = pCorRuntimeHost

以上により、「host」変数にホストインスタンスが代入されたため、これを起点にCLRの機能を利用していきます。

※ なお、外部アセンブリを実行するのみである場合は「ICLRRuntimeHost」インターフェイスのインスタンスを取得する方が容易に実現可能です。上記のGetInterface呼び出しにおいて「CLSID_CLRRuntimeHost = {90F1A06E-7712-4762-86B5-7A5EBA6BDB02}」「IID_ICLRRuntimeHost = {90F1A06C-7712-4762-86B5-7A5EBA6BDB02}」を用いてインスタンスを取得します。

CLR利用の開始と終了

CorRuntimeHostクラスには「Start」メソッド「Stop」メソッドがあり、明示的にCLRを起動・終了することができます。ヘルプには「通常では不要」とあり、特にStartについては何かしらの機能を利用した段階で自動起動されますが、プログラムとして生成されていないVBアプリケーションでの実行やVBAでは同じプロセス上でプログラムの起動終了が行われるため、予期しない動作を防ぐ目的で明示的に呼び出しておきます。

なお、Stopメソッドは必ず呼び出すようにしてください。(VBAの場合、Stopをし忘れるとVBAのホストプログラム終了時に異常終了する可能性があります。)

※ 必ず終了できるようにするために、「VBAの終了タイミングと終了処理の記述」で紹介している方法の利用も検討してください。

[VBA 7.0]

' ※ 以下のコードは CorRuntimeHost 取得コードの続きです。

    ' 主に host 取得直後に実行
    Call host.Start

    ...

    ' 主にすべての処理が終わった段階で実行
    Call host.Stop

CLR初期化のまとめ

ここまでの処理を、以下のように関数にまとめました(一部コメントは省略しています)。この関数「CreateCorRuntimeHost」を呼び出すことでホストインスタンスを得ることができます。

※ なお、mscoree.CorRuntimeHost はコクラスであるため New 演算子でも作成することができますが、そのインスタンスを利用した場合一部CLR機能の呼び出し時に内部例外が発生する場合があるのを確認しており、またバージョン指定もできないため、利用しない方が良いと思われます。

[VBA 7.0]

' バージョン文字列から CorRuntimeHost オブジェクトを作成します。
' - Version: 「v4.0.30319」のように「v」で始まるバージョン文字列
Public Function CreateCorRuntimeHost(ByVal Version As String) As mscoree.CorRuntimeHost
    Dim g(0 To 3) As Long
    Dim g2(0 To 3) As Long
    Dim pMetaHost As IUnknown, hr As Long
    Call ParseGUID(g, "{9280188D-0E8E-4867-B30C-7FA83884E8DE}") ' CLSID_CLRMetaHost
    Call ParseGUID(g2, "{D332DB9E-B9B3-4125-8207-A14884F53216}") ' IID_ICLRMetaHost
    hr = CLRCreateInstance(g(0), g2(0), pMetaHost)
    If hr < 0 Then Call Err.Raise(hr)

    Dim pRuntimeInfo As IUnknown
    Call ParseGUID(g, "{BD39D1D2-BA2F-486A-89B0-B4B0CB466891}") ' IID_ICLRRuntimeInfo
    ' ICLRMetaHost::GetRuntime(LPCWSTR, REFIID, void**) [vftable index = 3]
    hr = VBCallAbsoluteObject(pMetaHost, 3, vbLong, _
        StrPtr(Version), VarPtr(g(0)), VarPtr(pRuntimeInfo))
    Set pMetaHost = Nothing
    If hr < 0 Then Call Err.Raise(hr)

    Dim pCorRuntimeHost As IUnknown
    ' ICLRRuntimeInfo::GetInterface(REFCLSID, REFIID, void**) [vftable index = 9]
    Call ParseGUID(g, "{CB2F6723-AB3A-11D2-9C40-00C04FA30A3E}") ' CLSID_CorRuntimeHost
    Call ParseGUID(g2, "{CB2F6722-AB3A-11D2-9C40-00C04FA30A3E}") ' IID_ICorRuntimeHost
    hr = VBCallAbsoluteObject(pRuntimeInfo, 9, vbLong, _
        VarPtr(g(0)), VarPtr(g2(0)), VarPtr(pCorRuntimeHost))
    Set pRuntimeInfo = Nothing
    If hr < 0 Then Call Err.Raise(hr)
    Set CreateCorRuntimeHost = pCorRuntimeHost
End Function

補足: 旧バージョンの場合

.NET Framework バージョン4未満の場合はCLRCreateInstance関数を利用できませんが、代わりにCorBindToRuntimeEx関数を利用することができます。この関数を利用すると、指定したバージョンに対応するICorRuntimeHostインターフェイスのインスタンスを取得することができます。

なお、この関数の利用は前述したインスタンス取得方法より非常に容易ですが、「.NET Framework 4 以降ではdeprecated」となっている点に注意が必要です

[VBA 7.0]

Private Declare PtrSafe Function CorBindToRuntimeEx Lib "mscoree.dll" _
    (ByVal pwszVersion As LongPtr, ByVal pwszBuildFlavor As LongPtr, _
    ByVal startupFlags As Long, ByRef rclsid As Any, _
    ByRef riid As Any, ByRef ppv As Any) As Long

' 前述の「CreateCorRuntimeHost」と同じように利用できます。
Public Function CreateCorRuntimeHostOld(ByVal Version As String) As mscoree.CorRuntimeHost
    Dim flavor As String
    Dim g(0 To 3) As Long
    Dim g2(0 To 3) As Long
    Dim pCorRuntimeHost As IUnknown, hr As Long
    Call ParseGUID(g, "{CB2F6723-AB3A-11D2-9C40-00C04FA30A3E}") ' CLSID_CorRuntimeHost
    Call ParseGUID(g2, "{CB2F6722-AB3A-11D2-9C40-00C04FA30A3E}") ' IID_ICorRuntimeHost

    ' ワークステーションビルド用を利用する(サーバービルド用の場合は "svr")
    ' (StrPtrを用いるため、意図せず破棄されないように一旦変数に展開)
    flavor = "wks"
    hr = CorBindToRuntimeEx(StrPtr(Version), StrPtr(flavor), 0, _
        g(0), g2(0), pCorRuntimeHost)
    If hr < 0 Then Call Err.Raise(hr)
    Set CreateCorRuntimeHostOld = pCorRuntimeHost
End Function

ドメインの利用

CLRでは「ドメイン」と呼ばれる概念の中でアセンブリのロードや実行コードの処理などを行います。少なくとも(VB上で)アセンブリをロードするには「AppDomain」(System.AppDomain)インスタンスが必要になるため、以下のような方法でドメインのインスタンスを取得します。

なお、AppDomainインスタンスの型情報はタイプライブラリ/DLLの「mscorlib.dll」を参照設定に追加することで利用可能です。

※ タイプライブラリが一覧にない場合は、「mscoree.dll」の場合と同様に「<フレームワークのディレクトリ>\mscorlib.tlb」(TLBファイル)を手動で読み込みます。

これによって取得したAppDomainインスタンスは「System.AppDomain」クラスの(COM用にラップされた)インスタンスであるため、ここからCLRのさまざまな機能を利用することができるようになります。

既定のドメインの取得

CLRがロードされると「既定のドメイン」が必ず作成されているため、これを利用することができます。既定のドメインを取得するには「GetDefaultDomain」メソッドを利用します。

[VBA 7.0]

' 「host」は初期化済み・開始済みの mscoree.CorRuntimeHost オブジェクトとします。

    Dim pUnkDomain As IUnknown
    Dim domain As mscorlib.AppDomain
    ' 引数は出力引数であり、IUnknownのポインターが必要なので pUnkDomain を渡す
    ' (直接 domain 変数を指定しても可)
    Call host.GetDefaultDomain(pUnkDomain)
    Set domain = pUnkDomain

新規ドメインの作成

一方で新たにドメインを作成することもできます。今回のような目的では必要ではありませんが、アセンブリの実行環境の分離、デバッグ用に分かりやすい名前の付与、およびセキュリティを考慮した制限の追加、などの目的で利用できます。新たにドメインを作成するには「CreateDomain」メソッドを利用します。

なお、VBA上で実行する場合は、CLR実行環境の起動と終了(「ドメインのアンロード」を参照)を明確にするために、既定のドメインの利用ではなく新たなドメインの作成を強く推奨します。既定のドメインを用いた場合、CLRの呼び出し状況によってはVBAをホストするアプリケーションの終了時に異常終了する場合があります。(VB6.0系で動作させる場合も開発環境での確認を考慮すると作成した方が良いと考えられます。)

※ この他に mscorlib.AppDomainSetup (System.AppDomainSetup) を利用してドメイン初期化時の情報を与える「CreateDomainEx」メソッドもあります。用途に応じて使い分けてください。

[VBA 7.0]

' 「host」は初期化済み・開始済みの mscoree.CorRuntimeHost オブジェクトとします。

    Dim pUnkDomain As IUnknown
    Dim domain As mscorlib.AppDomain
    ' 適当なドメイン名を付けてドメインを作成
    ' 第3引数は出力引数であり、IUnknownのポインターが必要なので pUnkDomain を渡す
    ' (直接 domain 変数を指定しても可)
    Call host.CreateDomain("VB2CLRTest", Nothing, pUnkDomain)
    Set domain = pUnkDomain

ドメインのアンロード

CreateDomain で作成したドメインは以下のようにアンロードすることができます。一時的な目的で作成したドメインが不要になった場合は明示的にアンロードしておきます。

※ ドメインのアンロードが明示的に行われなくても、ホストの Stop メソッドが呼び出された場合すべての処理が終了します。

[VBA 7.0]

    Set domain = Nothing
    Call host.UnloadDomain(pUnkDomain)
    Set pUnkDomain = Nothing

まとめ

VB(VBA)でCLR/.NETの機能を利用するには、以下の手順でそれぞれインスタンスの初期化・取得を行い、AppDomainのインスタンスを手に入れます。

  1. 利用可能なCLRの情報を管理するインスタンスを得る (ICLRMetaHost)
  2. CLRの情報を持つインスタンスを得る (ICLRRuntimeInfo)
  3. CLRのホストとなるインスタンスを得て処理を開始する (ICorRuntimeHost)
  4. CLR上のドメインを得る (mscorlib.AppDomain / System.AppDomain)

次回では、このドメインを元にアセンブリのロードやそのアセンブリにあるインスタンスの取得などを行ってみます。

※ 余談ですが、今回の方法はCopyMemory(MoveMemory)などを用いておらず、ポインターアドレスを直接扱うのはDispCallFunc関数の利用周り程度であるため、機械語実行などに比べてやや安全にプログラムを試すことができます。

補足: ユーティリティ関数

処理を簡単にするため、説明では以下のユーティリティ関数を定義済みの関数として用いています。

[VBA 7.0]

Declare PtrSafe Function DispCallFunc Lib "oleaut32.dll" _
    (ByVal pvInstance As LongPtr, _
    ByVal oVft As LongPtr, _
    ByVal cc As Long, _
    ByVal vtReturn As Integer, _
    ByVal cActuals As Long, _
    ByRef prgvt As Integer, _
    ByRef prgpvarg As LongPtr, _
    ByRef pvargResult As Variant) As Long

' 文字列表現のGUIDを4要素のLong型配列に変換します。
' その配列のデータはGUIDのポインターとして利用することができます。
' - lnGUID は「lnGUID(0 To 3)」である必要があります。
' - 文字列は「{ }」の有無どちらでも指定できます。
' - 文字列は「{ }」を除いて「XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX」の形式、かつ
'   「X」はすべて16進数である必要があります。
Public Sub ParseGUID(ByRef lnGUID() As Long, ByVal str As String)
    If Left$(str, 1) = "{" Then
        If Right$(str, 1) <> "}" Then Call Err.Raise(5)
        str = Mid$(str, 2, Len(str) - 2)
    End If
    If Len(str) <> 8 + 4 + 4 + 4 + 12 + 4 Then Call Err.Raise(5)
    Dim parts() As String
    parts = Split(str, "-")
    If LBound(parts) <> 0 Or UBound(parts) <> 4 Then Call Err.Raise(5)
    If Len(parts(0)) <> 8 Then Call Err.Raise(5)
    If Len(parts(1)) <> 4 Then Call Err.Raise(5)
    If Len(parts(2)) <> 4 Then Call Err.Raise(5)
    If Len(parts(3)) <> 4 Then Call Err.Raise(5)
    If Len(parts(4)) <> 12 Then Call Err.Raise(5)
    lnGUID(0) = CLng("&H" + parts(0))
    lnGUID(1) = CLng("&H" + parts(2) + parts(1))
    lnGUID(2) = CLng("&H" + Mid$(parts(4), 3, 2) + Mid$(parts(4), 1, 2) + Right$(parts(3), 2) + Left$(parts(3), 2))
    lnGUID(3) = CLng("&H" + Mid$(parts(4), 11, 2) + Mid$(parts(4), 9, 2) + Mid$(parts(4), 7, 2) + Mid$(parts(4), 5, 2))
End Sub

' Object に対して IndexForVftable に対応するメソッドを呼び出します。
' - RetTypeにはメソッド本来の戻り値の型を VbVarType (または VT_*)で指定します。
' - Arguments にはメソッドの引数を指定します。
'   指定時の型がそのまま呼び出し時の変換のヒントとして用いられるため、
'   必要に応じて CLng などの明示的な型変換を行ってください。
Public Function VBCallAbsoluteObject(ByVal Object As IUnknown, _
    ByVal IndexForVftable As Integer, _
    ByVal RetType As VbVarType, _
    ParamArray Arguments() As Variant) As Variant
    ' Objectは必ず指定するものとする
    If Object Is Nothing Then
        Call Err.Raise(5)
    End If
    Dim hr As Long
    ' prgvt に指定する配列
    Dim argVt() As Integer
    ' prgpvarg に指定する配列
    ' (「VARIANT*」の配列なので LongPtr の配列とする)
    Dim argsPtr() As LongPtr
    Dim i As Long, c As Long
    Dim lb As Long, ub As Long
    lb = LBound(Arguments)
    ub = UBound(Arguments)
    c = ub - lb + 1
    ' 要素数0の配列は作れないので場合分け
    If c > 0 Then
        ReDim argVt(lb To ub)
        ReDim argsPtr(lb To ub)
        For i = lb To ub
            argVt(i) = VarType(Arguments(i))
            argsPtr(i) = VarPtr(Arguments(i))
        Next i
        ' 「4」は「CC_STDCALL」
        hr = DispCallFunc(ObjPtr(Object), _
            CLngPtr(IndexForVftable) * Len(argsPtr(0)), _
            4, _
            CInt(RetType), _
            c, _
            argVt(lb), _
            argsPtr(lb), _
            VBCallAbsoluteObject)
    Else
        ' ダミーの配列(実際には使われない)
        ReDim argVt(0)
        ReDim argsPtr(0)
        ' 「4」は「CC_STDCALL」
        hr = DispCallFunc(ObjPtr(Object), _
            CLngPtr(IndexForVftable) * Len(argsPtr(0)), _
            4, _
            CInt(RetType), _
            0, _
            argVt(0), _
            argsPtr(0), _
            VBCallAbsoluteObject)
    End If
    If hr < 0 Then Call Err.Raise(hr)
End Function

参考