実行ファイルがDLLの中にある関数やクラスを使うためには、実行ファイルが「DLLをロード」する必要があります。「ロード」を行うことで、実行ファイルの中にDLLが組み込まれ、使用できる状態になります(あ、「ロード」と「リンク」という単語がごっちゃになっていますが、ほとんど同じと考えていいと思います)。
この「DLLのロード方法」にはふたつの方法があります。それは「実行時(Load-Time)リンク」と「実行中(Run-Time)リンク」です。
「実行時リンク」は、前回説明した「アプリケーションが実行されたとき」にリンクされるものです。この方法は非常に簡単で、DLLのライブラリファイルさえあれば、コンパイラが自動的に「ロードの機能」を組み込んでくれます。この方法が「暗黙的リンク」と呼ばれるのもこのためです。
これらの問題は「実行中リンク」を行えば解決できます。この方法は「DLLのロード及び関数の呼び出し」を「プログラムの中に」書き込みます。つまり、リンカが設定してくれる部分をプログラム側でインプリメントするということです。 |
関数の定義
さて、実際の例を追ってみましょう。 あ、プロジェクトは前回のものをそのまま流用しても大丈夫でしょう。ただ、「実行中リンク」をテストするわけですから、前回追加したStdAfx.hのライブラリファイルとヘッダーファイルの設定は削除してください。 あと、目的は前回と同じ、sndPlaySound()を使用することです。
まず、「関数の定義」というものを作製します。書式は次のような形です。書き込む場所はどこでもOK。ヘッダーファイルの上の方にでも書いておいてください。 |
typedef BOOL (sndPlaySound)( LPCSTR, UINT );
// typedef 戻り値の型 (関数名)( 引数の型 ); //とゆー書式。
ちょっと見慣れない形ですね。まず、typedefというのは、C言語キーワードのひとつで、新しい型を作るものです。上の例では「sndPlaySoundはBOOL型の関数ですよー」という意味になっています。
右側の小カッコの中身は、関数の引数です。引数は、型だけで引数名は付けません。
ちょっと慣れないかもしれませんね。憶えるヒントとしては「#defineと左右逆」っていうところでしょうか。 |
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のファイル名(拡張子は必要なし)を入れればいいだけです。
ちなみにこの関数のMFCバージョンにAfxLoadLibrary()という関数があります。こっちの方は、クリティカルセクションというシンクロ(同期)システムを使用して、複数のスレッドがMFCのグローバル変数を破壊しないようにしているらしいです。マルチスレッドなアプリケーションで使用する場合には、AfxLoadLibrary()の方を使った方がいいでしょう。 |
関数へのポインタの取得
DLLをロードしたら、今度は関数へのポインタを取得します。そのためのAPIがGetProcAddress()です。第1引数には先ほど取得したDLLのインスタンスハンドル、第2引数は関数の名前です。成功すれば、関数へのポインタが返ってきますので、型キャストして先ほど作った変数で受け取ってください。 ここでも、失敗した場合にはNULLが返ってきます。このときも同様に、様々な後処理を行うことができます。
ここで注意して欲しいのが、呼び出している関数名がsndPlaySoundAだということです。最後のAってなんやねん!!
さて、実際に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付属のツールを使えば、どんな関数がエクスポートされてるか出力できますので)。 |
(C)KAB-studio 1998 ALL RIGHTS RESERVED. |