C++で汎用的なDLLを作成する

DLLは複数のプログラムから共通で利用できる部分を分離させたライブラリファイルですが、DLLにはいくつかの種類があり、予め利用用途を想定したうえでどのタイプのDLLを作成するかを決めておく必要があります。

  • Win32APIのような昔ながらの?DLL
  • MFC拡張DLL
  • COMコンポーネントとして作られたDLL
  • .NET Frameworkで作られたDLL

 

ここでは汎用性の高い昔ながらのDLLの作成方法を解説します。

プロジェクトの作成

Viual Studio を使って DLL を作成する場合、以下の手順になります。

メニューから[ファイル(F)]→[新規作成(N)]→[プロジェクト...(P)]を選択します。

Win32プロジェクトを選択します。

 

アプリケーションの種類からDLL(D)を選択します。

DLLMainメソッド

DLLは DLLMain というエントリーポイントを持つことが出来ます。

DLLがロードされようとする時やアンロードされる時に呼び出されれるので初期化などをここで行う事が可能です。

https://msdn.microsoft.com/ja-jp/library/cc429094.aspx

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                                         )
{
        switch (ul_reason_for_call)
        {
        case DLL_PROCESS_ATTACH:
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
        case DLL_PROCESS_DETACH:
                break;
        }
        return TRUE;
}

呼び出し規約に注意

呼び出し規約というのは、引数をスタックにプッシュする順序だったり呼び出し元と呼び出し先のどちらが最後にスタックから引数を削除するかというような、関数を呼び出す時の決まり事です。

 

詳細は https://msdn.microsoft.com/ja-jp/library/984x0h58.aspx を参照

 

 

1つのプロジェクトで作られたアプリケーションでは、統一した規約の下で関数を呼び出すので意識したことはあまりないかもしれませんが、DLLはいろいろなアプリケーションから呼び出される可能性があります。

呼び出す側のアプリケーションと呼び出される側のDLLとで、呼び出し規約が一致していないといけません

Visual Studioで作成するプログラムでは、

デフォルトの呼び出し規約が「__cdecl」になっています。

※プロパティの[c/c++]→[詳細設定]で確認できます

 

一方で、Win32APIなどは「__stdcall」という規約が使われており、DLLが提供する関数も「__stdcall」を使うのが一般的です。

 

そこで、DLLが外部に公開する関数だけ「__stdcall」規約にするという方法を用います。

関数名の前に「__stdcall」と記述する事で、関数毎に規約を定める事が可能です。

int __stdcall test(int a, int b)
{
        return a + b;
}

公開する関数を指定する

公開する関数の指定には「__declspec(dllexport)」を使う方法と「モジュール定義ファイル(.def)」を使う方法があります。

__declspec(dllexport)を使って公開

関数の先頭に「__declspec(dllexport)」を記述します。

https://msdn.microsoft.com/ja-jp/library/a90k134d.aspx

extern "C" {
    __declspec(dllexport) int __stdcall DLLTest_A(int a, int b);
}

C++言語の関数名に注意

C++言語では同じ名前で複数の関数を実装(オーバーロード機能)出来る仕組みなどがある為、コンパイル時に関数名を自動で置き換えるマングリングという処理が行われています。

 

この為コンパイル後の関数の名前はソースに書いた名前ではなくなってしまいます

 

しかもマングリングによる命名規則はコンパイラに依存してしまいます。

 DLLとして関数を公開しようとする場合これは不都合です。

 

そこで、extern "C" という宣言を使って、関数をC言語として扱うようにします。

これにより自動置き換えるされる事がなくなりソースで書いた名前で関数を公開する事ができます。

__stdcallによる名前装飾に注意

関数名を変えたくない為に extern "C" を使ったとしても、__stdcallを使うと変わってしまいます。

https://msdn.microsoft.com/ja-jp/library/zxk0tw93.aspx

 

__stdcallを使いつつ名前を変えたくない場合はモジュール定義ファイルを使う方法にしましょう。

モジュール定義ファイル(.def)を使って公開

公開する関数名をモジュール定義ファイルというものに記述します。

プロジェクトにモジュール定義ファイルが無い場合は以下の手順で追加していきます。

メニューから[プロジェクト(P)]→[新しい項目の追加...(W)]を選択します。

 

ファイルが追加されると同時にプロパティのリンカ設定に定義が追加されます。

モジュール定義ファイルへ関数名を定義していきます。

LIBRARY DLLTest

EXPORTS
        DLLTest_A
        DLLTest_B
        ; コメント

LIBRARYに続く文字にはDLLの名前を指定します。

EXPORTSの後に公開する関数名を羅列していきます。

;」の後ろはコメントになります。

 

https://msdn.microsoft.com/ja-jp/library/d91k01sh.aspx