Hatena::ブログ(Diary)

ほっしーの技術ネタ備忘録

2009/05/31 DLL の遅延ロード

前回は珍しく社会ネタを書いたのでまたしばらくは技術ネタに戻りまする。


あとデザインを一新してみました。プロフィールも少しきちんと。

mixi とか twitter とかもプロフィールを直したりしてるのでその流れで。

[] DLL の遅延ロード  DLL の遅延ロードを含むブックマーク  DLL の遅延ロードのブックマークコメント


例えば WinTab32.dll ( ペンタブレットライブラリ ) のように、すべての環境に入っているとは限らない DLL を使いたいとき。

何も考えずに WinTab32.lib をリンクしてしまうと、DLL が入っていない環境では

DLL存在しない旨のメッセージが表示されて、main(), WinMain() すら呼ばれません。


これではさすがにあんまりだし、無いならないで一部機能を無効にして起動したい場合は、

一般的には LoadLibrary(), GetProcAddress() API を用いて解決します。


typedef BOOL __stdcall SetLayeredFunc( HWND hwnd, COLORREF crKey, BYTE bAlpha, DWORD dwFlags );
extern SetLayeredFunc *SetLayeredWindowAttributes;

HINSTANCE hDllInst = LoadLibrary("user32.dll"); 
if( hDllInst == NULL ){ 
	MessageBox( "USER32.DLLが読み込めませんでした。","Error", MB_OK|MB_ICONSTOP ); 
}
SetLayeredWindowAttributes = (SetLayeredFunc*)GetProcAddress( hDllInst, "SetLayeredWindowAttributes" ); 
if( SetLayeredWindowAttributes == NULL ){ 
	FreeLibrary(hDllInst);
	MessageBox( "SetLayeredWindowAttributes()関数のポインタが取得できませんでした。","Error", MB_OK ); 
}

例えばこんな感じ。でもこれだとグローバル関数ポインタ型と関数ポインタを持つ必要があったり、

明示的に LoadLibrary(), FreeLibrary() する必要があって面倒です。


そこで、link.exe にある /DelayLoad スイッチを使います。

使い方はリンカオプションに「/DelayLoad:WinTab32.dll」のように書き足すだけ。


んで、実際にロードしたり読み込みエラーを検出するためにこんなコードをどこかに仕込みます。

#include <delayimp.h>

struct dllload_error : public runtime_error {
	dllload_error() : runtime_error( "DLLLOAD_ERROR" ) { }
};

// エラーフックハンドラ
FARPROC WINAPI hookDLLLoad( unsigned dliNotify, PDelayLoadInfo pdli )
{
	throw dllload_error();
};
extern PfnDliHook __pfnDliFailureHook2 = hookDLLLoad;

// 起動時にロードを仕掛けておく
class CWinTab32Loader {
public:
	CWinTab32Loader() {
		try {
			HRESULT hr = __HrLoadAllImportsForDll( "WINTAB32.dll" );
			bLoadSuccess = !FAILED( hr );

			if( bLoadSuccess ) {
				_RPTF0( _CRT_WARN, _T("DynamicLink: WinTab32.dll success.") );
			}
		} catch( dllload_error& ) {
			bLoadSuccess = false;
		}
	}
	inline bool IsSuccess() { return bLoadSuccess; }
private:
	bool bLoadSuccess;
} WinTab32Loader;

それ以外は通常通り、#include <wintab.h> でヘッダファイルインクルードしたり、

WinTab32.lib も一緒にリンクしたり、普通DLL を使う場合と同じで使えます。


WinTab32.dll存在しなくても、メイン関数が呼ばれるので

int main() {
	if( WinTab32Loader.IsSuccess() ) {
		AXIS axPressureScale;
		WTInfo( WTI_DEVICES, DVC_NPRESSURE, &axPressureScale );
	} else {
		printf( "wintab32.dll not found!\n" );
	}
}

とこんな風に DLL 内の関数を呼び分けたり、いろいろできます。


注意点としては、__HrLoadAllImportsForDll() 関数大文字小文字を区別することくらいでしょうか。

トラックバック - http://d.hatena.ne.jp/Hossy/20090531