DLLの実行中リンク

 実行ファイルがDLLの中にある関数やクラスを使うためには、実行ファイルが「DLLをロード」する必要があります。「ロード」を行うことで、実行ファイルの中にDLLが組み込まれ、使用できる状態になります(あ、「ロード」と「リンク」という単語がごっちゃになっていますが、ほとんど同じと考えていいと思います)。
 この「DLLのロード方法」にはふたつの方法があります。それは「実行時(Load-Time)リンク」と「実行中(Run-Time)リンク」です。

 「実行時リンク」は、前回説明した「アプリケーションが実行されたとき」にリンクされるものです。この方法は非常に簡単で、DLLのライブラリファイルさえあれば、コンパイラが自動的に「ロードの機能」を組み込んでくれます。この方法が「暗黙的リンク」と呼ばれるのもこのためです。
 しかし、この方法にはいくつかの問題があります。
 まず、状況に合わせてDLLを選ぶということができません。OSのバージョンやインストールされているアプリケーションによって、Systemフォルダ内のDLLは変わってきます。常に適切なDLLを選ぶということができません。
 また、DLLが見つからなかった場合の対処方法が貧弱です。見つからなかった場合、よく分からないメッセージボックスが現れたあと、強制的にアプリケーションが終了させられます。ファイルの圧縮・解凍や画像表示などの「プラグイン」を装備したいときには、これでは問題があります。
 さらに、起動時間を遅くさせることにも継ながります。たったひとつの関数を使用するために何百キロもあるDLLを読み込めば、それだけ重くなります。
 プログラムとしても、いくつか弊害があります。例えば、配布されているDLLにライブラリファイルが付いていない場合には、前回説明した方法ではリンクすることができず、実行ファイルが作製できません。

 これらの問題は「実行中リンク」を行えば解決できます。この方法は「DLLのロード及び関数の呼び出し」を「プログラムの中に」書き込みます。つまり、リンカが設定してくれる部分をプログラム側でインプリメントするということです。
 この方法だと「関数しか使えない」「めんどくさい」「危なっかしい」といったデメリットがあるため、普通は使う必要はありません。が、場合によっては「暗黙的リンク」のデメリットがアプリケーションにとって大きなネックになる場合があります。そういう時には必要な方法です。

関数の定義
 さて、実際の例を追ってみましょう。
 あ、プロジェクトは前回のものをそのまま流用しても大丈夫でしょう。ただ、「実行中リンク」をテストするわけですから、前回追加したStdAfx.hのライブラリファイルとヘッダーファイルの設定は削除してください
 あと、目的は前回と同じ、sndPlaySound()を使用することです。

 まず、「関数の定義」というものを作製します。書式は次のような形です。書き込む場所はどこでもOK。ヘッダーファイルの上の方にでも書いておいてください。


	typedef BOOL (sndPlaySound)( LPCSTR, UINT );

//	typedef 戻り値の型 (関数名)( 引数の型 );	//とゆー書式。
	

 ちょっと見慣れない形ですね。まず、typedefというのは、C言語キーワードのひとつで、新しい型を作るものです。上の例では「sndPlaySoundBOOL型の関数ですよー」という意味になっています。
 右側の小カッコの中身は、関数の引数です。引数は、型だけで引数名は付けません。

 ちょっと慣れないかもしれませんね。憶えるヒントとしては「#defineと左右逆」っていうところでしょうか。
 ま、この行とAPIヘルプとの戻り値や引数などを見比べて「この関数をこうやって定義すればいいのかぁ」ということを憶えておいてください。MFCとか、その他サンプルでも結構使われている形なんで、検索すれば例には困らないでしょう。

DLLのロード
 関数の定義が終わったら、早速使ってみましょう。前回の例でsndPlaySound()を使った場所を、次のコードで置き換えてください。


	HINSTANCE	hInst;
	sndPlaySound	*pfnDllFunc;

	hInst = ::LoadLibrary( "winmm" );	//DLLを読み込みます。
	if( hInst == NULL )
	{
		MessageBox( "DLLをロードできませんでした。" );
		return;
	}

	pfnDllFunc = (sndPlaySound *)::GetProcAddress( hInst, "sndPlaySoundA" );	//関数のアドレスを取得します。
	if( pfnDllFunc == NULL )
	{
		MessageBox( "関数を取得できませんでした。" );
		::FreeLibrary( hInst );       //DLLを解放します。
		return;
	}

	pfnDllFunc( "Test.wav", 0x0001 | 0x0002 );	//音発生!

	::FreeLibrary( hInst ); 
  	

 まずは、sndPlaySound *型の変数の作製です。これはさっき使った型を使います。注意して欲しいのはポインタだということです。
 基本的に、関数のやりとりを行う場合には、その関数へのポインタを使います。例えば「ウィンドウクラスの登録」(詳しくは「MFCユーザーのためのAPI講座(以下MFCxAPI講座)・ウィンドウクラスの登録」を見てね)を行うときには「ウィンドウプロシージャとして登録する関数へのポインタ」を渡します。
 このように「プログラム中で呼び出す関数を動的に変更したい」時には、関数へのポインタを使用することになります。だから、sndPlaySoundのポインタを作るわけです。
 ちなみにtypedefのことをよく知ってる人は「なんでtypedefでポインタのを作らないの?」と思うでしょう。単に分かりやすくするためです(爆)。ポインタ付きのは、(*sndPlaySound)にすればOK。*を付けるだけですね。

 話を元に戻しましょう。DLLのロードはLoadLibrary()というAPIを使用します。もうただ単純に、第1引数にDLLのファイル名(拡張子は必要なし)を入れればいいだけです。
 この関数は、通常の「実行時リンク」と同じように、実行ファイルがあるフォルダやWindowsフォルダ、Systemフォルダの中にこのファイル名のDLLがあるか探しだし、見つかればロードして、このDLLの「インスタンスハンドル」を返します。まぁこれは深く考えず「アプリケーションのハンドル」とでも考えておけば大丈夫でしょう。
 見つからなかった場合にはNULLを返します。これがもっとも違う部分でしょう。これによって、「他のDLLを探す」とか「何もせず処理を続行する」といった、見違えるほど高いユーザーインターフェイスを提供することができるようになるのです

 ちなみにこの関数のMFCバージョンにAfxLoadLibrary()という関数があります。こっちの方は、クリティカルセクションというシンクロ(同期)システムを使用して、複数のスレッドがMFCのグローバル変数を破壊しないようにしているらしいです。マルチスレッドなアプリケーションで使用する場合には、AfxLoadLibrary()の方を使った方がいいでしょう。

関数へのポインタの取得
 DLLをロードしたら、今度は関数へのポインタを取得します。そのためのAPIがGetProcAddress()です。第1引数には先ほど取得したDLLのインスタンスハンドル、第2引数は関数の名前です。成功すれば、関数へのポインタが返ってきますので、型キャストして先ほど作った変数で受け取ってください。
 ここでも、失敗した場合にはNULLが返ってきます。このときも同様に、様々な後処理を行うことができます。

 ここで注意して欲しいのが、呼び出している関数名がsndPlaySoundAだということです。最後のAってなんやねん!!
 「MFCxAPI講座・ポインタと文字列とCStringと」で軽く触れていますが、ウィンドウズには二種類の文字コード体系があります。
 ひとつはアスキーコード。これが今まで使われてきた文字コードで、一般的なものです。1文字を1バイトで表します。また、漢字等は2文字を組み合わせて表現します。
 もうひとつはユニコード。これは比較的新しい文字コードで、Win95では使われていません(そういえばWin98はどうなんだろう。CEは使えるはずだけど)。これは1文字を2バイトで表し、欧米の特殊アルファベット表記や漢字をすべてのPCで表せることを目標としています。
 ウィンドウズプログラミングでは、文字列を渡すランタイムやAPIのほとんどが、このふたつをプリプロセッサ(#defineとか#ifdefとか)を使って使い分ける仕組みになっています。

 さて、実際にsndPlaySound()を、MSYSTEM.Hの中から探してみると……。


#ifdef UNICODE
#define sndPlaySound  sndPlaySoundW
#else
#define sndPlaySound  sndPlaySoundA
#endif // !UNICODE
	

 と、こんな風に書いてあるわけですねー。
 これはつまり、DLL側でエクスポートされているのはsndPlaySoundW()とsndPlaySoundA()の両方ということです。そして、sndPlaySound()では関数を呼び出せないということです。
 いやー、しちめんどくさくなってきましたねー。前回はたーった2行追加しただけでAPIを使うことができたのに、いやはや……。
 と、話はまだ終わっていません。コードの続きを見てみましょう。

関数を使う!!
 というわけで、やっとこさめでたく、関数を使うことができました。
 この関数の部分だけは前回と同じ……と思ったら違うではありませんか!! 前回の第2引数は「SND_ASYNC | SND_NODEFAULT」となっていたのに、今回は16進数で表されています。
 これは、MSYSTEM.Hを見てみれば分かるでしょう。各16進数が上のフラグとして定義されています。今回はこのヘッダーファイルを読み込めないので、こうして整数値を設定しているというわけです。
 ちなみにこれは悪い見本です!! ちゃんとプログラムを組むときには、自分自身でこの定義を行いましょう。ソースコードの中に整数や16進数があるのは良くないことです(「0」とか「/2」とかは例外……かな?)。

 それにしても、ただ関数を呼び出すだけでも、色々弊害が出てきます。そしてこれは、もっと大きな問題も抱えています。
 もし「次期ウィンドウズ」とかで、このフラグの値が変わってしまったらどうなるでしょう。たったこれだけで、互換性なんてものは吹っ飛んでしまいます。
 でも、ヘッダーファイルを読み込んだ場合には、その「次期」ヘッダーファイルを使ってリビルドすれば、たったそれだけで「次期」バージョンの出来上がり。なんの心配もいりません。
 と、もちろんこんなにうまくはいきませんが、移植の手間とバグの可能性が減ることは確かです。

DLLのアンロード
 まだちょっと残ってました。LoadLibrary()を使ってロードしたDLLは、FreeLibrary()で解放する必要があります。ま、この「取得して、使った後は解放ね」はウィンドウズプログラミングのお約束なんで、大丈夫でしょう。
 ちなみにこれのMFC版AfxFreeLibrary()という関数もあります。そういえば、こっちの関数を使用する利点として、解説が日本語で書かれているっていうのがありますね(汗)。

こんなの何の役に立つの?
 分かんないです(汗)。実際、この方法はデメリットの方が多いです。もちろん、今回みたいに「標準で使えるAPI」にはこの方法は向きませんsndPlaySound()を例にしたのは、前回とコントラストをつけるためです。普通は、前回のようにライブラリファイルを用いて行います。
 ただ、中には「アンドキュメンテッド」なDLL&関数も存在します。そういうものにはたいていライブラリファイルが付いていないので、こういう関数を使用する場合には必要な技術です。
 それと一応、今度紹介するAPIベースのシステムフックとかで使うんで、その説明の時に出てくるでしょう。でもMFCベースのシステムフックもそのあと紹介する予定なんで、はっきり言ってあんま意味はないです(汗)。ま、なんかで役立つこともあるでしょう。
 次回は……なんだろう(汗)。たぶん「DLLの作り方」をやります。たぶん。

おまけ
 これはちょっと裏技的なものになってしまいますが、最終的には何もなくてもOKだったりします。

 まず使いたいDLLの中を見て「どんな関数やクラスをエクスポートしてるか」ってことを調べます(別にバイナリエディタやテキストエディタで見る必要はありません。Dumpbin.exeというVC付属のツールを使えば、どんな関数がエクスポートされてるか出力できますので)。
 次にDLLのプロジェクトを作り、、エクスポートされている関数のプロトタイプ宣言が入ったヘッダーファイルを自分で作り、ソースファイルの方は何もしない関数を定義して、エクスポート部分はまったく同じDLLを作ってしまいましょう。
 これをビルドすればあーら不思議、ライブラリファイルが(無理矢理)できちゃいます
 あとは、このニセライブラリファイルを使ってExeファイルを作り、DLLだけ本物のを使えばOK。もちろんこの方法は「ライブラリファイルだけない」という場合にはもっと楽になります。ちょっとした裏技でした。

(C)KAB-studio 1998 ALL RIGHTS RESERVED.