EternalWindows
DLL / システムとDLL

明示的リンクを行う各種アプリケーションは、 呼び出したい関数がどのDLLからエクスポートされているかを事前に把握しておかなければなりません。 多くの場合、そのような情報はリファレンスで確認し、 同時にその関数がエクスポートされているWindowsのバージョンも確認しますが、 DLL毎にある程度の位置づけがあるため、 関数の動作内容から関連するDLLの名前はある程度予測できる場合もあります。 次に、よく使われる関数をエクスポートするDLLを示します。

DLL 特徴
kernel32.dll カーネルオブジェクトやメモリ、DLLの操作など
例) WriteFile, GetProcessHeap, LoadLibrary
user32.dll ウインドウ操作やメッセージ、入力処理など
例) MessageBox, PostMessage, GetCursorPos
gdi32.dll GDIオブジェクトやグラフィックス描画、デバイスの照会など
例) CreateFont, BitBlt, ChangeDisplaySettings,
advapi.dll レジストリの操作やユーザー認証、サービスなど
例) RegOpenKeyEx, LogonUser, CreateService
shell32.dll 実行や特殊パス、UIの表示など
例) ShellExecute, SHGetSpecialFolderLocation, SHBrowseForFolder
winmm.dll サウンド再生やボリュームコントロール、マルチメディアタイマなど
例) PlaySound, mixerGetDevCaps, timeBeginPeriod

kernel32.dllは、原則全てのアプリケーションのアドレス空間にマッピングされます。 理由としては、このDLLがシステムサービスの呼び出しとなっているからなのですが、 これ以上の事についてはここでは触れません。コード上でkernel32.dllの関数を呼び出さなくても、 このDLLはマッピングされているものと解釈してください。 また、user32.dllは、ウインドウを表示する関数などを実装していますが、 このDLLがマッピングされる場合はgdi32.dllやshell32.dllなど、 他の多くのDLLがマッピングされることになるということも覚えておいてください。

上記のような、開発者にとって主要となる関数をエクスポートするDLL群が、 既にマッピングされている可能性が高いという点に注目した場合、 明示的リンクにおいて少し面白い案が浮かびます。 それは、既にアドレス空間にDLLがマッピングされているのであれば、 そのDLLをLoadLibraryでマッピングしようとするのではなく、 単純にGetModuleHandleでマッピングされているアドレスを取得すればよいというものです。

hmod = GetModuleHandle(TEXT("kernel32.dll"));

lpfn = GetProcAddress(hmod, xxx);

lpfn();

このコードでは、GetModuleHandleでマッピングされているkernel32.dllのアドレスを取得し、 そのアドレスを利用してGetProcAddressを呼び出しています。 もし、このコードでGetModuleHandleではなくLoadLibrayを呼び出した場合、 それで生じる欠点などは一切ありませんが、 kernel32.dllの参照カウントが1つ上がることになるという認識は重要です。 DLLにおける参照カウントとは、値が0になったときにDLLのマッピングが解除されるということを意味し、 FreeLibrayの呼び出しで参照カウントを1つ下がったときに解除が行われる可能性があります。 参照カウントが上げるというのは、 今からこのDLLを利用するからアドレス空間にマッピングされ続けていて欲しいという意味合いであり、 その必要がなくなった場合は、FreeLibrayで参照カウントを下げることにより、 DLLの参照カウントを元の値に戻しておきます。 GetModuleHandleで得られるハンドルに対してFreeLibrayを呼び出してしまうと、 単純にDLLの参照カウントが1つ下がることを意味していますから、 そのDLLが常に存在することを想定しているコードでは、エラーが発生する可能性があります。

さて、上記したシステムの一部といえるDLL群ですが、 これらは実際にはどこに存在しているのでしょうか。 これは多くの場合、c:\\WINDOWS\system32となっています。 LoadLibraryでファイル名だけを指定して目的のDLLがロードされるのは、 この関数がsystem32フォルダを検索対象としているからです。 DLLの検索については、次節で説明します。

user32.dllについて

user32.dllに起因するいくつかの問題について取り上げます。 まず、Windows Vista以前のWindowsでは、 user32.dllがプロセスのアドレス空間にマッピングされたとき、 DllMain関数のDLL_PROCESS_ATACH通知にて次のレジストリキーが調べられます。

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows

このキーにはAppInit_DLLsというエントリが存在し、 そこにDLLを指定してある場合は、user32.dllがそのDLLをロードします。 つまり、これはuser32.dllを利用するプロセスのアドレス空間には、 AppInit_DLLsで指定したDLLがマッピングされるということを意味しています。 本来、AppInit_DLLsの役割は、アプリケーショングローバルクラスを登録し、 ウインドウを表示するアプリケーションにそのクラスを利用できるようにさせることですが、 実際にはDLLをプロセスに注入する手段としても用いられるようなこともあり、 セキュリティ上の問題からWindows VistaからはAppInit_DLLsが考慮されなくなっています。 ちなみに、アプリケーショングローバルクラスの登録というのは、 DLL内でWNDCLASSEX.styleにCS_GLOBALCLASSを指定したRegisterClassExを呼び出すだけです。

第2の問題として、スレッドがuser32.dllのウインドウやメッセージ関数を呼び出した場合、 そのスレッドにメッセージキューが格納されることになります。 この場合、SetWindowsHookExによるグローバルメッセージフックが有効になるため、 そのスレッドのプロセスにはフックを利用する開発者が用意したDLLがロードされることになります。 これも先の問題と同じように、予期しないDLLがアドレス空間にマッピングされるケースといえます。

上記のように、自身の関知しないDLLがアドレス空間にマッピングされることを嫌う場合は、 コード上でuser32.dllがエクスポートする関数を呼ばないようにし、 さらにuser32.dllを内部的にマッピングするDLLの関数も呼ばないようにする必要があります。 基本的には、ウインドウなどのGUIを表示しなければuser32.dllがマッピングされることはないと思われますが、 プロセス間通信としてメッセージをベースとしているような場合は、マッピングは行われます。 また、文字列を整形するwsprintfやモジュールから文字列リソースを取得するLoadStringなどもuser32.dllの関数であるため、 これらの関数も呼び出さないようにしておかなければなりません。



戻る