Excel で DLL にアクセスする
適用対象: Excel 2013 | Office 2013 | Visual Studio
Microsoft Excel の DLL 関数やコマンドには、さまざまな方法でアクセスできます。
Declare ステートメントを使用して、関数またはコマンドが使用可能できるようになっている Microsoft Visual Basic for Applications (VBA) コード モジュールを通じてアクセスする。
CALL 関数または REGISTER 関数を使用して、XLM マクロ シートを通じてアクセスする。
直接ワークシートから、またはユーザー インターフェイス (UI) のカスタマイズされた項目からアクセスする。
このドキュメントでは、XLM 関数については説明しません。 それ以外の 2 つの方法のうち、どちらかを使用することをお勧めします。
直接ワークシートから、または UI のカスタマイズされた項目からアクセスするには、最初に関数またはコマンドを Excel に登録する必要があります。 コマンドまたは関数の登録の詳細については、「Excel で XLL コードにアクセスする」を参照してください。
VBA から DLL 関数およびコマンドを呼び出す
Declare ステートメントを使用すると、VBA の DLL 関数やコマンドにアクセスできます。 このステートメントには、コマンド用の構文と関数用の構文が 1 つずつあります。
構文 1 - コマンド
[Public | Private] Declare Sub name Lib "libname" [Alias "aliasname"] [([arglist])]
構文 2 - 関数
[Public | Private] Declare Function name Lib "libname" [Alias "aliasname"] [([arglist])] [As type]
オプションの Public キーワードと Private キーワードは、インポートされた関数のスコープ (Visual Basic プロジェクト全体、または Visual Basic モジュールのみ) をそれぞれ指定します。 名前は、VBA コードで使用する名前です。 これが DLL の名前と異なる場合は、Alias "aliasname" 指定子を使用する必要があります。また、DLL によってエクスポートされた関数の名前を指定する必要があります。 DLL 序数を参照して DLL 関数にアクセスする場合は、エイリアス名を指定する必要があります。これは、 #によってプレフィックスが付いた序数です。
コマンドは void を返す必要があります。 関数は、VBA が ByVal を認識できる型を返す必要があります。 つまり、いくつかの型 (文字列、配列、ユーザー定義型、およびオブジェクト) は引数をインプレースで変更することで、より簡単に返されるということです。
注:
VBA では、引数リストと Visual Basic モジュールに記載されている戻り値が DLL でコード化されたのと同じであることを確認できません。 間違いが原因で Excel がクラッシュする可能性があるため、この点は慎重に確認する必要があります。
関数またはコマンドの引数が参照またはポインターによって渡されない場合は、arglist 宣言の ByVal キーワードの前に指定する必要があります。 C/C++ 関数がポインターの引数を受け取る場合や、C++ 関数が参照の引数を受け取る場合は、ByRef 渡しにする必要があります。 ByRef キーワードは、VBA では既定であるため引数リストで省略することもできます。
C/C++ および VBA の引数の型
C/C++ と VBA の引数の型の宣言を比較するときには、次の点に注意が必要です。
VBA の String は、ByVal 渡しの場合はバイト文字列 BSTR 構造体へのポインターとして渡されます。ByRef 渡しの場合はポインターへのポインターとして渡されます。
文字列を格納している VBA の Variant は、ByVal 渡しの場合は Unicode ワイド文字文字列 BSTR 構造体へのポインターとして渡されます。ByRef 渡しの場合はポインターへのポインターとして渡されます。
VBA の Integer は、C/C++ の signed short と同等の 16 ビット型です。
VBA の Long は、C/C++ の signed int と同等の 32 ビット型です。
VBA と C/C++ は、どちらもユーザー定義データ型の定義が可能です。それぞれ、Type ステートメントと struct ステートメントを使用します。
VBA と C/C++ は、どちらも Variant データ型をサポートしています。C/C++ については、Windows OLE/COM ヘッダー ファイル内で VARIANT として定義されています。
VBA の配列は OLE の SafeArrays です。C/C++ については、Windows OLE/COM ヘッダー ファイル内で SAFEARRAY として定義されています。
VBA の Currency データ型は、ByVal 渡しの場合は Windows ヘッダー ファイル wtypes.h 内で定義されている CY 型の構造体として渡されます。ByRef 渡しの場合はそのポインターとして渡されます。
VBA では、ユーザー定義データ型のデータ要素は 4 バイト境界にパッキングされます。Visual Studio では、このデータ要素が既定で 8 バイト境界にパッキングされます。 そのため、C/C++ 構造体の定義は #pragma pack(4) … #pragma pack()
ブロックで囲んで要素の配置がずれないようにする必要があります。
次に、同等のユーザー タイプ定義の例を示します。
Type VB_User_Type
i As Integer
d As Double
s As String
End Type
#pragma pack(4)
struct C_user_type
{
short iVal;
double dVal;
BSTR bstr; // VBA String type is a byte string
}
#pragma pack() // restore default
VBA は、Excel がサポートするよりも多くの値の範囲をサポートすることがあります。 VBA の double は IEEE に準拠していて、現時点のワークシートでは 0 に切り捨てられている非正規化数をサポートしています。 VBA の Date 型は、負のシリアル化された日付を使用することで 1-Jan-0100 (100 年 1 月 1 日) よりも前の日付を表現できます。 Excel では、ゼロ以上のシリアル化された日付のみが許容されます。 VBA の Currency 型 (64 ビット整数にスケーリングされます) は、8 バイトの double ではサポートされない精度を実現できるためワークシートでは一致しません。
Excel は、VBA ユーザー定義関数に、次に示す型のバリアントのみを渡します。
VBA データ型 | C/C++ バリアント型のビット フラグ | 説明 |
---|---|---|
倍精度浮動小数点数 |
VT_R8 |
|
ブール型 |
VT_BOOL |
|
日付 |
VT_DATE |
|
String |
VT_BSTR |
OLE Bstr バイト文字列 |
範囲 |
VT_DISPATCH |
範囲とセルの参照 |
配列を含むバリアント型 |
VT_ARRAYVT_VARIANT |
リテラル配列 |
Ccy |
VT_CY |
64 ビット整数を拡張して、小数点以下 4 桁に四捨五入した精度を許可します。 |
エラーを含むバリアント型 |
VT_ERROR |
|
VT_EMPTY |
空のセルまたは省略された引数 |
VarType を使用して VBA で渡されたバリアント型の型を確認できます。ただし、関数は参照を使用して呼び出されたときに範囲の値の型を返す点が異なります。 バリアント型 (Variant) が Range 参照オブジェクトであるかどうかを判断するには、IsObject 関数を使用します。
Value プロパティを Variant に割り当てることで、Range から VBA バリアント型の配列を含む Variants を作成できます。 その時点で有効であった地域設定の標準通貨形式を使用して書式化されソース範囲に含まれるセルは、Currency 型の配列要素に変換されます。 日付として書式化されたセルは、Date の配列要素に変換されます。 文字列を含んでいるセルは、ワイド文字 BSTR バリアントに変換されます。 エラーを含んでいるセルは、VT_ERROR 型の Variants に変換されます。 BooleanTrue または False を含んでいるセルは、VT_BOOL 型の Variants に変換されます。
注:
バリアント型 (Variant) では、True は -1、False は 0 として格納されます。 日付または通貨金額として書式化されていない数値は、VT_R8 型のバリアントに変換されます。
バリアント型と文字列の引数
Excel は、内部的にワイド文字 Unicode 文字列で動作しています。 VBA ユーザー定義関数が String 引数を受け取るように宣言されていると、Excel は指定された文字列をロケール固有の方法でバイト文字列に変換します。 関数に Unicode 文字列が渡されるようにするには、VBA ユーザー定義関数が String 引数ではなく Variant を受け入れるようにする必要があります。 これにより、DLL 関数は、その Variant BSTR ワイド文字文字列を VBA から受け入れできるようになります。
DLL から VBA に Unicode 文字列を返すには、 バリアント型 (Variant) の文字列引数を変更する必要があります。 これを機能させるには、DLL 関数を Variant および C/C++ コードへのポインターとして宣言し、VBA コードで引数を ByRef varg As Variant
として宣言する必要があります。 古い文字列メモリを解放し、OLE Bstr 文字列関数を使用して DLL でのみ作成された新しい文字列値を解放する必要があります。
DLL から VBA にバイト文字列を返す場合は、バイト文字列 BSTR 引数をインプレースで変更する必要があります。 これを機能させるには、DLL 関数を BSTR と C/C++ コードへのポインターへのポインターを取るように宣言し、VBA コードの引数を " ByRef varg As String" として宣言する必要があります。
このような方法で VBA から渡された文字列は、メモリ関連の問題を避けるために、OLE BSTR 文字列関数を使用してのみ処理する必要があります。 たとえば、渡された文字列を上書きする前に SysFreeString を呼び出してメモリを解放し、SysAllocStringByteLen または SysAllocStringLen を呼び出して新しい文字列の領域を割り当てる必要があります。
Excel ワークシートのエラーは、次の表に示す引数を指定した CVerr 関数を使用することで、VBA の Variants として作成できます。 また、ワークシートのエラーは、VT_ERROR 型の Variants を使用して、次に示す値を ulVal フィールドに設定することで、DLL から VBA に返すこともできます。
エラー | バリアント型の ulVal 値 | CVerr 引数 |
---|---|---|
#NULL! |
2148141008 |
2000 |
#DIV/0! |
2148141015 |
2007 |
#VALUE! |
2148141023 |
2015 |
#REF! |
2148141031 |
2023 |
#NAME? |
2148141037 |
2029 |
#NUM! |
2148141044 |
2036 |
#N/A |
2148141050 |
2042 |
所定のバリアント型の ulVal 値は、CVerr 引数の値に 16 進数の x800A0000 を加えたものと同じになる点に注目してください。
ワークシートから DLL 関数を直接呼び出す
たとえば、VBA や XLM をインターフェイスとして使用したり、関数、引数、戻り値の型を事前に Excel に知らせることなく、ワークシートから Win32 DLL 関数にアクセスすることはできません。 これを行うプロセスを登録と呼びます。
次に示すような方法で、ワークシートから DLL の関数にアクセスできます。
前述したように VBA で関数を宣言して、その関数に VBA ユーザー定義関数でアクセスします。
XLM マクロ シートで CALL を使用することで DLL 関数を呼び出して、XLM ユーザー定義関数からアクセスします。
XLM コマンドまたは VBA コマンドを使用して XLM の REGISTER 関数を呼び出します。これにより、関数がワークシート セルに入力されたときに、その関数を認識するために Excel が必要とする情報を提供します。
DLL を XLL に変換して、XLL を有効化するときに C API の xlfRegister 関数を使用して関数を登録します。
4 番目のアプローチは自己完結型であり、関数を登録するコードと関数のコードは、どちらも同じコード プロジェクトに含まれています。 アドインに変更を加えても、XLM シートや VBA コードに変更を加える必要はありません。 C API の機能を維持したまま適切に管理された方法でこれを行うには、DLL を XLL に変換して、その結果のアドインをアドイン マネージャーで読み込む必要があります。 これにより、アドインが読み込まれると (または有効化されると)、DLL で公開してる関数を Excel から呼び出せるようになり、XLL に含まれるすべての関数を登録して、その他の DLL の初期化を実行できます。
Excel から DLL コマンドを直接呼び出す
Win32 DLL コマンドは、VBA などのインターフェイスが存在しない場合や、事前にコマンドが登録されていない場合は、Excel のダイアログ ボックスやメニューから直接アクセスすることはできません。
次に示すような方法で DLL のコマンドにアクセスできます。
前述したように VBA でコマンドを宣言して、VBA マクロからアクセスします。
XLM マクロ シートで CALL を使用することで DLL コマンドを呼び出して、XLM マクロからアクセスします。
XLM コマンドまたは VBA コマンドを使用して XLM の REGISTER 関数を呼び出します。これにより、マクロ コマンドの名前を期待するダイアログ ボックスにコマンドが入力されたときに、そのコマンドを認識するために Excel が必要とする情報が提供されます。
DLL を XLL に変換して、C API の xlfRegister 関数を使用してコマンドを登録します。
DLL 関数のコンテキストで前述したように、4 番目のアプローチは最も自己完結型であり、登録コードはコマンド コードに近いままです。 これを行うには、DLL を XLL に変換し、アドイン マネージャーを使用して結果のアドインを読み込む必要があります。 この方法でコマンドを登録すると、カスタム メニューなどのユーザー インターフェイスの要素にコマンドをアタッチしたり、特定のキーストロークやその他のイベントでコマンドを呼び出すイベント トラップを設定したりすることもできます。
Exce に登録されたすべての XLL コマンドについて、Excel では、次の形式になっていると見なされます。
int WINAPI my_xll_cmd(void)
{
// Function code...
return 1;
}
注:
EXCEL は、XLM マクロ シートから呼び出されない限り、戻り値を無視します。この場合、戻り値は TRUE または FALSE に変換されます。 したがって、コマンドが正常に実行された場合は 1 を返し、失敗した場合は 0 を返すか、ユーザーによって取り消された場合は 0 を返す必要があります。
DLL のメモリと複数の DLL インスタンス
アプリケーションが DLL を読み込むと、DLL の実行可能コードがグローバル ヒープに読み込まれ、実行できるようにされ、そのデータ構造のグローバル ヒープに領域が割り当てられます。 Windows では、メモリ マッピングを使用して、アプリケーションがアクセスできるように、これらのメモリ領域をアプリケーションのプロセス内にあるかのように表示します。
その後、2 番目のアプリケーションが DLL をロードしても、Windows は DLL の実行可能コードのコピーを別に作成することはありません (そのメモリは読み取り専用になっているため)。 Windows は、DLL の実行可能コードのメモリを両方のアプリケーションのプロセスにマッピングします。 ただし、DLL のデータ構造のプライベート コピー用に 2 つ目の領域を割り当て、このコピーを 2 番目のプロセスにのみマップします。 これにより、どちらのアプリケーションも相互の DLL データに干渉しないようにします。
そのため、DLL 開発者は、静的変数やグローバル変数、データ構造体が複数のアプリケーションからアクセスされたり、同じアプリケーションの複数のインスタンスからアクセスされたりすることについて心配する必要がなくなります。 すべてのアプリケーションのすべてのインスタンスは、DLL のデータの独自のコピーを取得します。
DLL 開発者は、異なるスレッドから DLL を何度も呼び出すアプリケーションの同じインスタンスを心配する必要があります。これは、そのインスタンスのデータの競合が発生する可能性があるためです。 詳細については、「Excel でのメモリ管理」を参照してください。