ネタ元:WinUnitをマルチバイトコードで使うと実行時にエラー(わんくま掲示板)
WinUnit 便利っすね~。DLLになってさえいれば、さまざまなテストができます。おいらは、今のところはツールメニューに仕込んで使ってます。ビルド後イベントに入れてないのは、テストしたいDLLが、MFC拡張DLLだから...そのままじゃビルド後のイベントに使えないのよねぇw
なので、テスト専用のMFCレギュラーDLL(今は違う名前だったかな?)が、あります。<こいつを自動テストにすればいいんだが、今のところはまだやってないw
ちなみに、テスト関数にブレークポイントはっておけば、デバッグもできます。実はこっちの方が便利だったりするw<おい!
さて、DLLといえば、厄介なのがDLL同士の従属関係。古くからこのエラーで悩まされている人が後を絶たないのが現実です。
見たことありませんか?
「Hoge.dll が見つからなかったため、このアプリケーションを開始できませんでした。アプリケーションをインストールし直すとこの問題は解決される場合があります。 」
や
「指定されたモジュールが見つかりません。」
というメッセージ、もしくは 126 や 0x8007007E というエラー番号。
これらのエラーはすべて同じものです。WinError.h(SDKのヘッダーです)に、ERROR_MOD_NOT_FOUND という値で定義されているエラーです(上のパターンは異なる表現方法なので、全部挙げてみましたw)。
なぜこれらのエラーが発生するのでしょうか?
最初のメッセージは、親切にもDLL名を挙げていますので、わかりやすいですね。「Hoge.dll が見つからなかった」だからロードできなかったと。こちらは、エンドユーザー向けのメッセージで、カーネルがロード失敗時に出してくれるメッセージです。まだ、前世紀のころこのメッセージボックスが中身からっぽで出てくるというOSがあったんですがねw
ちなみに、ネタ元は、2個目のメッセージが表示されていました。
では、なぜこのようなエラーが出るんでしょうか?
今回の場合は、WinUnit に指定したDLL(仮にTest.dll)が、Hoge.dll を参照していたために発生しています。
Test.dll と Hoge.dll は同じフォルダにあるにもかかわらず、「なぜかHoge.dllを見つけることができず」結果として Test.dll のロードに失敗してしまったのが原因です。
では、なぜロードできないのでしょうか?(ちなみに、Test.dll はフルパスで指定されています)
これ、おいらもまったく同じ原因でエラーがでて、えええええええ!って感じだったんですが...
原因は、WinUnit が DLLをロードする際、LoadLibrary API でロードしていたのが原因です。
ではなぜ、これでエラーが出るのでしょうか?それは、Windows の DLLサーチパスに起因しています。
MSDN Library(もちろん英語)に Dynamic-Link Library Search Order とそのものずばりのページがありました(昔はなかったんだが...<いつの時代だよ!)
標準のDLL検索パスは、
- アプリケーション(EXE、すなわちホストプロセス)のあるディレクトリ
- システムディレクトリ。GetSystemDirectory API で取得できるディレクトリ
- 16bit 版のシステムディレクトリ(互換性に起因するもの。64bitOSだとたぶん使われないと思いますが正直不明w)
- Windows ディテクトリ。GetWindowsDirectory API で取得できるディテクトリ
- カレントディテクトリ
- PATH 環境変数に指定されているフォルダ(SafeDllSearchMode を操作すると除外可能)
- レジストリの、App Paths キーで指定されている特別なフォルダ(インストーラで設定されます)
となっています(詳しくは原文参照)。個人的には、3や4はもういらねーだろう?とは思うんですが、なにせ歴史のあるOSなのでそうも言えない互換性の問題があったりするんですよねw
よく見てください。上記のリストには、dll をロードしたパスは含まれていません。これが原因なんですね。
ではどうすればいいのでしょうか?
こちらは、Alternate Search Order の項にあるように、LoadLibraryEx API で、LOAD_WITH_ALTERED_SEARCH_PATH を指定するか、SetDllDirectory API(Vista以降)を使ってDLL検索パスを追加する方法で、解決することができます。
LoadLibraryEx を利用すると、標準検索パスの1番が、LoadLibraryEx で指定されたDLLと同じフォルダに切り替わります(DLLがフルパス指定されている場合)。
SetDllDirectory を利用した場合は、標準DLL検索パスの1と2の間で、SetDllDire で指定したパスを検索します。
ちなみに、MFC(DLLでリンクしている場合)では、LoadLibrary の代わりに、AfxLoadLibrary を、LoadLibraryEx の代わりに AfxLoadLibraryEx を使うと、安全にMFC拡張DLLをロードすることができます(マルチスレッドなアプリの場合だけですがw)。
あ、AfxLoadLibraryEx はリファレンスないのね。アンドキュメントですか...w