手元に DLL だけがあって、 それとリンクするために LIB ファイルが欲しいことがあります。 例えば C:\Windows\System32\ntdll.dll の中身を見ると:
C:\> dumpbin /exports c:\Windows\System32\ntdll.dll
ordinal | hint | RVA | name |
---|---|---|---|
9 | 0 | 000093E0 | A_SHAFinal |
10 | 1 | 00009050 | A_SHAInit |
11 | 2 | 00009520 | A_SHAUpdate |
となっていますから、これを使って sha1 が計算できそうです。 そこで、ntdll.dll とリンクするような LIB ファイルが欲しいわけです。
しかし Bing 先生に訊いてみると、わらわらと不正な方法が出てきます。 というわけでここに正しい方法をメモしておきます。
まず、いろいろ試してみた結果最も楽な方法を述べます。 (以下 Visual Studio 2012 を使うことが前提)
extern "C" { void __stdcall A_SHAFinal (int, int){} void __stdcall A_SHAInit (int, int){} void __stdcall A_SHAUpdate(int, int, int){} } // extern "C"
このソースは、LIB を生成するためだけのものです。 必要最低限のことだけ書けばいいです。
NAME "ntdll.dll" EXPORTS A_SHAFinal A_SHAInit A_SHAUpdate
NAME "DLL名" のところには、本当にリンクしたい DLL の名前 (今の場合は ntdll.dll) を書きます。書き忘れると大変なことになります。
なお、関数をエクスポートする方法は:
の三通りがありますが、__stdcall の関数の場合、 上手くいくのは DEF ファイルを書くことだけです。それ以外は失敗します。
C:\> cl /LD akkarin.cpp /link /NODEFAULTLIB /NOENTRY /def:akkarin.def
このコマンドで、akkarin.dll, akkarin.exp, akkarin.lib の三つのファイルができます。 その際:
akkarin.exp : warning LNK4070: .EXP 内の /OUT:ntdll.dll ディレクティブの指定が出 力ファイル名 'akkarin.dll' と異なっています。ディレクティブは無視されます。
というような警告が出ますが、これは意図した動作ですので問題ありません。
ゴミ箱に akkarin.dll と akkarin.exp を捨てます。
残った akkarin.lib が、最終的に欲しかった LIB ファイルです。
C:\> cl /Ox /EHsc myapp.cpp akkarin.lib
ここからは、上記の方法を採らなければならない理由を説明します。
その前に。dumpbin /exports で表示される ordinal, hint とは何でしょうか。 DLL のシンボルテーブルは、二つのものからなっています:
[A] のインデクスが ordinal、[B] のインデクスが hint です。
動的なリンクが行われる際、まずインポートする人が知らされている hint 値を使って、DLL の [B] を引きます。 引いたときに、その関数名が期待していた関数名なら、 そこに記された [A]のインデクスを使って [A] を引き、アドレスを得ます。 もし期待していた関数名と違ったら、[B] を一から検索して関数名を探します。
というわけで、hint 値は間違っていてもいいのです。 新しく関数が追加されれば、[B] は変わってしまいますから、 Windows のバージョンが違えば hint 値は違うものです (多分)。 ですから気にしないで行きましょう。
ここからが本題です。LIB ファイルは
IMAGE_ARCHIVE_START IMAGE_ARCHIVE_MEMBER_HEADER 可変長のデータ IMAGE_ARCHIVE_MEMBER_HEADER 可変長のデータ IMAGE_ARCHIVE_MEMBER_HEADER 可変長のデータ : :
という構造をしています(各構造体は SDK に定義されています)。 LIB ファイルの中でシンボル名を保持しているのは
IMAGE_ARCHIVE_MEMBER_HEADER IMPORT_OBJECT_HEADER CHAR[] シンボル名 0x00 CHAR[] DLL 名 0x00
という箇所です。各シンボルにつき一個ずつ IMAGE_ARCHIVE_MEMBER_HEADER と IMPORT_OBJECT_HEADER が付きます。
IMPORT_OBJECT_HEADER の定義を SDK からコピペしてきましょう:
typedef struct IMPORT_OBJECT_HEADER { (省略) WORD Type : 2; // IMPORT_TYPE WORD NameType : 3; // IMPORT_NAME_TYPE WORD Reserved : 11; // Reserved. Must be zero. } IMPORT_OBJECT_HEADER;
NameType というメンバがあります。ここに入る値は:
typedef enum IMPORT_OBJECT_NAME_TYPE { IMPORT_OBJECT_ORDINAL = 0, // Import by ordinal IMPORT_OBJECT_NAME = 1, // Import name == public symbol name. IMPORT_OBJECT_NAME_NO_PREFIX = 2, // Import name == public symbol name skipping leading ?, @, or optionally _. IMPORT_OBJECT_NAME_UNDECORATE = 3, // Import name == public symbol name skipping leading ?, @, or optionally _ // and truncating at first @ } IMPORT_OBJECT_NAME_TYPE;
と定義されています。
という三種類の名前比較法があるのです。 __stdcall の関数は、DLL側では接頭辞も接尾辞もありません。 そしてオブジェクトファイルのほうでは、「_関数名@16」のように接尾辞が付きます。
ですから、NameType == IMPORT_OBJECT_NAME_UNDECORATE でなければいけません
DEF ファイルを書けば、ソースファイルがなくても、 次のコマンドで LIB ファイルが得られます:
C:\> lib /def:akkarin.def /out:akkarin.lib
しかしこの方法で作られた LIB ファイルでは、 NameType == IMPORT_OBJECT_NAME_NO_PREFIX となります。
これではどうやっても DLL 側の「関数名」と、オブジェクトファイル側の「_関数名@16」を 照合できません。ですからこの LIB ファイルは正しくありません。
ソースファイルの関数定義に __declspec(dllexport) を付加すれば、 DEF ファイルがなくても次のコマンドで LIB ファイルが得られます:
C:\> cl /LD akkarin.cpp /Fe:ntdll.dll /link /NODEFAULTLIB /NOENTRY
しかしこの方法で作られた LIB ファイルでは、 NameType == IMPORT_OBJECT_NAME となります。
これも正しくありません。
#pragma comment(linker, "/EXPORTS:symbol") を使う方法は cl コマンドが成功しないので、僕には正しい使い方がわかりません……。
結局、正しい LIB ファイルを作るには、 DEFファイルと偽のソースファイルを用意して、 偽の DLL を作り、その副産物としての LIB を流用するしかないのです。多分。
dumpbin /exports は序数 (ordinal) も表示してくれます。 ですから DEF ファイルに:
NAME "ntdll.dll" EXPORTS A_SHAFinal @ 9 A_SHAInit @ 10 A_SHAUpdate @ 11
と書きたくなってしまいます。しかしこれは罠です。 DEFファイルに序数を書いてはいけません。絶対にダメです。
序数の指定されたものは、序数を使ってリンクするようになってしまいます。 関数名は決して見に行きません。 ですから DLL のバージョンの違いで序数がずれると、 この LIB ファイルを使って作られた EXE は動かなくなります。
実際、上記の ntdll.dll の序数は、64bit のものです。 ツールが 64bit なので、64bit のシステム DLL を見に行ってしまうのです。 この序数は 32bit 版と異なります。ですから この序数を使った DEF を用いて 作った 32bit 版 EXE は、最初から動作しません。