EternalWindows
DLL / インポートライブラリの役割

何らかの関数を実装することになった場合、 その関数はなるべく汎用的なコードで記述したいと思うものです。 そうすることで、その機能は特定のアプリケーションだけに限定されず、 他のアプリケーションからも使用できることになります。 ただし、そうした複数のアプリケーションで使用されるコードを複数のEXEファイルに格納してしまっては、 EXEファイルのサイズが大きくなってしまう問題がありますから、 コードを格納することを目的とした別のファイルが用意されています。 これがDLL(Dynamic Link Library)であり、今日のWindowsアプリケーションが動作する基盤になっています。

Windows APIと呼ばれる関数群は、全てのアプリケーションにとって有用になるものですから、 原則としてDLLに実装されています。 たとえば、MessageBoxの関数コードはuser32.dllに実装されています。 このようなコードの分離は先に述べたようなEXEサイズの肥大化を防ぎますが、 実際にアプリケーションが実行される際のことを考えると、ある1つの疑問が生じると思われます。 それは、EXEとDLLという異なるファイル間の呼び出しが何故成立するのかというものです。 これについて考えるために、次のコードを検証します。

#include <windows.h>

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	MessageBox(NULL, TEXT("ボタンを押すと終了します。"), TEXT("OK"), MB_OK);

	return 0;
}

上記コードはメッセージボックスを表示するだけの単純なプログラムですが、 そもそも何故このコードはコンパイル可能なのでしょうか。 確かにMessageBoxのプロトタイプはwindows.h(正確にはwinuser.h)に定義されていますし、 引数の指定にも特に問題はないため、一見するとコンパイルに失敗する要素はどこにもないように思えます。 しかし、上記コードにはMessageBoxの実装自体は存在しないわけですから、 そのような実装の確認できない関数の呼び出しを、本当に許可してもよいのかという疑問も残ります。 この疑問を解消するためには、コンパイルの仕組みを今一度考える必要があります。

コンパイルというのは、簡単に言えばソースコードを機械語に翻訳することです。 これを行うアプリケーションがコンパイラです。 コンパイラは、個々のソースファイルからオブジェクトファイルを作成するだけで、 実際にEXEファイルを作成するわけではありません。 EXEファイルを作成するのはリンカと呼ばれるアプリケーションであり、 リンカは1つ又は複数のオブジェクトファイルをリンクし、 それをEXEファイルの一部として埋め込みます。 MicrosoftのVisual Studioは、EXEファイルの作成にビルドという言葉を使っていますが、 これにはコンパイルとリンクの両方を行うという意味を持っています。

上記したリンカには、ライブラリ(DLL)の参照を解決するという役割も担っています。 簡単に言えばコードの中で呼び出されている関数が、 DLLによってエクスポート(提供、公開などの意味)されているかどうかを調べることです。 DLLを作成すると、そのDLLがエクスポートしている関数の一覧を記録した インポートライブラリという.libの拡張子のファイルも作成されます。 リンカは、このインポートライブラリの中身をチェックします。 たとえば、インポートライブラリにMessageBoxが含まれていたら、 リンカはMessageBoxが実際にDLLに存在する関数であると認識し、 MessageBoxの呼び出しを認めます。

リンカは、自信が検索対象とするインポートライブラリと検索対象のパスを管理しています。 この検索対象に入っていないインポートライブラリに含まれる関数を呼び出そうとした場合、 リンクエラーが生じてビルドは失敗することになります。 次のコードを見てください。

#pragma comment (lib, "winmm.lib")

本サイトのいくつかのプログラムでは、#pragmaディレクティブを使っています。 上記コードの狙いは、winmm.libというインポートライブラリの中身を検索するようリンカに指示することです。 これにより、PlaySoundのようなwinmm.libに記録されている関数の呼び出しは、リンクエラーになることはありません。 MessageBoxはuser32.libに記録されている関数ですが、 このuser32.libはデフォルトでリンカの検索対象となっているため、 #pragmaでリンカに指示を出す必要はなかったのです。 リンカが検索対象としているインポートライブラリの一覧はVC 2005であれば、 プロジェクトのプロパティから「構成プロパティ/リンカ/入力/追加の依存ファイル」で参照することができます。 この追加の依存ファイルにwinmm.libを入力したならば、 winmm.libを指定した#pragmaを使用する必要はなくなります。

これまでの話で重要なのは、実際にはソースファイルに存在しない関数であっても、 その関数がインポートライブラリに記録されているならば、問題なしというものです。 しかし、本節の冒頭でも述べたように、 MessageBoxのようなWindows APIのコードはDLLにて実装されています。 つまり、作成されたEXEファイルにはMessageBoxのコードは存在しません。 これでは、EXEファイルを起動してMessageBoxの呼び出しに差し掛かった場合、 アプリケーションはエラーを起こしてしまいそうですが、そのようなことはないのです。 本質的な問題は、関数のコードがEXEファイルにあるかどうかではなく、 プロセスのアドレス空間にあるかどうかなのです。 次節では、このことについて言及します。


戻る