DLL(ダイナミックリンク・ライブラリ)とは,具体的にどんなモノなのでしょうか。アプリケーションからはどのように使われているのでしょうか。DLLはどのようにすれば作れるのでしょうか。DLLにすることによって,どのような利点があるのでしょうか。
●DLLはさまざまな部品の集合体
DLLとはDynamic Link Libraryの略です。日本語に直すと「動的に結合できる書庫ファイル」ということになります。DLLの概念は,プログラマではない人にとって理解しづらいものかもしれませんが,ごく簡単に解説していくことにしましょう。
一般的なソフトウェアはさまざまな部品から組み立てられています。例えば,ドローソフトは画面に絵を表示する部品,ユーザーの指示に従って線を描く部品,ディスクに記録する部品といったような,さまざまな部品の組み合わせだと考えることができます。
これらの部品の中には,異なるアプリケーションで共同利用できるものが数多く含まれます。例えば,ディスクに記録する汎用部品があるなら,それはドローソフトだけでなくワープロや表計算ソフトでも利用できるはずです。
MS-DOS時代は一度に一つのアプリケーションしか起動することができませんでした。したがって,アプリケーションごとに別々に部品を設計して組み込むような作り方がなされるのが当たり前でした。ところが,Windowsは複数のソフトウェアがメモリにロードされ,同時に実行されます。にもかかわらず,すべてのソフトウェアはメモリとハードディスクを共有しなければなりません。同じような部品をすべてのアプリケーションが個別に持つのは,メモリやハードディスクのむだだということになったのです。
そこで,Windowsには共同利用が可能な部品を一つのファイルにまとめ,アプリケーションソフトが起動すると,そのソフトが使う部品のファイルをロードして利用する機構が組み込まれました。この部品の書庫ファイルがDLLファイルだと考えるといいでしょう。
例えば,トランプのカードを動かす汎用部品を作ったとします。この部品は,画面にトランプを表示したり,それを動かすことができます。このような部品をCARDS.DLLという1個のファイルにまとめておき,トランプを使うすべてのゲームがCARDS.DLLを共用すれば,ハードディスクやメモリのむだが省けます。また,ゲームを開発する人も,他人が作ったCARDS.DLLを使うことでトランプの表示部分を作る必要がなくなり,ゲームの主要部分だけに力を注ぐことができるのです*1。
●実行ファイルのヘッダに記述される
DLL内部の部品は,さまざまなアプリケーションが利用します。Windowsの実行ファイルの先頭部分には,その実行ファイルが実行時に利用するDLLのファイル名や部品の名前が詰め込まれています。
Windowsは実行ファイルをメモリにロードすると,それが使うDLLファイル名を調べ,DLLがメモリに読み込まれていなければハードディスクから読み込み,部品を利用できるように環境を整えます。もちろん,そのDLLがすでにメモリの内部にあれば,Windowsはメモリ内部のDLLを使用します。
Visual Basicで作成されたフリーソフトなどを実行しようとすると,「VBRxxx.DLLがない」というようなエラーに遭遇した経験を持つ読者がいるかもしれません。これは,Visual Basicで作成されたすべてのアプリケーションが使う「ランタイムライブラリ」のDLLファイルをハードディスクから見つけられなかった,というメッセージをWindowsシステムが出しているのです。
実行ファイルの先頭領域(Executable Headerといいます)には,DLLのファイル名だけでなく,さまざまな情報が入っているので,ヘッダ部分のすべてを手作業で解析するのは熟練したプログラマでも難しい作業です。しかし,Windows標準の「クイックビューア」で実行ファイルやDLLを開くと,ヘッダ部分を自動で解析して,分かりやすい表にまとめてくれます。試しに,NOTEPAD.EXE(メモ帳)をクイックビューアで開いた例を掲載しておきましょう(画面1)。
画面1 NOTEPAD.EXEのヘッダ部分
さまざまな情報が表示されていますが,「Import Table」部分を見てください。この表には,NOTEPAD.EXEが使うDLLファイル名と,その中の部品名が列挙されています。例えば,画面の内容からSHELL32.DLLのDragFinishという部品を使うということが分かります。
では,SHELL32.DLLを開いてみましょう(画面2)。
画面2 SHELL32.DLLのヘッダ部分
DLLのファイル形式は実行ファイルとほぼ同じなのですが,通常の実行ファイルがImport Tableしか持たないのに対し,DLLはExport Table(提供する部品の表)を持っています*2。SHELL32.DLLのExport Tableの中にDragFinishという名前が見えますね。この部品をNOTEPAD.EXEは利用しています。NOTEPAD.EXEがメモリにロードされると,SHELL32DLLもロードされ,NOTEPAD.EXE内部のDragFinishの呼び出しがSHELL32.DLLのDragFinishと結び付けられるわけです。
●DLLの効用
WindowsアプリケーションのプログラマがDLLファイルを作成する理由は,大きく分けると二つです。一つは,汎用部品を独立させるというDLL本来の使い方です。例えば,グラフィックビューアの画像を展開する部分をDLLファイルにまとめておけば,そのDLLファイルを差し替えるだけで新しい画像形式に対応できるようになりますし,ドローソフトで同じDLLを再利用できるなどのメリットがあります。
もう一つは,Windowsのシステムに近い処理を行う場合です。WindowsシステムにはDLLでしかできない,例えばAPIコールを横取りするなどのプログラムは,DLLでなければ作れないようになっています。そのような場合は,DLLを作成せざるを得ません。
DLLファイルを作成するのは,プログラマにとって難しくはありません。C言語でプログラミングができるのなら,Visual C++を使用して作成します。DLLは,メモリにロードされたときにシステムから呼び出されるLibMain関数と,そのDLLが提供する部品(関数)から成り立っています。16ビット時代はセグメントにかかわる制限に気をつける必要がありましたが,32ビットDLLならば,そのような制限を気にせずに通常のプログラムと同じように作成することができます。
ところで,これまではアプリケーションを主体に話を進めてきましたが,OSそのものも,中核をなす低レベルカーネルと部品の集合体みたいなものと考えることができます。分かりやすい例を挙げるなら,例えばウィンドウを表示する部品,キーボードの入力を受け付ける部品,ファイルの一覧を見る部品……という具合です。
実際に,WindowsではOSがサービスする大部分の機能がDLLファイルに実装されています。Windowsのそこを支える3大DLL,KERNEL32.DLL,USER32.DLL,GDI32.DLLの内部には,Windowsのシステムサービスのほとんどが収められています。DLLはWindowsを支える機構だといってもいいでしょう*3。
●OSもモジュールで構成される
16ビット時代(Windows 3.1まで),Windowsはインテル8086/80286アーキテクチャで設計されていました。細かい話は省きますが,80286までは「セグメント」がメモリモデルを支配していたため,動的に実行ファイルと結合するDLLのような機能を実現するのは非常に難しく,Windows 3.1まではトリッキーな技と仕掛けを使ってDLLファイルと実行ファイルの動的結合を行う仕組みになっていました。16ビット時代のDLLをクイックビューアで覗くと,32ビットDLLにはないデータが格納されていることが確認できますが,これはDLLが使うセグメントベースなど,32ビットDLLでは不要になったデータの指定です。
Windows 95やNTではシステムが32ビット化され,セグメントメモリモデルが撤廃されています*4。DLL機構そのものも,Windows 3.1と比べるとスマートな形で実現されています。とはいえ,現在のWindows 95でも16ビットDLLを利用することができ,また32ビットアプリケーションと16ビットDLLを動的に結合することさえ可能となっています。
WindowsのサービスをDLLに実装することで,さまざまな利点が生まれます。例えば,特定のDLLファイルを差し替えるだけでOSのバージョンアップや機能強化,バグフィックスが可能になります。また,OSを機能ごとのモジュールとして設計・制作できるため,開発も楽になります。
一方,同じDLLでさまざまなバージョンが混在する危険性も出てきます。また,バージョンアップに伴って新しいDLLと古いDLLの互換性が失われることも考えられます。
DLLのバージョン管理はユーザーにとって難しい作業です。マイクロソフトは,DLLのバージョン管理を確実に行うことができるような仕組みを提案していますが,すべてのアプリケーションが自分が利用するDLLのバージョンを正しく管理し,全体を矛盾なく収めるのは,まだ難しい状態だといえるかもしれません。
(米田 聡)
*1:CARDS.DLLは\Windows\Systemの下に実在するDLLで,FreeCellなどのトランプゲームが利用しています
*2:Export Tableを持つ拡張子がEXEのファイルもあります。また,DLLファイルがほかのDLLファイルを利用することも多く,ほとんどのDLLファイルにはImport Tableもあります
*3:実際には,Windows 95のUSER32.DLLとGDI32.DLLの多くの部品は,16ビットコードで作られたUSER.EXE,GDI.EXEを呼び出します。この2個のファイルは拡張子がEXEですが,内部は16ビットのDLLファイルです
*4:実際には,Windows 95には16ビットの痕跡が随所にあり,局所的にはセグメントメモリモデルがまだ残っています