「自作」DLLを使おう!!

 前回作ったDLLを、実際に使ってみましょう。今回の話は、別に自作DLLだけではなく、API系のDLLやMFC系のDLLを使う場合にも言えることなので、結構重要だったりします。

出力されたファイルの確認
 DLLを作製したときに、いろんなファイルが生成されたと思います。そのファイルを確認してみましょう。

 DLLファイル(*.dll)は、作製した関数やクラスが入っているファイルです。実行時に、Exeはこのファイルにリンクします。逆に言えばExeファイルを作るときには必要ないファイルでもあります。
 ライブラリファイル(*.lib)は、「どのDLLでどの関数やクラスがエクスポートされているか」が書かれているファイルです。これがExeファイルを作るときに必要となるファイルです。
 エクスポートファイル(*.exp)は、基本的には使わないファイルです。ライブラリファイルとは別に、DLLやエクスポートされた関数やクラスについての情報を持つファイルです。あとで定義ファイルとかに使ったりすることもありますが、ここでは説明しません。
 プログラムデータベース(*.pdb)は、デバッグ時にのみ作製され、デバッグには欠かせないファイルです。これは結構重要なので、あとでしっかり説明します。

 基本的に重要なのはこの4ファイルです。ではまず、Exeファイルのビルドに必要なライブラリファイル、そして関数の宣言が書かれたヘッダーファイルについて見ていきましょう。

検索ディレクトリの指定
 DLLを使うアプリケーションを作る時に必要なファイルは「ヘッダーファイル」「ライブラリファイル」です。もちろん前前回のように「ライブラリを使わない方法」を使えば両方ともいりませんが、自作DLLでそれは難しいことは、前回を読めば分かると思います。

 さて、この2ファイルをどうやって使えばいいのか、順に見ていくことにしましょう。
 インクルードファイルの指定に使う#includeでは、ふたつのファイル指定方法があります。ひとつは"で囲むもので、これはプロジェクト内のファイルをインクルードするときに使います。
 もうひとつは<>で囲むもので、これはすべてのアプリケーションで使うようなヘッダーファイルをインクルードするときに使います。

 このどちらを使ってDLLのヘッダーファイルをインクルードするかは、DLLによって違ってくると思います。
 システムフック用のDLLといった、特定のアプリケーションのみでしか使わない場合には、DLLのヘッダーファイルをExeのプロジェクトがあるフォルダへとコピーし(「プロジェクトへ追加」をする必要はありません)、"でインクルードするのがいいでしょう。
 MFCのような、複数のアプリケーションから使用するDLLの場合には、< >を使ってインクルードできるようにするのがいいでしょう。また、DLLがまだ作製途中で、なんども改変する場合にはヘッダーファイルのコピーは避けた方がいいので、そういった場合にもこちらの方がいいでしょう。

 では、どうやって自作DLLのヘッダーファイルを< >でインクルードするのかというと、「検索ディレクトリ」にこのヘッダーファイルがあるフォルダを追加すればいいのです。
 「オプション」ダイアログの「ディレクトリ」のページで「表示するディレクトリ」を「インクルードファイル」にしてください。そうすると、その下の欄にフォルダ一覧が表示されると思います。
 < >でインクルードされるファイルは、すべてのここで指定されたフォルダから検索されています。ここに書かれているフォルダを見てみれば、そこにはWin32SDKやMFCのヘッダーファイルがあるはずです。
 自作DLLのヘッダーファイルが置かれているフォルダを、ここに追加してください。そうすることで、< >で同じようにインクルードすることができるようになります。
 また、ついでですのでライブラリファイルソースファイルのフォルダも追加しておいてください。
 ちなみに、検索は上に書かれたフォルダから行われるので、同じファイル名のヘッダーファイルやライブラリファイルがある場合には、順番が重要になってきます。望んだファイルがロードされないと感じたら、この部分を注意してみてください。
 個人的には、ここの部分はプロジェクトごとにも設定できるようにして欲しかった……(ライブラリファイルは「設定」ダイアログの「リンク」−「インプット」で追加できました)。

どこでインクルードするか
 さて、では実際にインクルードしてみましょう。Exeファイルの側で#include <Func.h>のようにインクルードすればOKです。
 ただし、注意すべき点がふたつあります。

 まずひとつは、どのファイルでインクルードするかという問題です。
 インクルードする場所は主にふたつ。ひとつはソースコードの頭。もうひとつはStdAfx.hの中です。
 「特定の機能だけを持つDLL」の場合には、その機能を使うソースコードでインクルードするのが一番でしょう。ですが、そのDLLの機能を複数のソースコードで使用する場合には、そのすべてでインクルードしなければなりません。
 それを回避するのが、StdAfx.hでのインクルードです。このファイルでインクルードすることで、プログラム全体からDLLの機能を使用することができるようになります。基本的には、この方法を採るのがいいと思います。

 ところがこれにはデメリットもあります。
 「プリコンパイル済みヘッダーファイルを使用」の設定が入っているとき、StdAfx.hは巨大なオブジェクトファイルとして作製されます。このあと、DLL側のヘッダーファイルを書き直す度にStdAfx.hを再コンパイルすることになります。これはかなりの時間がかかるので、毎回毎回行うとかなり辛いと思います。

 このふたつのメリットとデメリットを考えて、DLLが完成するまではソースコードで、完成したらStdAfx.hでインクルードするのがいいでしょう。

「標準インクルードファイル」の作製
 もうひとつの問題は、どのファイルをインクルードすべきかという点です。前回作製したDLLの場合、__declspec(dllexport)を用いてエクスポートした場合には、Func.hにはDLL_EXPORTが用いられているのでDefs.hもインクルードしなければなりません。また、仮にFunc.hCArrayなどを使っていればafxtempl.hをインクルードする必要が出てきます。
 つまりどういうことかというと、Exeの側で、インクルードするファイルを決めなければならないということです。DLL側で必要としたヘッダーファイルは、Exe側でも必要になるということになります。が、DLLのインクルードは、当然ソースファイルや、StdAfx.hに書いてあったりして、分かりにくい場合が多いでしょう。

 そこで、DLL側に「このヘッダーファイルをインクルードすればばっちり大丈夫!」というファイルを作ってしまいましょう。
 これはDLLのプロジェクトとは無関係のファイルなので、「ファイル」−「新規作製」とかは使わずにヘッダーファイルをDLLのプロジェクトがあるフォルダに作ってください(ここではファイル名をStdHead.hとします)。
 そして、その中身は次のような感じに書いてください。


#ifndef __STDHEAD_H__
#define __STDHEAD_H__

// ライブラリファイルを読み込みます。
#pragma comment(lib, "Test.lib")

// ヘッダーファイルを読み込みます。
#include <Defs.h>	//マクロ用ファイル。
#include <Func.h>	//関数のプロトタイプ宣言が入ったファイル。

#endif	//__STDHEAD_H__
	

 このように、DLLの側で「必要なファイルをインクルードしてくれるヘッダーファイル」を作っておくと、とても便利です。例えばDLLの仕様が変わって、定義ファイルを使うことになった場合には、Defs.hのインクルードをコメントアウトすればいいわけです。Exe側は、あとでリビルドするだけで済むというわけです。
 ただ、ファイル名だけは気を付けてください。先ほど説明したとおり、ヘッダーファイルは「検索リスト」の上の方のフォルダにあるファイルが優先されます。システムフックなどの、特定のExeでしか使わないDLLでは、特別な名前を使用した方が安全でしょう。

 で、このファイルを作製したら、Exe側でこのファイルをインクルードしてください。それだけでDLLの機能をすべて使えるようになります。
 ちなみに、MFCにもAfx.hという同様の機能を持ったヘッダーファイルがあります。参考にしてみてください(ってゆーか僕はこれを参考にして自分のDLLにこのファイルを追加しました(汗))。

ライブラリファイル
 は、実はもうできちゃってます
 ライブラリファイルも、ヘッダーファイルと同じく「オプション」の「検索ディレクトリ」で登録されたフォルダから検索されます。ここでライブラリファイルのあるフォルダを登録しておけば、OKです。
 あとは、ライブラリファイルを「検索リスト」に加えれば終了。#pragma comment(lib, "Test.lib")と、上の「これさえインクルードすればすべておっけー!」ヘッダーファイルに書き込まれていることを確認してください。
 あと、ヘッダーファイルと同様、ファイル名の重複、検索ディレクトリの優先順位に気を付けてください。

 ここまでで、Exeファイルの側は終了。あとはビルドしてしまえば、完成です。
 簡単にまとめると、まず検索ディレクトリに「ヘッダーファイル」と「ライブラリファイル」のあるフォルダを加え、次にDLLのヘッダーファイルのあるフォルダに標準ヘッダーファイルを作製し、最後にExeの側でこれをインクルードする、とこの3つの手順を踏むことで、Exe側はビルドできます。
 で、ここからが結構大変だったり……。

DLLの位置と自動コピー
 さて、DLLが完成し、Exeもビルドが終了した。じゃーデバッグテストで実行してみよう、とそのまえにDLLがどのフォルダにあるかを確認してください。
 「DLLを使おう!!」で説明したように、DLLは特定のフォルダにある必要があります。Exeファイルやシステムフォルダ、ウィンドウズフォルダにDLLがなければ、Exeは異常終了します。これはデバッグ時でもなんでも同じです。
 とゆーわけで、まだ置いていない場合には、作製したDLLを特定のフォルダにコピーしてください。システムフックのように特定のExeでしか使わないDLLはExeと同じフォルダに、MFCのように複数のアプリケーションから使用するDLLはシステムフォルダにコピーするのがいいでしょう。

 でも、DLLをいちいちドラッグアンドドロップしてコピーするのは非常にめんどくさいです。ですが、これは簡単に解決します。これを自動化しましょう。
 DLL側のプロジェクトを開き、「プロジェクト」−「設定」ダイアログの右端のタブ「ビルド後の処理」のページを開いてください。このページでは、ビルド後にMS−DOSコマンドを使用することができます。
 「ビルド後の処理の説明」欄は、ビルド時にアウトプットに表示される文字列です。適当な言葉を入れておいてください(筆者は「DLLのコピー」と入れています)。
 次に「ビルド後の処理コマンド」にコピーのコマンドを追加します。MS−DOSのコピーコマンドはそのまんまcopyです。書式は


	copy コピー元ファイル名 コピー先フルパス+ファイル名
	

 となります。第1パラメータはプロジェクトファイルのあるフォルダです。ですから、ここではdebug\t_DLL.dllとなります。
 第2パラメータは、コピー先のフルパスです。最後は、そのコピーするファイル名になります(つまり、ファイル名を変えてコピーすることもできるということです)。システムフォルダにコピーするのであればC:\windows\system\t_DLL.dllということになります。つまり、


	copy debug\t_DLL.dll C:\windows\system\t_DLL.dll
	

 のように書き込めばいいということです。もしこのDLLが、ひとつのアプリケーションからしか利用されないのであれば、第2パラメータをそのアプリケーションの実行ファイルのあるフォルダへとコピーすればOK。簡単ですね。
 デバッグ版とリリース版のファイル名が同じ場合には、同じフォルダ(特にシステムフォルダ)にコピーされると混乱します。そういう場合には、デバッグ版のみのコピーをお奨めします。

 さて、DLLのコピーも終了しました。これでもうデバッグもできますし、添付して配布することもできます。
 でも、なんでデバッグができるんでしょうか。システムフォルダのDLLにステップインすると、ちゃんとプロジェクトフォルダにあるソースコードが表示されます。この仕組みを解説しましょう。

プログラムデータベース
 DLLの作成時に同時に生成されたファイルの中に、拡張子がpdbのファイルがあると思います。
 このファイルはプログラムデータベースと呼ばれ、デバッグのための情報が書き込まれています。
 プログラムデータベースには、DLLやExeのどの部分がソースコードのどの位置にあたるのかが書き込まれています。デバッガを実行すると、デバッガはこのファイルを読み込みブレークポイントの設定にあわせてソースコードを表示します。

 ソースコードの検索は、3段階に行われます。
 まず、プログラムデータベースにはソースファイルの位置がフルパスで書き込まれています。まずこのファイルを検索します。
 見つからなかった場合には、インクルードファイルなどと同じく「検索ディレクトリ」に書き込まれたフォルダを検索します。
 それでも見つからないと「ソースファイルの検索」というダイアログが表示され、ソースファイルのあるフォルダがどこにあるのか訊かれます。
 ここでキャンセルすると、とうとうソースファイルの検索を諦め、DLL内のコード(いわゆるアセンブリコード)を表示します。
 なんと言っても特徴的なのはフルパスが書き込まれているという点です。一応補助的な検索機能はあるとはいえ、フルパスで直接というのはなかなか強烈です。

 さて、もうひとつ疑問が残ります。それはプログラムデータベースファイルの検索です。ソースファイルと違い、「検索ディレクトリ」は使用しません。……イヤな予感のしたあなた、正解です。
 実は、デバッグ版のExeファイルにはpdbファイルの位置がフルパスで書き込まれています。これはぜひ自分で確かめてみてください。たぶん最後の方に書いてあると思います。デバッガはDLLを読み込んだとき、まずこのフルパスでpdbファイルを読み込んでみます。
 ない場合には、デバッガはDLLの置かれたフォルダを検索し、pdbファイルを探します。注意して欲しいのは、DLLのような検索を行うわけではないということです。DLLがシステムフォルダにあるのなら、システムフォルダしか探しません。Exeのあるフォルダに置いても、なんの意味もないということです(おそらくプリコンパイル済みファイルの関係で、同名のpdbファイルが存在しやすいためだと思われます)。
 で、これで検索は終わり。見つからなかった場合には、DLLのコードさえ表示せず、何事もなかったかのように次の行へと移ります。

 以上のように、プログラムデータベースはフルパスが多く使われています。これはふたつの点で注意が必要です。
 ひとつは、フルパスに2バイト文字が入っているとデバッグができないという点です。たぶんこれはMSDEVのバグだと思います。「DLLにステップインできないよ〜」という方はこの部分を注意してみてください。
 もうひとつは再配布の問題です。他の方のハードディスクにコピーするのですから、フルパスはまったく意味をなしません。使ってもらう方に「ソースファイルの検索ディレクトリをちゃんと指定してもらうこと」と「DLLと同じフォルダにpdbファイルを置いてもらうこと」の2点を説明する必要があるでしょう(インストール時に書き換えるっていう手もあるけど)。
 もちろんこれは、自分で使う場合にもプロジェクトファイルの移動などは難しいということです。うまくいかないときには以上のような部分に注意してみてください。

再配布するDLL
 さて、DLLが完成した、Exeも完成した、デバッグも終了した、じゃぁホームページに掲載しましょう、という段階です。
 まず必要なのが、自作DLLファイルとそれを使用するExeファイル。これは当然ですね。どちらもリリースバージョンだということを確認してください(「デバッグ」と「リリース」については次回説明する予定です)。
 自作DLLが「拡張DLL」の場合、もう2ファイル、MFC42.dllMSVCRT.dllの2ファイルが必要になります。前者はMFCが、後者はランタイム関数が入っているDLLです。

 この2ファイルに関しては注意が必要です。まず、標準インストールのウィンドウズ95には入っていません。また、バージョンの問題もあります。MFC42.dllは、VCの4.2と5.0で使用されていますが、4.2に添付されたものは5.0で作られたExeやDLLとリンクできません。クラス等、追加されたものがあるからです。
 以上のことをふまえて、この2ファイルを再配布する必要があります(もちろんマイクロソフトによって許可されています)。ただし、圧縮して600KB弱……ちょっと大きいですね。
 実際、DLLを作製する場合(ってゆーかVCで作製する場合)にはこれが大きなネックになっています。各社のアプリケーションにも添付されていることが多いため、インストールする必要がない場合も確かにありますが、ある場合ももちろんあります。できれば、インストーラなどを使って、安全にインストールするのがいいでしょう。

 ちなみに、ExeがどのDLLを使用しているのか簡単に調べる方法を解説しておきます。
 Exeをデバッグモードで実行すると、アウトプットにその時点でウィンドウズがロードしているDLLが表示されます。
 その中で「'*.dll' をロードしました、合致するシンボル情報は見つかりませんでした。」と表示されたものは、配布する必要はありません。
 「'*.dll' のシンボルを読み込みました」は、そのとき初めてロードされるDLLです。これはデバッグバージョンで使用するDLLなので、このリリースバージョンのDLLが、配布する必要のあるDLLということになります(デバッグバージョンのDLLは通常のアプリケーションでは使用されないため、「すでにロードされている」ということはないはずですから)。

 もう一度書きます。「'*.dll' のシンボルを読み込みました」と書かれているDLLのリリースバージョンを配布してください。MFC42.dllのデバッグバージョンはMFC42d.dllMSVCRT.dllのデバッグバージョンはMSVCRTd.dllです。で、配布するのはあくまでリリースバージョンですから。念を押すようですけど、くれぐれも注意してください。

プログラマーに公開する場合
 同様に再配布する場合でも、アプリケーションとしてではなく、他のプログラマーが使えるようにDLLを公開したい場合もあるでしょう。
 そういう場合、最低限必要なのは、リリースバージョンのDLLライブラリファイル、そしてヘッダーファイルです。この3つはかならず配布してください。
 でも、使ってくれる人のことを考えるのなら、やっぱり全部がいいでしょう。つまり、上の3つに加えて、デバッグバージョンのDLL&LIB、デバッグに必要なプログラムデータベース、そしてソースコード

 これらがあるとないとでは、デバッグのしやすさが全然違います。確かに、ソースコードを配布するのは勇気がいるかもしれません。でも、コードを見てもらい、使ってもらうことで自分のプログラムを鍛えてもらえるということは大きいことです。できるだけ、全部添付するようにしましょう。
 あ、こういう場合には「これさえインクルードすれば全部OKヘッダーファイル」も添付した方がいいでしょう。

まとめ
 今回はちょっとまとまりがなかったかなーとゆー気がしないでもないです。「DLL関係の細かな注意」みたいになっちゃったかも。でも、DLL関係で分かりにくい部分は網羅したつもりです。どうでしょー。
 次回は、関数ではなくクラスを作ってみます。また、「デバッグバージョン」と「リリースバージョン」のふたつのDLLを作る方法や、DLL側でデバッグする方法などを解説しようと思います。

(C)KAB-studio 1998 ALL RIGHTS RESERVED.