DLL の開発

適用対象: Excel 2013 | Office 2013 | Visual Studio

ライブラリとは、実行可能アプリケーションにいくつかの機能やデータを提供するコンパイル済みコードの集合です。 ライブラリは静的または動的にリンクさせることが可能で、従来、ファイル名の拡張子はそれぞれ .lib および .dll となっています。 静的ライブラリ (C ランタイム ライブラリなど) はコンパイル時にアプリケーションにリンクされ、結果的に実行可能プログラムの一部となります。 アプリケーションは、必要なとき (通常は、アプリケーションの起動時) に DLL を読み込みます。 DLL は、別の DLL を読み込み動的にリンクできます。

DLL を使用する利点

DLL の主な利点は次のとおりです。

  • すべてのアプリケーションが、ディスク上の単一コピーを共有できます。
  • アプリケーションの実行可能ファイルは、サイズを落として保管されます。
  • 大規模な開発プロジェクトを細分化できます。 アプリケーションと DLL の開発者が合意する必要があるのは、それぞれの部分間のインターフェイスのみになります。 このインターフェイスは、DLL によってエクスポートされます。
  • DLL の開発者は DLL を更新できます。多くの場合、その目的は効率性を向上させたり、バグを修正したりすることにあります。DLL のエクスポートされたインターフェイスを変更しない場合には、DLL を使用しているすべてのアプリケーションを更新する必要はありません。

DLL を使用すると、Microsoft Excel にワークシート関数とコマンドを追加できます。

DLL を作成するためのリソース

DLL を作成するには次のものが必要です。

  • ソース コード エディター。
  • ソース コードを、使用しているハードウェアと互換性のあるオブジェクト コードに変換するコンパイラ。
  • 使用している場合に静的ライブラリからコードを追加したり、実行可能 DLL ファイルを作成したりするためのリンカー。

Microsoft Visual Studio などの最新の統合開発環境 (IDE) には、これらすべてが備わっています。 また、さらに多くのもの、つまりスマート エディター、コードをデバッグするためのツール、複数のプロジェクトを管理するためのツール、新しいプロジェクト ウィザード、および他の重要な多くのツールもあります。

DLL は、C/C++、Pascal、Visual Basic などいくつかの言語で作成できます。 Excel に用意されている API のソース コードが C および C++ であることから、この資料ではこれら 2 つの言語についてのみ取り上げます。

関数とコマンドのエクスポート

DLL プロジェクトをコンパイルするとき、コンパイラとリンカーは、エクスポート対象の関数を把握してアプリケーションが使用できるようにする必要があります。 このセクションでは、そのための方法について説明します。

コンパイラがソース コードをコンパイルすると、一般に、関数の名前がソース コード内の外観から変更されます。 通常、名前の装飾と呼ばれるプロセスで、名前の先頭または末尾にを追加することでこれを行います。 DLL を読み込むアプリケーションで認識できる名前で関数がエクスポートされていることを確認する必要があります。 これは、装飾された名前を単純なエクスポート名に関連付けるようリンカーに指示することを意味します。 エクスポート名には、ソース コードに最初に表示された名前または他の名前を指定できます。

名前をデコレートする方法は、言語、およびコンパイラに指示されている関数を使用できるようにする方法、つまり呼び出し規約によって異なります。 DLL が使用する Windows 標準のプロセス間呼び出し規約は、WinAPI 規約と呼ばれています。 これは、Windows ヘッダー ファイルで WINAPI として定義され、次に Win32 宣言子である __stdcall を使用して定義されます。

Excel で使用する DLL エクスポート関数 (ワークシート関数、マクロシート同等関数、ユーザー定義コマンドのいずれの場合であっても) は、必ず WINAPI / __stdcall 呼び出し規則を使用する必要があります。 Win32 コンパイラの既定として __cdecl 呼び出し規則を使用するには、関数の定義に WINAPI 指定子を明示的に含める必要があります。何も指定しない場合は WINAPIV として定義されます。

リンカーに対して、関数がエクスポートされること、名前が次の方法のいずれかによって外部で認識されることを通知できます。

  • 対象の関数を、DEF ファイルの EXPORTS キーワードの後に配置し、リンク時にこのファイルを参照するよう DLL プロジェクト設定を行います。
  • 関数の定義で __declspec(dllexport) 宣言子を使用します。
  • #pragma プリプロセッサ ディレクティブを使用して、リンカーにメッセージを送信します。

プロジェクトでは 3 つの方法すべてを使用できます。また、コンパイラとリンカーも 3 つの方法をサポートしていますが、これらの方法を複数使用して 1 つの関数をエクスポートしてはいけません。 たとえば、DLL に 2 つのソース コード モジュール (1 つの C と 1 つの C++) が含まれており、それぞれにmy_C_exportと my_Cpp_export 、エクスポートする 2 つの関数 含まれているとします。 わかりやすくするため、各関数は 1 つの倍精度数値引数を取り、同じデータ型を返すことにします。 次のセクションでは、これらの各メソッドを使用してそれぞれの関数をエクスポートする方法の、代替方法について説明します。

DEF ファイルの使用

double WINAPI my_C_export(double x)
{
/* Modify x and return it. */
    return x * 2.0;
}
double WINAPI my_Cpp_export(double x)
{
// Modify x and return it.
    return x * 2.0;
}

DEF ファイルには、次の行を含める必要があります。

EXPORTS my_C_export = _my_C_export@8 my_Cpp_export

EXPORTS ステートメントの後の行の一般的な構文は、次のようになります。

entryname[=internalname] [@ordinal[NONAME]] [DATA] [PRIVATE]

C 関数はデコレートされますが、DEF ファイルはリンカーに対して、(この例の場合) 元のソース コード名を使用して関数を公開することを明示的に強制する点に注目してください。 リンカーは元のコード名を使用して C++ 関数を暗黙的にエクスポートするので、DEF ファイルにデコレートされた名前を含める必要はありません。

32 ビットの Windows API 関数呼び出しの場合、C コンパイル関数の装飾の規則は次のとおりです。 function_namefunction_name@n になります。 n は、すべての引数で 10 進として表されるバイト数で、各バイトは 4 の最も近い倍数に切り上げられます。

注:

Win32 では、すべてのポインターが 4 バイト単位です。 戻り値の型は、名前デコレートに影響を与えません。

C++コンパイラは、関数と関数プロトタイプを extern "C" {...}で囲むことにより、C++ 関数のデコレートされていない名前を強制的に表示させることができます。 この例に示されているように、ブロックします。 (ここでは中かっこ {} は省略されています。宣言が参照するのは、直後の関数コード ブロックのみだからです)。

extern "C"
double WINAPI my_undecorated_Cpp_export(double x)
{
// Modify x and return it.
    return x * 2.0;
}

C または C++ ソース ファイルに含めることができるヘッダー ファイルに C 関数プロトタイプを配置する場合、次のプリプロセッサ ディレクティブを含める必要があります。

#ifdef __cplusplus
extern "C" {
#endif
double WINAPI my_C_export(double x);
double WINAPI my_Cdecorated_Cpp_export(double x);
#ifdef __cplusplus
}
#endif

__declspec(dllexport) 宣言子の使用

次に示すように、__declspec(dllexport) キーワードを関数の宣言で使用できます。

__declspec(dllexport) double WINAPI my_C_export(double x)
{
/* Modify x and return it. */
    return x * 2.0;
}

The __declspec(dllexport) keyword must be added at the extreme left of the declaration. The advantages of this approach are that the function does not need to be listed in a DEF file, and that the export status is right with the definition.

C++ 関数が C++ 名前デコレートによって使用可能になるのを回避する場合には、次のように関数を宣言しなければなりません。

extern "C"
__declspec(dllexport) double WINAPI my_undecorated_Cpp_export(double x)
{
// Modify x and return it.
    return x * 2.0;
}

リンカーはこの関数を my_undecorated_Cpp_export として使用できるようにします。つまり、デコレートなしで、ソース コードで示されているままの名前になります。

#pragma プリプロセッサ リンカー ディレクティブの使用

Microsoft Visual Studio の最近のバージョンでは、#pragma ディレクティブと一緒に使用する、2 つの事前定義されたマクロをサポートしています。リンカーに対して、関数コードから直接関数をエクスポートするように指示できます。 FUNCTIONFUNCDNAME (それぞれの末尾はアンダースコア 2 つで構成されています) というマクロは、それぞれデコレートされない関数名とデコレートされた関数名に拡張されます。

たとえば、Microsoft Visual Studio を使用している場合、これらの行を次のように共通ヘッダー ファイルに組み込むことができます。

#if _MSC_VER > 1200 // Later than Visual Studio 6.0
#define EXPORT comment(linker, "/EXPORT:"__FUNCTION__"="__FUNCDNAME__)
#else // Cannot use this way of exporting functions.
#define EXPORT
#endif // else need to use DEF file or __declspec(dllexport)

このヘッダーがソース ファイルに含まれていると、2 つの関数例を次のようにエクスポートできます。

C コード:

double WINAPI my_C_export(double x)
{
#pragma EXPORT
/* Modify x and return it. */
    return x * 2.0;
}

C++ コード:

double WINAPI my_Cpp_export(double x)
{
#pragma EXPORT
// Modify x and return it.
    return x * 2.0;
}

このディレクティブは、関数の本体内に配置する必要があり、コンパイラ オプション /EP または /P が設定されていない場合にのみ拡張されます。 この手法を使用すると、DEF ファイルまたは __declspec(dllexport) 宣言が不要になり、関数コードでエクスポート状態の仕様を維持できます。

関連項目