タイトルの疑問を解消すべく下記の検証をしたのですが、行き詰ってしまった為 皆様の
知恵と経験をお借りしたく投稿させて頂きました。
情報( 例 これを調べろ・誤解している・検証方法が間違っている )をお持ちでしたら、
どうかお教え頂けないでしょうか。宜しくお願い致します。
【検証1】
例えば kernel32.dll に実装されている LoadLibraryA() を例に採る。
Visual Studioに付くdumpbin.exeを使い、OS毎に↓のコマンドで LoadLibraryA() の序数
を調べる。
dumpbin /exports kernel32.dll | grep LoadLibraryA ( grep は Cygwin 1.7.17 の物を
使用 )
すると、OSの版毎にバラバラな事が分かった↓
・WinXP SP3 32[bit] Pro. の system32 フォルダで実行
581
・Win7 SP1 64[bit] Pro. の system32・SysWOW64 フォルダで実行
830
・Win8 64[bit] Pro. の system32・SysWOW64 フォルダで実行
961
ある環境( 例 WinXP )で LoadLibraryA() を使うアプリをビルド( 勿論 左記APIは暗黙的
リンク )して動いた物が、序数の互換性がない他環境( 例 上記Win7 )で正しく動いた。
動くのは当然としても、序数が異なる為 疑問。
【検証2】
「.exe と .dll を作り動作確認後、.dll だけ更新し 再び動作確認する」方法(下記)で
調べた。
結果、エクスポート要素と序数の対応が 新旧dllの間で変わると、期待通り 正しく動か
なくなってしまう事が確認できた。
1) test.dll に a() b() というエクスポート関数を用意。
序数は __declspec(dllexport) で自動生成( 関数名の文字コード順に、1から振られ
る )した。
その結果 序数は、a() が 1、b() が 2 を振られた。
2) a() b() を順に呼ぶ test.exe を作り、↑ を暗黙的リンクし動作確認。予想通りに動
いた。
3) test.dll に _() というエクスポート関数を __declspec(dllexport) を伴って追加
し、dllのみビルドした。
その結果 序数は、_() が 1、a() が 2、b() が 3 を振られた。
4) ( ビルドせず )test.exe を実行すると、予想通り _() a() の順で呼ばれた。
DLL内の各関数の序数の振られ方が、 旧DLL 1) と 新DLL 3) の間で変わった( = 後方
互換性がなくなった )為。
それにより、ローダによって解決されるインポートアドレステーブルの各要素の結合
先が変わり、異なる関数が呼ばれた。
上記2つの検証結果が矛盾した所で、それ以上の調査で手詰まりになってしまった。
簡単な物言いになってしまいますが、自前で序数が管理できないものは序数など使わな
ければ良いだけです。
Export時に名無しのAPIなら序数以外に指定方法がありませんが、「名前」さえ付けれ
ば序数が分からなくてもAPIのアドレスの取得が可能です。
ですから、test.dllの各APIに名前をつけてあげてください。
(プロジェクト構成が分かりませんが、多分「test.def」というファイルで定義される
べきと想像してます)
取得方法を具体的に説明すると、GetProcAddress()を使って名前でアドレスを取得する
ようにすると序数に振り回されることはありません。
http://msdn.microsoft.com/ja-jp/library/cc429133.aspx
あと、「静的リンク」「動的リンク」という単語をキーワードに、DLLの使い方を学ば
れると良いかと思います。
関数のエクスポートには、
1.名前でエクスポート。__declspec(dllexport) を使用
2.序数のみでエクスポート。*.def に func @N して、NONAMEを指定
3.両方できるようにエクスポート
がありますね。NONAMEを指定しない限り、
4.名前で解決できる関数テーブルが作られる
わけですが、
5.序数で解決できる関数テーブルも必ず作られる
わけです。NONAMEを指定すると名前で解決できる関数テーブルが
作成されないので、DLLのサイズが節約できます。
また、@Nを指定しないと、自動的に序数が割り当てられます。
さて、今日では一般に序数によるリンクはまったく行われません。
これは、名前による解決のほうが、融通が利くからですね。
当たり前ですが、名前による解決を行う場合は、
その関数に割り当てられている序数はまったく参照されません。
Export側だけでなく,実行ファイルのImportテーブルも見た方が良いですし,Dependency
Walkerで見た方がわかりやすいです。
ちなみに,【検証2】の通りにやっても,正しい関数が呼び出されます。
# 【検証2】に.DEFファイルでNONAMEを指定した,という記述がないので,__declspec
(dllexport)だけを使ってエクスポートしている
この場合,Dependency Walkerで実行ファイルを見ると,
・OrdinalはN/A
・Hintが0と1
・Functionがaとb
・Entry PointがNot Bound
という表示になります。
# dumpbin /importsで見ても,0と1の表示であることから,ordinalではなくhintが書かれ
ていることがわかる
ここの説明を信じてはいけません。
http://msdn.microsoft.com/ja-jp/library/253b8k2c(v=vs.80).aspx
•明示的リンクの場合、アプリケーションとインポート ライブラリをリンクする必要は
ありません。DLL 内の変更によってエクスポート序数が変更される場合、明示的リンク
を使うアプリケーションをリンクし直す必要はありません (序数値ではなく関数の名前
を使って GetProcAddress を呼び出すと仮定した場合)。他方、暗黙的リンクを使うアプ
リケーションは、新しいインポート ライブラリとリンクし直す必要があります。
んー
1.windowsのdllは序数値がOSバージョンによって変わっている。
2.自前でEXEとDLLを作って試してみたけど、序数値が変わるとうまくいかない。
3.windows APIは序数値が違うのになぜ上位互換性があるのだろう。
4.動的リンクで使うLoadLibraryA()等の関数が入っているkernel32.dllでさえ
序数値が違う。
どういう仕組みになっているのかわからなくなった。
ということみたいです。
僕としては推測ですが、
kernel32.dllだけはそれぞれOS独自の暗黙的リンクにで関数が読み出されて後は動的
リンクで必要に応じて読み出される。だと思いますね。
exeが実行される順番は、
1.システムコールが実行されて、仮想アドレス空間が用意される(ntdll.dllかも)
2.exeの本体部分がマッピングされ、kernel32.dllがマッピングされ、
プロセスが開始される。
3.exeの参照テーブルを見て、暗黙に参照されている全てのdllがマッピングされる。
4.exeのwmainCRTStartup()の実行->WinMain()の実行
という順番だと思います(vv;)。
従って、exeがなんかやり始めた時点では既に暗黙に参照されている全てのdllの
マッピングとダイナミックなリンクが完了しているわけですね。
現在ではLoad On Callはできないみたいですし。
> ここの説明を信じてはいけません。
この書き方は変でした。
msdnの記述がおかしいということです。