EternalWindows
DLL / 明示的リンク

前節で述べたDLLのマッピングは、暗黙的リンクと呼ばれたりします。 ローダが、EXEがインポートしているDLLをマッピングしてくれるため、 プロセスは自らDLLをマッピングするようなコードを実行する必要はありません。 このように暗黙的リンクには、DLLのマッピングを透過的に行うという大きな利点がありますが、 場合によっては欠点になることもあります。

たとえば、Windows Vista以降のみに存在するDLLを利用するEXEを作成したとしましょう。 もし、このEXEをWinodws XPで動作させたとしたら、 DLLが存在しないがためにDLLのマッピングは失敗してしまいます。 この結果、そのプロセスは起動に失敗したということになり、 システムはアプリケーションの初期化エラーを知らせるダイアログを表示します。 仮に、内部でOSの判定を行ってそのVista以降の関数を呼ばないようにしても、 関数名がEXEのインポートセクションに記述されるという事実は変わりありませんから、 やはり起動時にエラーが発生することになります。

上記のような事体を防ぐには、どのようなシステムにも存在するDLLを利用するよう アプリケーションを設計するのが一番ですが、他にも対策があります。 DLLをプロセスの起動と共にマッピングするのではなく、 任意のタイミングでマッピングするという方法はどうでしょうか。 このようにすれば、たとえDLLが存在しないがためにマッピングに失敗したとしても、 為替処理を実行するなどエラーの対策を施すことができます。 次に示すLoadLibraryは、引数で指定されたDLLをアドレス空間にマッピングします。

HMODULE LoadLibrary(
  LPCTSTR lpFileName
);

lpFileNameは、マッピングしたいDLLの名前を指定します。 戻り値は、マッピングされたDLLのアドレスです。 NULLのときは、DLLが存在しないなど何からの事情でマッピングに失敗したことを意味します。

DLLのマッピングを終えたら、そのDLLに実装されている関数を呼び出すことができます。 関数のアドレスは、GetProcAddressで取得できます。

FARPROC GetProcAddress(
  HMODULE hModule,
  LPCSTR lpProcName
);

hModuleは、ロードしたDLLのアドレスを指定します。 lpProcNameは、取得したい関数の名前を指定します。 この名前は、常にASCII文字列となります。 戻り値は、hModuleのDLLがlpProcNameで示される関数をエクスポートしている場合はその関数となり、 関数が存在しない場合はNULLになります。

特定のDLLをアドレス空間にマッピングする理由は、 そのDLLがエクスポートしている関数を呼び出したいからであり、 呼び出しが終了した場合はDLLのマッピングを解除しても問題ありません。 次に示すFreeLibraryは、指定されたアドレスのDLLのマッピングを解除します。

BOOL FreeLibrary(
  HMODULE hModule
);

hModuleは、マッピングを解除したいDLLのアドレスを指定します。 戻り値は、関数が成功した場合にTRUEとなります。 この関数を呼び出すと、第1引数で示されるDLLのマッピングは解除され、 もう第1引数のアドレスを使ってDLLにアクセスすることはできません。 DLLは多くのプロセスで共有されているものなので、 この関数を呼び出したからといって、本当にDLLが開放されるとは限りません。 参照カウントによってはDLLのマッピングが解除されない場合もあるのですが、 このことについては別の節で説明します。

今回のプログラムは、mscoree.dllがエクスポートするGetCORSystemDirectoryという関数を呼び出します。 mscoree.dllは.NET Frameworkがシステムにインストールされていない場合は存在しないため、 このようなDLLを扱いたい場合はDLLに暗黙的リンクするわけにはいきません。 LoadLibraryによる明示的リンクが必要です。

#include <windows.h>

typedef HRESULT (STDMETHODCALLTYPE *LPFNGETCORSYSTEMDIRECTORY)(LPWSTR, DWORD, DWORD *);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	WCHAR                     szBuf[256];
	DWORD                     dwLength;
	HMODULE                   hmod;
	LPFNGETCORSYSTEMDIRECTORY lpfnGetCORSystemDirectory;

	hmod = LoadLibrary(TEXT("mscoree.dll"));
	if (hmod == NULL) {
		MessageBox(NULL, TEXT("mscoree.dllが見つかりません。"), NULL, MB_ICONWARNING);
		return 0;
	}

	lpfnGetCORSystemDirectory = (LPFNGETCORSYSTEMDIRECTORY)GetProcAddress(hmod, "GetCORSystemDirectory");
	if (lpfnGetCORSystemDirectory == NULL) {
		MessageBox(NULL, TEXT("関数がエクスポートされていません。"), NULL, MB_ICONWARNING);
		FreeLibrary(hmod);
		return 0;
	}
	
	lpfnGetCORSystemDirectory(szBuf, sizeof(szBuf), &dwLength);
	
	MessageBoxW(NULL, szBuf, L"OK", MB_OK);

	FreeLibrary(hmod);
	
	return 0;
}

このコードの説明に入る前にもう一度、暗黙的リンクと明示的リンクの違いについて見てみましょう。 上記のコードは、できることなら次のように記述したかったのです。

#include <windows.h>
#include <mscoree.h>

#pragma comment (lib, "mscoree.lib")

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	WCHAR szBuf[256];
	DWORD dwLength;
	
	GetCORSystemDirectory(szBuf, sizeof(szBuf), &dwLength);

	MessageBoxW(NULL, szBuf, L"OK", MB_OK);

	return 0;
}

このコードでは、GetCORSystemDirectoryを呼び出すためにそれを定義しているmscoree.hをインクルードし、 mscoree.libにリンクするようにしています。 これにより、作成されたEXEファイルのインポートセクションには、 GetCORSystemDirectoryをエクスポートするmscoree.dllの名前が格納されるわけですが、 このDLLがどの環境にも存在することが保障されていないため、 EXEの起動時にエラーを起こす可能性があります。 よって、このような起動時による暗黙的リンクではなく、 任意の時点による明示的なマッピングが必要になるのです。

hmod = LoadLibrary(TEXT("mscoree.dll"));
if (hmod == NULL) {
	MessageBox(NULL, TEXT("DLLが見つかりません。"), NULL, MB_ICONWARNING);
	return 0;
}

LoadLibraryでmscoree.dllをアドレス空間にマッピングし、そのアドレスをhmodで取得します。 これにより、後はhmodを使ってmscoree.dllがエクスポートする関数のアドレスを取得できます。

lpfnGetCORSystemDirectory = (LPFNGETCORSYSTEMDIRECTORY)GetProcAddress(hmod, "GetCORSystemDirectory");
if (lpfnGetCORSystemDirectory == NULL) {
	MessageBox(NULL, TEXT("関数がエクスポートされていません。"), NULL, MB_ICONWARNING);
	FreeLibrary(hmod);
	return 0;
}

GetProcAddressを呼び出して、hmodからGetCORSystemDirectoryのアドレスを取得します。 このコードは、mscoree.dllがGetCORSystemDirectoryをエクスポートしていることを知っているから記述したのであって、 ここでGetProcAddressが失敗するようなことは考えられないようにも思えますが、そのような解釈はあまり好ましくありません。 新しいWindowsの登場に伴い、新しいDLLが追加されると共に以前から存在するDLLも更新されますから、 そのDLLに新しい関数が追加されることはよくあることです。 この場合、特定のDLLに特定の関数が存在するかどうかは、Windowsのバージョンによって異なるわけですから、 DLLのマッピングに成功しても、関数のアドレスを取得することに成功するとは限りません。 よって、GetProcAddressの戻り値は確認しておくべきであるといえるのです。 ちなみに、GetCORSystemDirectoryは.Net Framework1.0のmscoree.dllからエクスポートされている関数であるため、 関数が存在しないことによる失敗は起こりえないはずです。

GetProcAddressの戻り値は、LPFNGETCORSYSTEMDIRECTORYという型でキャストされています。 この型はGetCORSystemDirectoryのプロトタイプを表す関数ポインタであり、 型を持つ変数を宣言すればその変数で関数呼び出しを行えます。 GetCORSystemDirectoryは、.Net Frameworkがインストールされているパスを返す関数で、 次のように定義されています。

HRESULT GetCORSystemDirectory( 
  LPWSTR pbuffer,   
  DWORD cchBuffer, 
  DWORD* dwlength
); 

この定義を専用の型として定義します。

typedef HRESULT (STDMETHODCALLTYPE *LPFNGETCORSYSTEMDIRECTORY)(LPWSTR, DWORD, DWORD *);

これで、LPFNGETCORSYSTEMDIRECTORYがGetCORSystemDirectoryの呼び出しに利用できる型となりました。 STDMETHODCALLTYPEはGetCORSystemDirectoryの呼び出し規約ですが、 大抵の関数の場合はWINAPIという呼び出し規約を指定することになるでしょう。 次に、GetCORSystemDirectoryの呼び出しを確認します。

lpfnGetCORSystemDirectory(szBuf, sizeof(szBuf), &dwLength);

lpfnGetCORSystemDirectoryは、GetCORSystemDirectoryへの関数ポインタですから、 この変数で関数の呼び出しを行うことができます。 この時点では既に関数のアドレスを取得していますから、 ここでエラーが発生した場合は引数の不正によるものとなるでしょう。

今回のプログラムでは、DLLのマッピングやアドレス取得の失敗時にアプリケーションが終了することになっていますが、 実用的なプログラムの場合はあまり好ましい動作とはいえないでしょう。 たとえば、システム情報を表示するアプリケーションを開発するとして、 GetCORSystemDirectoryの明示的な呼び出しを考えていたとします。 実行時に、mscoree.dllをマッピングできなかった場合、 それが意味することは.Net Frameworkがインストールされているパスを取得できないということだけですから、 アプリケーションの終了などはせず、その情報の部分だけを空白すればよいでしょう。


戻る