CreateProcess によるアプリ実行時の必要モジュールの検索順序 – プログラミング – Home

CreateProcess によるアプ...
 
通知
すべてクリア

[解決済] CreateProcess によるアプリ実行時の必要モジュールの検索順序


ミミ
 ミミ
(@ミミ)
ゲスト
結合: 25年前
投稿: 63
Topic starter  

いつもお世話になります。
ミミです。

環境は 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の事だと
思っていました。
この認識は間違っていませんでしょうか?

長文、そして分かりにくい質問かもしれません。
何卒ご指導のほどよろしくお願いいたします。


引用未解決
トピックタグ
wood
 wood
(@wood)
ゲスト
結合: 23年前
投稿: 895
 

以前似たような質問をしたことがあって

カレントとモジュール ディレクトリ
は意味が違います、と言う指摘を受けたことがありますよ

最近プログラミングしてないので、有識者のご意見をお待ちください


返信引用
KING・王
 KING・王
(@KING・王)
ゲスト
結合: 21年前
投稿: 122
 

だいぶ推測なのですが、こんな感じではないでしょうか?
#識者の方々、フォローをお願いします。

ア.
> フォルダ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がアプリケーションがロードされたディレクトリになっていると思います。


返信引用
ミミ
 ミミ
(@ミミ)
ゲスト
結合: 25年前
投稿: 63
Topic starter  

woodさま、KING・王さま、ご返信ありがとうございます。
ミミです。(遅くなってしまい申し訳ありません。)

ア.に対するご意見、大変参考になりました。
私が所持するMSDNでは、
1.アプリケーションがロードされたディレクトリ
と記述されていた為、解釈を誤っていた様です。

ロード元と考えると、なるほど、確かにフォルダAの事を
まず見に行くと考えられますね。
# 実際にそのようなテスト結果(上記1~3の事例)もあるので、
# さらに納得できます。

イに関して、今回は静的リンクの為、コンパイル時に lib を指定しています。
(プロジェクトの設定にて。)
「libの内容が変更されていなければ」というのが少々分からないのですが、
「dllだけの差し替えでバグの修正などができるのが、dllの利点だった」という事より、
今回の例では、(test1.exe は Ver1のlibをリンクしていますが) 実際に
Ver2 の dll と使用するのは、問題ないと解釈しました。
違っていればご指摘ください。

ウのご意見で、アのご意見がさらに確信へとつながった気がします。
「エクスプローラーで操作する、即ちカレントはフォルダBですが、
 フォルダAからCreateProcess()する時の、カレントはフォルダA。」
…納得です。言われてみればそうですよね。
CreateProcess()で実行されたアプリは、当然そのフォルダから検索するもの
と勘違い&思いこんでいたようです。

woodさまからのご指摘にもありました、「カレント」と「モジュールディレクトリ」
の関係を今後調べてみます。

おかげさまをもちまして、本件の質問は解決しました。
いろいろとありがとうございました。


返信引用
ミミ
 ミミ
(@ミミ)
ゲスト
結合: 25年前
投稿: 63
Topic starter  

自分の投稿した日本語がヘンでしたので修正します。

(誤)
「エクスプローラーで操作する、即ちカレントはフォルダBですが、
 フォルダAからCreateProcess()する時の、カレントはフォルダA。」

(正)
「エクスプローラーを操作して、フォルダB内のtest2.exeを実行した際、
 カレントはフォルダBですが、
 フォルダAからtest1.exeを実行した以上、test1.exe が CreateProcess()
したとしても、カレントはフォルダAのまま。
 即ち、明示的にカレントを変更しない限り、フォルダAを探す。」


返信引用
とと
 とと
(@とと)
ゲスト
結合: 20年前
投稿: 11
 

イに関して。

まず、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に関して色々知っておく必要があります。

ある機能をライブラリ化して使用したいが、バージョンの互換性とかを気にしたくない!!の
であれば
スタティックライブラリで作成し、使用するほうが安全ではあります。


返信引用

返信する

投稿者名

投稿者メールアドレス

タイトル *

プレビュー 0リビジョン 保存しました
共有:
タイトルとURLをコピーしました