2006 年 11 月 20 日 23 時 52 分

DLL の初期化とグローバル変数


このアーカイブは同期化されません。 mixi の日記が更新されても、このアーカイブには反映されません。


昨日未解決にしていた DLL のハンドルは、
どこから取ってくることができるのか。

インプロセスサーバに限らず、一般的な Windows DLL には、
DllMain という初期化用関数がある。
この関数は DLL の実装者が用意するもので、
プロトタイプは以下の通りだ。

BOOL WINAPI DllMain(HINSTANCE hinstDLL,
        DWORD fdwReason, LPVOID lpvReserved);

DLL は外部のプロセスによって利用されるので、
プロセスがスレッドに関するアクションを起こした際に、
この関数にイベントが通知される。

引数の hinstDLL には DLL のハンドルが渡される。
dwReason がイベントの種類を表している。

よく使うのは DLL_PROCESS_ATTACH であり、
これは DLL が読み込まれた際に呼び出されるので、
DLL 全体の初期化を行うにはちょうど良い。

hinstDLL は DLL が読み込まれている間は固定値なので、
これをどこかに保存しておけばどこからでも参照できる。
そこでグローバル変数 g_moduleHandle を作り、
そこにハンドルを格納することにしよう。

グローバル変数は、global.hpp に宣言しておく。
場所は「#include <ole2.h>」の下にしておこう。

========== global.hpp ==========

//(…前略…)

#include <ole2.h>

// グローバル変数
extern HMODULE g_moduleHandle;

#endif // !global_hpp_included

========== end of global.hpp ==========

extern は、どこかに変数の実体があるという宣言である。
後ろに初期化子を持たない記述をした場合、
class の定義と同じで、宣言自身はメモリを割り当てないが、
どこかにある変数の値を参照することが可能になる。


そのため、必ずどこかのソースファイルに実体が必要だ。
extern で宣言された変数は共有されるため、
実体は 1 つだけ存在する必要がある。
もし、実体が複数あるとエラーになる。

では、実体を定義するソースファイルも作ろう。
単純に考え、「global.cpp」としておこうか。

========== global.cpp ==========

#include "global.hpp"

// グローバル変数の実体
HMODULE g_moduleHandle = NULL;

========== end of global.cpp ==========

後ろに初期化子を持つ記述をした場合、
変数の実体を定義したことになる。
(前に extern をつけても構わない)
この変数は、global.cpp にその実体を持つが、
他のファイルからも参照が可能となる。

この事を、C 言語では「外部リンケージを持つ」と言う。
extern は外部リンケージを持つのだ。
逆に、static の場合は内部リンケージとなり参照できない。

では、この変数に DLL のハンドルを入れるために、
DllMain 関数を準備しよう。

========== library.cpp ==========

#include "global.hpp"

// DLL のハンドラ関数

BOOL WINAPI DllMain(HINSTANCE hinstDLL,
        DWORD fdwReason, LPVOID lpvReserved) {

    if (fdwReason == DLL_PROCESS_ATTACH) {

        // DLL ロード時にハンドルを記憶しておく
        g_moduleHandle = hinstDLL;

    }

    return TRUE;
}

========== end of library.cpp ==========

これで、DLL 読み込み時にハンドルがコピーされ、
全ソースファイル内で利用可能となる。

ところで、g_moduleHandle は、HMODULE 型だが、
hinstDLL は、HINSTANCE 型である。
これらは 16 ビット環境では異なる型だったのだが、
昨今の環境では同一とみなして構わないため、
キャストなしで代入することが可能である。

さて、例によってビルドしてみる。
記述にミスがない限りは、コンパイルが通るはずだ。
C++ の場合、ちょっとした文法ミスだけで
大量のエラーに見舞われる危険性がある。

記述を確認し、環境が許せばこまめにコンパイルして
エラーを確認する癖をつけることが必要である。

では、明日はインスタンスの作成について考えてみよう。



Copyright (c) 1994-2007 Project Loafer. All rights reserved.