1. ホーム
  2. dll

WindowsのDLLプログラミングにおけるインポートとエクスポート。declspec(dllimport) , __declspec(dllexport) , および

2022-03-02 10:18:23

Windows DLLをプログラミングする際に、関数や変数をインポートするために__declspec(dllimport)キーワードを使用することができます。

<スパン 関数のインポート
    DLLで関数を使用する必要がある場合、多くの場合、関数を視覚的にインポートする必要はなく、コンパイラが自動的にインポートしてくれます。しかし、関数を視覚的にインポートすると、コンパイラはより質の高いコードを生成します。コンパイラは関数がDLLにあるかどうかを正確に知っているので、より良いコードを生成でき、間接的なコールフォワードも必要なくなります。
    Win32のPEフォーマット(Portable Executable Format)では、すべてのインポートアドレスを1つのインポートアドレステーブルにまとめています。関数のインポートに __declspec(dllimport) を使用する場合と使用しない場合の違いを具体例で説明します。
    func が DLL の関数であると仮定して、今、生成する .exe の main 関数内で func 関数を呼び出し、func 関数を表示せずに (つまり、表示せずに) インポートしてみます。__declspec(dllimport)), コード例は次の通りです。
    int main()
    {
        func();
    }
コンパイラは、次のような呼び出しコードを生成します。
    call func
リンカは、この呼び出しを次のようなコードに変換します。
    call 0x40000001 ; ox40000001 は "func" のアドレスです。
そして、リンカが生成する サンク の形をした
    0x40000001: jmp DWORD PTR __imp_func
ここでのimp_funcは、.exeのインポートアドレステーブルにおけるfunc関数のスロットのアドレスです。ローダーは、.exe がロードされたときに、.exe のインポートアドレステーブルを更新するだけでよいのです。
    また、__declspec(dllimport)を使って表示的に関数をインポートした場合、リンカはThunkを生成せず(要求されていない場合)、代わりに間接呼出を直接生成します。したがって、次のようなコードになります。
    __declspec(dllimport) void func1(void);
    void main(void)
    {
        func1();
    }
以下の呼び出しディレクティブが起動されます。
    コール DWORD PTR __imp_func1
    このように、関数を表示単位でインポートすることは、ターゲットコードの削減に効果的です(Thunkが生成されないため)。また、DLL外の関数をDLL内で使用することも、このようにスペースと時間の効率を上げることができます。
<スパン 変数のインポート
    関数と異なり、DLLで変数を使用する場合は、表示上インポートする必要があります。変数のインポートには、__declspec(dllimport) キーワードを使用します。DLL で .def を使って変数をエクスポートする場合、非推奨の CONSTANT の代わりに DATA 変数を使うべきです。CONSTANT はポインタを使った変数への間接アクセスが必要な場合があり、いつ何か問題が起こるか分からないからです。

まず、コードを見てください。以下は、dev-c++で独自のDLLをビルドする際のdll.h内のコードで、a:_declspec(dllexport)があります。

#ifndef _DLL_H_
#define _DLL_H_//Protect against duplicate definitions(重複定義からの保護

#if BUILDING_DLL
#define DLLIMPORT __declspec (dllexport)
#else /* BUILDING_DLLではありません */
# define DLLIMPORT __declspec (dllimport)
#endif /* BUILDING_DLLでない場合 */


DLLIMPORT void HelloWorld (void);


#endif /* _DLL_H_ */

上記コードの_delcspce(dllexport)は、プログラムの可読性を高めるためにマクロとして定義されています! これは、関数をエクスポート関数、つまり、その関数を含むプログラム以外のプログラムから呼び出される関数として定義するためのものです! この文では、void Helloword(void)となっています。

msdnより:32ビットコンパイラ版では __declspec(dllexport) キーワードを使用して、DLL からデータ、関数、クラス、またはクラスメンバー関数をエクスポートします。 __declspec(dllexport) オブジェクトファイルにexport指示文を追加する

関数をエクスポートするには __declspec(dllexport) キーワードは、呼び出し元のコンベンションキーワードの左側に表示されなければなりません(キーワードが指定されている場合)。たとえば、次のようになります。

__declspec(dllexport) void __cdecl Function1(void);

クラス内のすべてのパブリックデータメンバーとメンバー関数をエクスポートするには、次のようにクラス名の左側にキーワードを記述する必要があります。

class __declspec(dllexport) CExampleExport : public CObject
{ ... class definition ... };

DLL を生成する場合、エクスポートされる関数のプロトタイプやクラスを含むヘッダファイルを作成するのが一般的です。 __declspec(dllexport) という宣言をヘッダーファイルに追加します。コードの読みやすさを向上させるために、以下の宣言を追加します。 __declspec(dllexport) マクロを定義し、エクスポートされる各シンボルに対してそれを使用します。

#define DllExport __declspec( dllexport ) 

__declspec(dllexport) DLL のエクスポートテーブルに関数名を格納します。

関数のエクスポートに使用された方法(.def ファイルまたは __declspec(dllexport) キーワード)を判断するには、次の質問に回答してください。

  • 常にエクスポート関数を追加する必要がありますか?

  • 例えば、再生成できない多くの実行ファイルで使用されるサードパーティのDLLや、簡単に再生成できるアプリケーションで使用されるDLLのみなど、誰が使用するのでしょうか?

<スパン .DEFファイルを使用するメリットとデメリット

関数を.defファイルでエクスポートすると、エクスポート番号を制御することができます。DLL にエクスポートされる関数を追加する場合、(他のエクスポートされる関数よりも)高いシリアル番号の値を割り当てることができます。このようにすると、暗黙的リンクを使用するアプリケーションは、新しい関数を含む新しいインポートライブラリに再リンクする必要がありません。これは、例えば、多くのアプリケーションで使用されるサードパーティのDLLを設計する場合に重要です。MFC DLLは、.defファイルを使用して生成されます。

.defファイルを使用するもう一つの利点は、NONAME属性を使って関数をエクスポートすることができることで、シリアル番号だけをDLLのエクスポートテーブルに入れることができます。エクスポートされる関数の数が多い DLL では、NONAME 属性を使用することで DLL ファイルのサイズを小さくすることができます。モジュール定義文の書き方については、「モジュール定義文の規則」を参照してください。シリアル番号のエクスポートについては、以下を参照してください。 <スパン DLLから関数を名前ではなくシリアル番号でエクスポートする .

.def ファイルを使用する主な欠点は、C++ ファイルで関数をエクスポートする場合、.def ファイルに修飾名を記述するか、外部の "C" を使用して標準 C とリンクする必要があることです。 エクスポートされた関数の定義 を使用すると、コンパイラによる名前の変更を避けることができます。

.def ファイルに修飾名を入れる必要がある場合、DUMPBIN ユーティリティまたは /MAP リンカ オプションを使用して修飾名を取得することができます。コンパイラが生成する修飾名は、コンパイラ固有のものであることに注意してください。Visual C++ コンパイラーによって生成された修飾子を .def ファイルに配置する場合、 DLL にリンクするアプリケーションも同じバージョンの Visual C++ で生成し、 呼び出すアプリケーションの修飾子名が DLL の .def ファイルにエクスポートされた名前と一致するように する必要があります。

<スパン declspec(dllexport)を使用するメリット・デメリット

__declspec(dllexport) を使用すると、.def ファイルの管理やエクスポートされた関数の修飾名を取得することを考えなくて良いので便利です。これは、例えば、あなたがコントロールするアプリケーションで使用するためにDLLを設計している場合、うまく機能します。新しいエクスポート関数を含む DLL を再生成する場合、アプリケーションも再生成する必要があります。なぜなら、異なるバージョンのコンパイラーで再コンパイルすると、エクスポートされる C++ 関数の修飾名が変更される可能性があるからです。