DLLを使おう!!

 DLLプログラミングは、ウィンドウズプログラミングと切っても切れない関係……のはずなのに、結構わかりにくいんですよね。そういう部分をまとめてみました。

「DLL」は知ってるけど……
DLLを使わない場合
 「DLL」。この名称は「Dynamic Link Library」の略です。日本語に訳すと「動的にリンクするライブラリ」といったところでしょうか<一語しか訳してないやん。

 通常アプリケーションを作製する場合、「各ファイルをコンパイルする」−「作製されたオブジェクトファイルをリンクする」という手順(いわゆる「ビルド」と呼ばれるもの)を踏んで「実行ファイル(Exeファイル)」が作製されます。
 アプリケーションはいっぺんにすべてが作られるわけではなく、ソースファイル単位でまず「コンパイラ」「オブジェクトファイル」というものを作り、次に「リンカ」が各オブジェクトファイルをくっつけ、「実行ファイル」にします。
 この「ビルド時にリンクする」ことを「静的(Static)リンク」と呼びます。

DLLを使う場合
 このソースコードの一部を実行ファイルとは別に作製したものが、DLLです。
 実行ファイルとDLLは、別々に作製されます。DLLも実行ファイルと同じ手順、コンパイルを行い、リンクをして、作製されます。実行ファイルも、ソースの一部がない状態で、コンパイルされ、リンクが行われて、作製されます。こうして、元はひとつだったファイルがふたつに分かれたことになります。
 DLLへのリンクは、実行ファイルが「実行」された時に行われます。実行ファイルはまず実行ファイルのあるフォルダからDLLを探し、カレントフォルダからDLLを探し、システムフォルダからDLLを探し、ウィンドウズフォルダからDLLを探します。
 DLLが見つかったら、実行ファイルはDLLへとリンクします。こうしてふたつのファイルはひとつの実行ファイルと同じ機能を持つことになるというわけです。このリンクを「動的(Dynamic)リンク」と呼びます。

 このように、DLLとは「実行するときだけリンクされる、プログラムの一部」なのです。

DLLを使うメリットとデメリット
 DLLを使うメリットとデメリットは、なんでしょうか。

 メリットはまず「ファイルサイズの節約」でしょう。ある関数やクラスをいくつものアプリケーションで使う場合、その関数やクラスをDLLの中に入れてしまえば、単純計算で「DLLのサイズ×(アプリの数−1)」だけの節約になります。

 メリットとデメリットが同時に存在するのが、「同じコードの省略」です。プログラムの中で似たコードをできるだけ作らないのが、バグを減らすコツのひとつです。これはアプリケーション単位の話ですが、DLLを用いればさらに複数のアプリケーション内の同一コードを省略して、ひとつにまとめることができることになります。
 ですが、これは逆に言えば「そのDLLにバグがあれば、それを使用するすべてのアプリに影響が出る」ということになります。このデメリットは皆さんイヤというほど味わっているでしょう(泣)。
 ただし、このバグを修正する場合、「このDLLを書き換えるだけですべてのアプリが正常に戻る」ということにもなります。これまた「サービスパック」という形でイヤというほど味わっているでしょう(爆)。アプリケーションのサイズが大きい場合、ユーザーがダイヤルアップでダウンロードしなければならない場合等、小さなモジュールで修正できるというメリットは比較的大きいと言えます。

 別に、「DLLに使うコードは元々多く存在しなければならない」ということはなく、たった一度しか使われないような希有なコードを入れることもあります。例えば、その希有なコードを組めるスタッフがいなくて、外注しなければならない場合、その完成品をDLLとして受け取ることで、実行ファイルとDLLを別プロジェクトとして平行作業することができます
 仕様書の上で「ここの関数はこういう呼び出しにする」ということをあらかじめ決めておくことで、実行ファイル側のプロジェクトと、DLL側のプロジェクトが別々に作業を進めることができます。そして、最後にふたつを一緒のフォルダに入れて実行するだけで、結果が分かります。また、途中のテストも、同期を取る必要がなくなります。
 しかしこれは逆に、「DLLを作ること自体がプロジェクトになる」ということを示しています。実際、コードをふたつに分けてDLLに入れるだけでも、結構手間がかかります。こういう場合、できればMSDEVをふたつ起動して、同時に作製するのがいいでしょう(かなりメモリを食いますが)。

 DLLは、システムフォルダにインストールされることで、そのDLLをすべてのアプリケーションが使用できるようになります。のちほど説明しますが、すべてのウィンドウズAPIは、DLL内の関数として提供されています。つまり、システムフォルダのDLLはそのままウィンドウズのシステムとなるということです。
 また、シェルエクステンション――例えば特定のファイルにプロパティシートを追加したり、特定のフォルダを特殊な機能を持ったフォルダにしたり――を行う場合、これらの機能を実現する「COMインターフェイス」も、DLLに組み込んで使用します。また、コントロールパネル(拡張子cplのファイル)も、DelphiやVBでおなじみのActiveXも、DLLとほとんど同じものです。これらも「システム」として機能し、すべてのアプリケーションが共有します。

 それと、「システムフック」「サブクラス化」という、ユーティリティアプリケーションにとって非常に強い味方となるこのふたつの機能を使用する場合、DLLを必ず使わなければならないのです。
 実は、この講座の最終目的として、このふたつを使いこなすというものがあります(予定ですが)。そのためにも、まずDLLというものについてしっかりと理解しておきたい、というわけです。

「ライブラリファイル」のあるDLLを使ってみよう!!
 さて、では実際にDLLをアプリケーションで使ってみましょう!!
 DLLを使う場合、「ライブラリファイル(拡張子がLibのファイル)」を使う場合と使わない場合で大きく違います。今回は使ってみましょう(使わない方法は次回説明します)。

 今回サンプルとして使ってみるDLLおよびその中の関数を説明します。今回使用するのはsndPlaySound()という関数です。この関数はAPIに区分されますが、その中でも「マルチメディアAPI」と呼ばれるものです。マルチメディアAPIは、音を鳴らしたり、動画を表示したりといったまさに「マルチメディア」な機能を持ったAPIグループです。DirectXのようなばりばりの機能は持っていませんが、アプリケーションでちょっと何かしたいな、という時くらいには十分役立ちます。
 今回使用するsndPlaySound()は、音を鳴らす関数です。WAVファイルを読み込み、再生するだけの機能を持つ関数です。MFCはマルチメディアAPIをサポートしていないので、デフォルトではこの関数を使うことはできません。自分で、このAPIを持つDLLへとリンクするようなプログラムを書く必要があるということです。その方法を、これから解説します。

 まず、最初に「Win32SDKヘルプ」のsndPlaySound()のページを見てください。英語で色々書かれていますねー。そのページの一番上の行。関数名の右隣にみっつのボタンがありますね。その一番左の「Quick Info」のボタンを押してください。
 そうすると、この関数のリファレンスページが表示されます。このページは非常に重要です!! 特に重要なのが、5行目のImport Library、6行目のHeader Fileの欄です。

 Import Libraryは、プログラムを組むときに使う情報が入っている「ライブラリファイル」のファイル名です。これが、プログラムを組むときに必要になってきます。
 Header Fileは説明しなくても大丈夫でしょう。この関数のプロトタイプ宣言が書かれたヘッダーファイル名です。この関数を使うソースファイルで、このヘッダーファイルをインクルードすればいいわけです。

ライブラリファイル
 コンパイルが終了してObjファイルがいっぱいできた後、リンカは「このオブジェクトファイルが使ってる関数はどのオブジェクトファイルにあるのかなー」と調べます。その中から見つからないと、「ぐがっ、てことはDLLかぁ!?」と、今度は「ライブラリファイル」を検索します。
 検索するライブラリファイルのリストは「プロジェクト」−「設定」ダイアログの「リンク」ページ・「一般」カテゴリ・「オブジェクト/ライブラリモジュール」の中に書かれているものです。え? 何もかかれてないって? さてはあなた、MFCプログラムしかしてませんね。早速、素の「Win32 Application」を作製して同じ欄を見てみましょう。kernel32.lib user32.lib gdi32.lib...とずらーっと書いてあるでしょう。MFCはちょっとした工夫で隠してあるだけです(この部分はのちほど説明)。

 さて、見つからない関数があった場合、この「検索ライブラリファイルのリスト」の順に、ライブラリファイルを調べて「関数があるか」チェックします。
 では、「ライブラリファイル」とはなんなんでしょうか。

Objファイルにない関数のライブラリファイルからの検索
 「ライブラリファイル」は、DLLの作成時に一緒に作製されるファイルです。DLLの中の「外から使われるための関数・クラス」は、特別に外から分かる形になってコンパイルされます(これを「エクスポート」といいます)。このとき、「作製されたDLLのファイル名(基本的にライブラリファイルの拡張子違い)」「エクスポートされた関数・クラス」が、ライブラリファイルに書き込まれます
 「関数どこかなー」とリンカは、ライブラリファイルの中の関数を検索していきます。関数が見つかったら「この関数はこのDLLにあるよ」と書き込んで、検索を終了します。不明な関数がなくなったらリンクは終了、実行ファイルが完成します。

 ここで重要なのは「DLLは必要ない!!」ということです。実行ファイルを作製するときに必要なのは「ライブラリファイル」です。これは逆に言えば「ライブラリファイルがすべて」ということです。ライブラリファイルが古いままだと、新しいDLLと合わなくてアプリケーションが実行できなくなります。リンカはライブラリファイルさえあれば安心してしまうのです
 「DLLのメリット」として、DLLはいつでも気軽に更新できるというメリットを上げました。が、それは同時にバージョン管理が大変だということです。ライブラリファイルとの整合性は常に注意しておいてください。

で……使おうね
 話が逸れちゃいました。例を元にちゃんと使ってみましょう。

 sndPlaySound()のリファレンスを元に、ライブラリファイルとヘッダーファイルが分かりました。まず、ライブラリファイルを何とかしましょう。
 ライブラリファイルは、先ほどの欄に書き加えておけばOK。ファイルの間は半角スペースで空けておきます。まぁ、MFCなら何もかかれてないと思いますが。
 ……で、これで終わり。あっさりですねー。でも、この欄にこのファイルだけ。ちょっと寂しいですね。どうせなら……MFCと同じ方法を取ってみましょう

 VCのMFCのヘッダーファイル(たぶんVC/MFC/Include/フォルダ)の中のAFX.hの最初の方を見てください。#pragma comment(lib, "kernel32.lib")みたいなのがずらーっと書いてありますね。そして、中にはライブラリファイル名が……。そう、ここで検索ライブラリファイルリストを設定しているのです
 #pragmaというのは「プリプロセッサ」のひとつで、その中でも特に様々なことをしてくれるものです。機種依存の高い機能、例えばこのような「ライブラリファイルの指定」を、C言語標準の機能として使用することができ、プリプロセッサの方で「機種依存の壁」を崩してくれます。
 まぁ、#pragmaはかなり複雑なんで、上のものをそのまま使っちゃいましょう。そうしたら、「設定」ダイアログの方は削除しちゃってOKです。

 実際のコードは、stdafx.hの下の方(//{{AFX_INSERT_LOCATION}}の上の行くらい)に、次の2行を追加してください。


#pragma comment(lib, "winmm.lib")
#include <mmsystem.h>
	

 ライブラリファイル名とヘッダーファイル名が「リファレンス」にあったものと同じだということを確認してください。こういう風に書き込めば、他で設定する必要はないということですね。

 ちなみに話は逸れますが、「プロジェクト」−「設定」ダイアログの「C/C++」ページ・「プリコンパイル済みヘッダー」カテゴリで、「プリ(略)を使用」を選びかつ「StdAfx.h」を設定しているのなら、とてつもなく大きい「Pchファイル」が作製されるでしょう。
 「プリコンパイル」とは、「あまり変更しないファイルをコンパイルしてまとめたオブジェクトファイル」みたいなのだと思えばいいでしょう。ビルドしたあとこれにリンクする、という手順を取るとアプリケーションの完成が早くなります。ただし、このファイル自体はかなり大きいんでコンパイルにかなりの時間がかかります
 これは全然問題ないんですが、例えば上のヘッダーファイルとかが自分で作ったDLLの場合には、DLLのヘッダーファイルを変更する度に「プリコンパイル済みヘッダーファイル」が再コンパイルされるということになります。これはかなりの負担になるので、変更が必要ないくらいチェックしてからこのヘッダーファイルに組み込むのがいいでしょう。

 はっ、まだ残っていましたね、実際に音を鳴らす部分です。ま、ソースファイルのどこでもいいんで次の行を加えてください。


	::sndPlaySound( "Test.wav", SND_ASYNC | SND_NODEFAULT );	//音発生!
	

 んで、「Test.wav」というファイルをどっかから調達して、置いてください(置く場所ですが、デバッグバージョンだと「プロジェクトファイルのあるフォルダ」みたいです。心配の時はフルパスで指定してね)。

 さて、実際にプログラム内でしたことはなんでしょう。まとめると、
・「#pragma」を使ってライブラリファイルを検索リストに追加。
・関数の宣言が入ったヘッダーファイルをインクルード。
・で、使う。
 スゲー簡単ですね。なぜか? 当然なんです。だって、すべてのAPIはこのように使うんですから

APIの場合
 先ほどちょっと書いた「Win32 Application」の場合を思い出してください(まだ見てない人は必ず試すこと!! もしくは「MFCユーザーのためのAPIプログラミング講座」のサンプルを見てね)。ライブラリファイルの検索リストにずらーっと書かれていましたね。
 さて次に、RegisterClass()とかCreateDC()とかGlobalAlloc()の各APIのリファレンスを見てください。user32.lib、gdi32.lib、kernel32.libをそれぞれインポートすること、と書かれていますね。そして、それを上のWin32アプリで検索リストに加えてますね。またはMFCの場合にはAfx.h#pragmaを使って加えられてますね。

 そう、ちょっと触れたとおり、APIはすべてDLLに入っており、ライブラリファイルを使ってアプリケーションからリンクするという形をとっているのです。MFCを使っていると、「DLLなんて使ってないよーん」とか思うかもしれませんが、全然そういうことはないのです。ウィンドウズアプリケーションはすべて、こうしてDLLを通してAPIを使用し、アプリケーションが実行されるのです。
 って、別にたいそうなことではないんですが。MFCに染まっているとこういうことを知らずにプログラミングしてると思います。DLLを使う時にはMFCの恩恵に与れない場合が多く出てくるので、その準備運動みたいなものです。今まで「MFCおんりぃ」だった人は、ソースコードとかを見て、ちょっとAPIプログラミングについて見てみるといいかもしれません。

まとめ&これから
 ちょっと触れたように、この講座は最終的には「システムフック」や「サブクラス化」を解説しようと思っています。また、次回は「DLLの実行時リンク」を紹介しようと思います。
 「あれ? MFCチップスにおんなじのなかった?」ありました(爆)。まぁ図とか増やして、もうちょっと分かりやすくして、別講座にするってことです。あんまり気にしないでね。

(C)KAB-studio 1998 ALL RIGHTS RESERVED.