読者です 読者をやめる 読者になる 読者になる

DLL の闇 (5)

続き物.

何か,掘れば掘るほど不思議な挙動が出てくるような.
テストは Windows XP SP2 と,Windows Vista build 5456 にて.実験項目が多いので,個々の結果については信用する前に各自確かめるようにしてください.

絶対パス指定で,KnownDLL と同じベースファイル名を持つ DLL を呼び出せる

LoadLibrary("c:\\priv\\wininet32.dll");

システムディレクトリの wininet32.dll ではなく c:\priv\wininet32.dll がロードされる.絶対パス指定への介入方法は少なく,現在把握しているのは .local によるリダイレクションのみかな.

KnownDLL が無視されるのはどんな場合?

色々.KnownDLL よりも優先度が高いのは,少なくとも,manifest による場所指定,.local によるリダイレクション,絶対パス指定の 3 つ.他にもベースファイル名のみを指定した場合,既に同じベースファイル名の DLL が読み込まれていたときに最初に読み込まれた DLL を返すというルールの方があるが,これも KnownDLL より優先度が高い.例えば以下の mod1 と mod2 は同じものが返る (これ以前に wininet32.dll がロードされていなかった場合).

// mod1 と mod2 は同じ
HMODULE mod1 = LoadLibrary("c:\\priv\\wininet32.dll");
HMODULE mod2 = LoadLibrary("wininet32.dll");

Windows のパス形式の不思議

CreateFile には,ファイル名末尾の空白と"."を取り除くという特殊ルールがある (Windowsパス名の落とし穴).
また,LoadLibrary にはファイル名の末尾に"."を記述したときの特殊ルールがあります.

lpFileName パラメータでファイル名の拡張子を省略した場合、既定の拡張子として「.DLL」が追加されます。文字列の最後に「.」を記述すると、拡張子なしのモジュール名になります。拡張子を省略した場合、この関数は、ロード済みモジュールのファイル名本体と、ロードしたいモジュールのファイル名本体だけを比較します。ファイル名本体どうしが一致した場合、ロードに成功します。それ以外の場合、この関数は次の順序でファイルを検索します。

これに関連してか,いくつか不思議な挙動が見え隠れ.
c:\priv\mydll.dll があると仮定して.

// mod1 と mod2 は同じものが返る
HMODULE mod1 = LoadLibrary("c:\\priv\\mydll.dll");
HMODULE mod2 = LoadLibrary("c:\\priv\\mydll.dll");
// mod3 と mod4 は同じものが返る
HMODULE mod3 = LoadLibrary("c:\\priv\\mydll.dll");
HMODULE mod4 = LoadLibrary("c:\\priv\\mydll.dll . . .");
// mod5 と mod6 は同じものが返る (空白の違いは無視)
// mod5,mod7,mod8 は全て異なる (ピリオドの違いは無視されず)
HMODULE mod5 = LoadLibrary("mydll.dll");
HMODULE mod6 = LoadLibrary("mydll.dll ");
HMODULE mod7 = LoadLibrary("mydll.dll .");
HMODULE mod8 = LoadLibrary("mydll.dll . . .");

// これらへの GetModuleFileName は全て "c:\priv\mydll.dll" を返す

NTFS の不思議 (1)

NTFS のハードリンクで,c:\priv\mydll.dll の別名 c:\priv\mydll2.dll を作る.fsutil コマンドを使用.

// mod1,mod2,mod3 は同じものが返る
// mod4 のみ異なる
HMODULE mod1 = LoadLibrary("c:\\priv\\mydll.dll");
HMODULE mod2 = LoadLibrary("c:\\priv\\mydll2.dll ");
HMODULE mod3 = LoadLibrary("mydll.dll");
HMODULE mod4 = LoadLibrary("mydll2.dll");

// mod1, mod2, mod3 に対する GetModuleFileName は "c:\priv\mydll.dll" を返す
// mod4 に対する GetModuleFileName は "c:\priv\mydll2.dll" を返す
// 順序を変えてみる
// mod1, mod2, mod4 は同じものが返る
// mod3 のみ異なる
HMODULE mod2 = LoadLibrary("c:\\priv\\mydll2.dll ");
HMODULE mod1 = LoadLibrary("c:\\priv\\mydll.dll");
HMODULE mod3 = LoadLibrary("mydll.dll");
HMODULE mod4 = LoadLibrary("mydll2.dll");

// この(同一の)ハンドルに対する GetModuleFileName は "c:\priv\mydll2.dll" を返す
//"c:\priv\mydll.dll" ではないことに注意 (順序に依存している) 
// mod3 に対する GetModuleFileName は "c:\priv\mydll.dll" を返す
// GetModuleHandle はどうか?

// x2 のみ NULL になる
// その他は全ては同一のハンドルが返る
HMODULE mod1 = LoadLibrary("c:\\priv\\mydll.dll");
HMODULE x1   = GetModuleHandle("mydll.dll");
HMODULE x2   = GetModuleHandle("mydll2.dll");
HMODULE x3   = GetModuleHandle("c:\\priv\\mydll.dll");
HMODULE x4   = GetModuleHandle("c:\\priv\\mydll2.dll");
HMODULE mod2 = LoadLibrary("c:\\priv\\mydll2.dll ");
HMODULE y1   = GetModuleHandle("mydll.dll");
HMODULE y2   = GetModuleHandle("mydll2.dll");
HMODULE y3   = GetModuleHandle("c:\\priv\\mydll.dll");
HMODULE y4   = GetModuleHandle("c:\\priv\\mydll2.dll");
// 順序を変えてみる

// x1 のみ NULL
// その他は全ては同一のハンドルが返る
HMODULE mod2 = LoadLibrary(("c:\\priv\\mydll2.dll");
HMODULE x1   = GetModuleHandle(("mydll.dll");
HMODULE x2   = GetModuleHandle(("mydll2.dll");
HMODULE x3   = GetModuleHandle(("c:\\priv\\mydll.dll");
HMODULE x4   = GetModuleHandle(("c:\\priv\\mydll2.dll");
HMODULE mod1 = LoadLibrary(("c:\\priv\\mydll.dll");
HMODULE y1   = GetModuleHandle(("mydll.dll");
HMODULE y2   = GetModuleHandle(("mydll2.dll");
HMODULE y3   = GetModuleHandle(("c:\\priv\\mydll.dll");
HMODULE y4   = GetModuleHandle(("c:\\priv\\mydll2.dll");

NTFS の不思議 (2)

NTFS のジャンクションで,c:\priv\ の別名 c:\priv2\ を作る.linkd コマンドを使用.

// 全て同一のハンドルが返る
HMODULE mod1 = LoadLibrary("c:\\priv\\mydll.dll");
HMODULE x1   = GetModuleHandle("c:\\priv\\mydll.dll ");
HMODULE x2   = GetModuleHandle("c:\\priv2\\mydll.dll ");
HMODULE mod2 = LoadLibrary("c:\\priv2\\mydll.dll");
HMODULE x1   = GetModuleHandle("c:\\priv\\mydll.dll ");
HMODULE x2   = GetModuleHandle("c:\\priv2\\mydll.dll ");

// GetModuleFileName は "c:\priv\mydll.dll" を返す

NTFS の不思議 (3)

NTFS のシンボリックリンクで,c:\priv\mydll.dll の別名 c:\priv\mydll2.dll を作る.
シンボリックリンクWindows Vista 以降でのみ使用可能*1

mklink c:\priv\mydll2.dll c:\priv\mydll.dll

実験結果はハードリンク時と同じ.

NTFS の不思議 (4)

NTFS のシンボリックリンクで,c:\priv\ の別名 c:\priv2\ を作る.

mklink /d c:\priv2 c:\priv

実験結果はジャンクション時と同じ.

NTFS の不思議 (まとめ)

結論として,NTFS によって同一のファイルに複数の名前を付けても,パス付きの指定ではロードされるモジュールはユニークになる.ただし,ベースファイル名のみを指定した場合は,同一のファイルを異なるものとして認識する.(末尾に "." 追加で従来起きていたようなことが起きる)

*1:[http://homepage1.nifty.com/emk/symlink.html:title=ハードリンク/ジャンクション作成ツール]の解説が参考になります.