いつもお世話になります。
ミミです。
環境は VC++6.0 です。
+フォルダA+ +フォルダB+
| | | |
|test1.exe | |test2.exe |
|hoge.dll | |hoge.dll |
| (Ver1)| | (Ver2)|
+-----+ +-----+
test1.exe は、フォルダAの hoge.dll を、
test2.exe は、フォルダBの hoge.dll を、
静的リンクする様にコンパイルしています。
(プロジェクトの設定、リンクタブ中でライブラリを指定しています。)
また、フォルダAとフォルダB内の hoge.dll は同一ではなく、
フォルダBの hoge.dll が新しい(新たに関数が追加されている)もの(Ver2)とします。
このとき、エクスプローラーで、
・フォルダAを開き、test1.exeを実行
・フォルダBを開き、test2.exeを実行
する場合は何ら問題はありません。
ですが、test1.exe 中で、CreateProcess()により、test2.exe を起動しようとすると、
GetLastError()値が193で返ってきてしまい、実行する事ができません。
原因を探るために、以下の手順を踏みました。
1.フォルダAの hoge.dll を Ver2 のものに置き換え、
フォルダBの hoge.dll を Ver1 のものに置き換える。
→ エクスプローラーによるフォルダB内の test2.exe の実行はできませんが、
(GetLastError=193)test1.exe からの CreateProcess() は成功しました。
2.フォルダAの hoge.dll を Ver2 のものに置き換え、
フォルダBの hoge.dll を削除する。
→ エクスプローラーによるフォルダB内の test2.exe の実行はできませんが、
test1.exe からの CreateProcess() は成功しました。
→ 即ち、フォルダBの中の hoge.dllは参照していない
3.フォルダA、フォルダB共に、hoge.dll を Ver2 にする。
→ test1.exe からの CreateProcess()、
フォルダA・B共にエクスプローラーからの起動、成功しました。
(尚、フォルダAの hoge.dll は削除できません。
静的リンクの為、hoge.dllが無いと、test1.exeが実行できない為。)
今回ご質問したいのは以下の3点です。
ア.
test1.exe から test2.exe を CreateProcess()により実行しようとした時、
かつ test2.exe で必要とする dll が、test1.exe と同じ箇所(フォルダA)
に既に存在していた場合、フォルダA を優先的に参照すると自分なりに
結論を出しましたが、これで正しいのでしょうか?
イ.
test1.exe は、Ver1 のライブラリを指定してコンパイルされていますが、
実際に Ver2 の dll を使用して動くというのは、たまたまなのでしょうか?
(上記1~3の例)
ウ.
私は現在、CreateProcess() の第2引数(lpCommandLine)には NULL を指定しています。
MSDN では、NULLの時は
1.アプリケーションがロードされたディレクトリ
2.…(略)
とありました。
個人的には、test1.exe が test2.exeを実行するなら、
「アプリケーションがロードされたディレクトリ」というのはフォルダBの事だと
思っていました。
この認識は間違っていませんでしょうか?
長文、そして分かりにくい質問かもしれません。
何卒ご指導のほどよろしくお願いいたします。
以前似たような質問をしたことがあって
カレントとモジュール ディレクトリ
は意味が違います、と言う指摘を受けたことがありますよ
最近プログラミングしてないので、有識者のご意見をお待ちください
だいぶ推測なのですが、こんな感じではないでしょうか?
#識者の方々、フォローをお願いします。
ア.
> フォルダA を優先的に参照する
フォルダAを優先的に参照するのではなく、LoadLibrary()の解説にあるように、
1.アプリケーションのロード元ディレクトリ
2.カレンドディレクトリ
(以下略)
の順番にで検索をかけるため、1.に該当し、
フォルダAのDLLがロードされていると思われます。
ここで、test2.exeはCreateProcess()が実行された時のカレントディレクトリが、
test2.exeのアプリケーションのロード元ディレクトリになると思われます。
#CreateProces()の実行前にtest1.exe内でカレントディレクトリをフォルダBに変更すれば、
#アプリケーションのロード元ディレクトリもフォルダBになるような気が・・・(あくまで推
測)
イ.
libの内容が変更されていなければ、dllの内容が変更されても大丈夫だと思います。
(だから、exeを変更しなくても、dllだけの差し替えでバグの修正などができるのが、
dllの利点だったと・・・)
libの内容が変更される条件などは分かりかねますが・・・
ウ.
上で書きましたが、フォルダBはあくまで、test2.exeが存在するフォルダであって、
test2.exeをロードしているのは、test1.exeのカレントのディレクトリなので、
フォルダAがアプリケーションがロードされたディレクトリになっていると思います。
woodさま、KING・王さま、ご返信ありがとうございます。
ミミです。(遅くなってしまい申し訳ありません。)
ア.に対するご意見、大変参考になりました。
私が所持するMSDNでは、
1.アプリケーションがロードされたディレクトリ
と記述されていた為、解釈を誤っていた様です。
ロード元と考えると、なるほど、確かにフォルダAの事を
まず見に行くと考えられますね。
# 実際にそのようなテスト結果(上記1~3の事例)もあるので、
# さらに納得できます。
イに関して、今回は静的リンクの為、コンパイル時に lib を指定しています。
(プロジェクトの設定にて。)
「libの内容が変更されていなければ」というのが少々分からないのですが、
「dllだけの差し替えでバグの修正などができるのが、dllの利点だった」という事より、
今回の例では、(test1.exe は Ver1のlibをリンクしていますが) 実際に
Ver2 の dll と使用するのは、問題ないと解釈しました。
違っていればご指摘ください。
ウのご意見で、アのご意見がさらに確信へとつながった気がします。
「エクスプローラーで操作する、即ちカレントはフォルダBですが、
フォルダAからCreateProcess()する時の、カレントはフォルダA。」
…納得です。言われてみればそうですよね。
CreateProcess()で実行されたアプリは、当然そのフォルダから検索するもの
と勘違い&思いこんでいたようです。
woodさまからのご指摘にもありました、「カレント」と「モジュールディレクトリ」
の関係を今後調べてみます。
おかげさまをもちまして、本件の質問は解決しました。
いろいろとありがとうございました。
自分の投稿した日本語がヘンでしたので修正します。
(誤)
「エクスプローラーで操作する、即ちカレントはフォルダBですが、
フォルダAからCreateProcess()する時の、カレントはフォルダA。」
(正)
「エクスプローラーを操作して、フォルダB内のtest2.exeを実行した際、
カレントはフォルダBですが、
フォルダAからtest1.exeを実行した以上、test1.exe が CreateProcess()
したとしても、カレントはフォルダAのまま。
即ち、明示的にカレントを変更しない限り、フォルダAを探す。」
イに関して。
まず、DLLの静的リンクと動的リンクの違いが関係あります。
そして、C++の特徴:関数のオーバーロードに関係があります。
静的リンク:プログラムロード時に、DLLをロードし、暗黙的に関数コールを行う。
動的リンク:任意の時にロードを行い、明示的に関数コールを行う。
関数のオーバーロード:同一名(引数は違う)関数を宣言することを可能としている。
さて、ここで疑問が生じてきます。
動的リンク時に、DLLをロード後関数をコールを行う場合、
ロードしたDLL内から関数名で関数ポインタを検索・取得しますが
同一名の関数があった場合、どちらの関数なのかあいまいになってしまいます。
そこで、オーバーロードされた関数のために、C++では全ての関数名を
コンパイラが勝手に装飾して、関数名を変えてしまうのです。
void FuncA(char a);
void FuncA(long a);
と2つ宣言していても、コンパイル時に装飾が行われ
void FuncA@XXXXXX(char a);
void FuncA@XXXXXX(long a);
と変更されてしまいます。(XXXXXXにランダムな英数文字列になります)
こうすることにより、同一名の関数を宣言しても、内部的には違う関数名になっているので
表向きは同一名の関数を宣言(オーバーロード)することを実現しています。
以上を踏まえた上で、、、
LibにはDLL名とそのDLLが提供する関数名(内部的な関数名)など
静的リンクに必要な情報が含まれています。
この情報を使い、コンパイラは、コンパイル時にコードを構築します。
*つまり、FuncA( 1 );というDLL関数を呼び出している部分は、Libを参照に
FuncA@XXXXXX( 1 );に置き換えて、コンパイルする感じです。
Libが更新される時は、DLLのエクスポートヘッダーが変更された時だと思います。
総論:
既存のエクスポートをそのままに、新たなエクスポートを追加したDLLを使用する分では
下位互換を維持できますので、なんら問題ありません。
*「既存のエクスポートをそのまま」というのは、この場合Ver1のエクスポート部分を
一切変更していない場合です。
より詳しく知りたければDLL Hell等について調べてみてください。
DLLのバージョンアップ(特にエクスポートクラスがある場合)は
注意しておかないと、メモリリークやDLL Hellを引き起こす可能性がありますので
DLLを使用するならば、DLLに関して色々知っておく必要があります。
ある機能をライブラリ化して使用したいが、バージョンの互換性とかを気にしたくない!!の
であれば
スタティックライブラリで作成し、使用するほうが安全ではあります。