以下構成のプログラムでDLL1が存在しない時に、DLL2側の異常処理が予定通り動作するか
確認しようとしたのですが、
VB6製のプログラムを実行すると
>『実行時エラー'53'』:
>ファイルが見つかりません。
>c:\xxxxxxxxxxDLL3.dll
が表示されます。
DLL1が存在しない設定にする前まではDLL1~3まで正常に呼び出せることを確認していますので
原因が解りません。
また、DLL1をインストールすると上記エラーは発生しなくなります。
この現象について調査する方法(もしくは説明)などを教えて頂きたく宜しくお願いします。
<プログラムの構成>
・???製のDLL1・・・・API群のDLL(既存リソース)
・VC6製のDLL2・・・・DLL1を簡単に使うためのラッパーDLL(既存リソース)
・VC6製のDLL3・・・・DLL2をVBから呼び出すためのラッパーDLL
・VB6製のプログラム・・・DLLにパラメータを渡し処理結果を表示する
<処理の流れ>
VB6製プログラム→VC6製のDLL3→VC6製のDLL2→???製のDLL1
備考1:DLLが全て存在する場合はプログラムからDLL1~3まで正常に呼び出せることを確認している。
DLL3、DLL2は、依存するDLLに対し、pragma commentによるlib指定か、リンクオプションでlibを指定していると推測します。
これは「暗黙的リンク」と呼ばれます。
http://msdn.microsoft.com/ja-jp/library/253b8k2c(VS.80).aspx
動的にリンクされた参照を含むプログラムが起動されると、プログラムの実行可能ファイル内の情報に従って、必要な DLL を探します。DLL が見つからないと、システムは処理を停止し、ダイアログ ボックスを表示して、エラーを報告します。見つかった場合は、DLL モジュールがプロセスのアドレス空間に割り当てられます。
今回の場合、DLL3のロードで、DLL2も同時にロードされます。同様にDLL2のロードでDLL1がロードされます。
----
遅延ロードの指定を試してください。
昔は無かった機能ですが、pragmaの指定で「明示的リンク」を代替できます。
http://msdn.microsoft.com/ja-jp/library/151kt790(VS.80).aspx
DLL3のどこかに
#pragma comment(lib, "delayimp.lib") #pragma comment(linker, "/DELAYLOAD:dll2.dll")
と、書いておき、
DLL2には
#pragma comment(lib, "delayimp.lib") #pragma comment(linker, "/DELAYLOAD:dll1.dll")
と、書いておきます。
このpragma指定で、DLL内関数を使用する直前でDLLがロードされるようになります。
(DLL3をロードするとき、DLL2はロードされない)
(DLL2をロードするとき、DLL1はロードされない)
結果、DLL3のロードでDLL1の不在例外が上がることはありません。
(DLL1のDLL内関数を使用する直前で例外が上がる)
DLL3、DLL2は、依存するDLLに対し、pragma commentによるlib指定か、リンクオプションでlibを指定していると推測します。
これは「暗黙的リンク」と呼ばれます。
http://msdn.microsoft.com/ja-jp/library/253b8k2c(VS.80).aspx
動的にリンクされた参照を含むプログラムが起動されると、プログラムの実行可能ファイル内の情報に従って、必要な DLL を探します。DLL が見つからないと、システムは処理を停止し、ダイアログ ボックスを表示して、エラーを報告します。見つかった場合は、DLL モジュールがプロセスのアドレス空間に割り当てられます。
今回の場合、DLL3のロードで、DLL2も同時にロードされます。同様にDLL2のロードでDLL1がロードされます。
----
遅延ロードの指定を試してください。
昔は無かった機能ですが、pragmaの指定で「明示的リンク」を代替できます。
http://msdn.microsoft.com/ja-jp/library/151kt790(VS.80).aspx
DLL3のどこかに
#pragma comment(lib, "delayimp.lib") #pragma comment(linker, "/DELAYLOAD:dll2.dll")
と、書いておき、
DLL2には
#pragma comment(lib, "delayimp.lib") #pragma comment(linker, "/DELAYLOAD:dll1.dll")
と、書いておきます。
このpragma指定で、DLL内関数を使用する直前でDLLがロードされるようになります。
(DLL3をロードするとき、DLL2はロードされない)
(DLL2をロードするとき、DLL1はロードされない)
結果、DLL3のロードでDLL1の不在例外が上がることはありません。
(DLL1のDLL内関数を使用する直前で例外が上がる)
回答ありがとうございました。
とりあえず
VB側にDLL呼び出し失敗のときのための
goto Errorを作成していましたが
原因が分からず、非常に気になっている問題でした。
ご指摘の通り、
以下のDLLはlibを指定してリンクしています。
・VC6製のDLL2・・・・DLL1を簡単に使うためのラッパーDLL(既存リソース)
・VC6製のDLL3・・・・DLL2をVBから呼び出すためのラッパーDLL
遅延ロードを行えば、DLL内で異常処理も記述できるのですね。
すっきりしました。
VC6のDLLは静的リンクをしているようですね。
静的リンクの場合、カーネルはプログラムの開始時にすべてのDLLをロードし、アドレス解決を行います。
そのため、VB6→DLL3→DLL2→DLL1のようにDLLが静的リンクで依存関係になっている場合、ロード順(アドレス解決順)は、DLL1→DLL2→DLL3→VB6の順番で行われます。(DLL1が一番最初にロードされる)
そのため、DLL1自体が存在しない場合は、DLL2がロードできず、DLL2がロードできないとDLL3がロードできず、DLL3がロードできないため、結果的に「DLL3がロードできない」というエラーになります。(「ファイルが見つからない」というのは本来あまり適切なメッセージではありません。正しくは、「リンクしているDLLのいずれかのファイルが見つからない」とでも言うべきです)
DLL1が存在しないことをDLL2で検出して異常処理を行いたいということであれば、DLL2からDLL1を静的リンクせず実行時リンクを行う必要があります。
静的リンクの場合は、カーネルがすべてのアドレス解決を行いますので、DLLが存在しない場合は検出できません(プログラム自体起動できない)
実行時リンクは、DLLのアドレス解決をカーネルに任せるのではなく、自分自身が行う必要があります。LoadLibraryでDLLを指定してロードし、GetProcAddressでDLL内の関数へのポインタを取得します。DLLが存在しない場合はLoadLibraryがエラーコードを返しますので、適切な異常処理を行うことができます。
静的リンクではなく実行時リンクを使うには、例えば以下のようなページを参考にしてください。
VC++「DLL作成/呼出方法」メモ(Hishidama's VC++Memo "DLL")
「実行時リンク LoadLibrary」等のキーワードで検索してもたくさん見つかります。
なお、DLL内のDllMainは、そのDLLが完全にロード完了(静的リンクしているすべてのDLLがロードされアドレス解決された状態)した後に初めて呼び出されますので、静的リンクしているDLLの1つでもロードできなかった場合はロードが完了しない為DllMainも呼ばれません。
解りやすい解説ありがとうございます。
原因が分からず、非常に気になっていたのですが、
静的リンクの場合は、
『プログラムの開始時にすべてのDLLをロードし、アドレス解決』を行っていたのですね。
アドレス解決の順序などもしらなかったためエラーメッセージに首をかしげていたのですが
お蔭様で少し納得することができました。
>>DLL2からDLL1を静的リンクせず実行時リンクを行う必要があります。
具体的なご指摘ありがとうございます。
DLL2は既存リソースなので手を加えない予定ですが、
対応方法が分かっただけでも嬉しいです。
回答ありがとうございました。
とりあえず
VB側にDLL呼び出し失敗のときのための
goto Errorを作成していましたが
原因が分からず、非常に気になっている問題でした。
ご指摘の通り、
以下のDLLはlibを指定してリンクしています。
・VC6製のDLL2・・・・DLL1を簡単に使うためのラッパーDLL(既存リソース)
・VC6製のDLL3・・・・DLL2をVBから呼び出すためのラッパーDLL
遅延ロードを行えば、DLL内で異常処理も記述できるのですね。
すっきりしました。