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

技術ネタの備忘録です。基本的に私が忘れないためのものです。他の人の役にも立つといいなぁ。

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 でヘッダファイルをインクルードしたり、
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() 関数は大文字小文字を区別することくらいでしょうか。