システムフック

 さて、ユーティリティーアプリケーションにとって欠かすことのできない「システムフック」について見てみましょう。
 と、今回のコードは前回のものをかなり流用するのでご注意を。それだけ、ローカルフックとシステムフックはほぼ同じということでもありますね。それに、今回はDLLを作製するので、その辺もマスターしてからお読みください。

ローカルフックとの違い
 グローバルフックは、単純に言えばすべてのスレッドにセットされるローカルフックです。基本的なシステム、方法はローカルフックとほとんど同じですので、フックをセットすること自体はそれほど難しいことではありません。

 ただ、次のふたつの点に注意してください。
 まずひとつは、システムフックを使用すると、他のアプリケーションがセットしたフックとフックプロシージャが継ながるということです。前回説明したとおり、SetWindowsHookEx()を使ってフックプロシージャを橋渡しさせていく必要があります。ここで失敗すると他のアプリケーションがセットしたフックを殺してしまうことになってしまいます。注意してください。
 もうひとつの点は、フックプロシージャをDLLに置く必要があるということです。グローバルフックは、DLLをすべてのスレッドにセットする必要があります。つまりフックされたメッセージの処理は、ExeではなくDLLで行うということです。またこれは新たな問題を生みます。それは、それぞれのDLLがグローバル変数を持つということです。前回作製したクラスのスタティック変数なども、DLLごとに持ってしまうことになってしまいます。この問題にも対処しなければなりません。

 ひとつめの問題は、SetWindowsHookEx()を適正に呼べば問題はないでしょう。というわけで、今回はふたつめの点について具体的に見ていこうと思います。

DLLへの移植  では、前回作製したCKeyboardHookクラスをDLLに移植しましょう。
 まずDLLのプロジェクトを作製します。今回のような「特定のアプリでしか使わないDLL」は、Exeのプロジェクトの隣くらいに作製するといいでしょう。プロジェクトは「MFCの拡張DLL」、前にDLLを作製したときと同じです。
 次に、前回作製したExeのプロジェクトフォルダからフックの入ったファイル、KeyboardHook.cppKeyboardHook.hを今回作製したDLL側のプロジェクトフォルダへとコピーしてください。エクスプローラー等を開いてそのままコピー、です。移動したら、「プロジェクト」−「プロジェクトへ追加」−「ファイル」でこのふたつのファイルを追加してください。
 移動などをしたくない場合には、前回と同じように、プロジェクトワークスペースのクラスビューの右クリックを使って新規にクラスを作製してください。
 さて、ここまでがいわゆる「ローカルフック」と同じ手順です。ここからはシステムフック独自の処理をしていきます。

インスタンスハンドルの取得
 SetWindowsHookEx()の第3引数、前回はNULLを渡しました。システムフックの場合、ここにDLLのインスタンスハンドルを渡す必要があります。
 ところが、AfxGetInstanceHandle()ではExeのインスタンスハンドルが返ってしまうため、DLLのインスタンスハンドルを取得する関数を作製する必要があります。
 DLLのインスタンスハンドルはDllMain()の第1引数に渡されます。プロジェクト名.cppを開いて確認してみてください。このインスタンスハンドルを、KeyboardHook.cppから取得できるようにすればいいわけです。

 というわけで、プロジェクト名.cppDllMain()の前半部分を次のようなコードに書き変えてください。


static HINSTANCE g_hDllInst = NULL;

HINSTANCE GetThisHInst()
{
	return g_hDllInst;
}
// 上の6行を追加。

extern "C" int APIENTRY
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
	g_hDllInst = hInstance;	//この行を追加。
	// あとはそのまま。
	

 まずインスタンスハンドルをグローバル変数に格納します。(ホントはこのさらに上にあるAFX_EXTENSION_MODULEにも格納されています)。そして、このインスタンスハンドルを関数から返すようにします。
 でもこのままでは呼び出せないので、プロトタイプ宣言を作製します。「ファイル」−「新規作製」で「C/C++ヘッダーファイル」を選び、プロジェクト名.hというファイルを作製してください。作製したら、そのファイルに1行を書き込んでください。


HINSTANCE GetThisHInst();
	

 この関数を呼び出せば、DLLのインスタンスハンドルを取得できるというわけです。そこで、KeyboardHook.cppの最初のインクルード部分で、プロジェクト名.hをインクルードして、ついでに、移動前のExe側のファイルへのインクルードが残っていると思うので、それを削除してください。
 では、この関数をさっそく使いましょう。KeyboardHook.cppCKeyboardHook::Set()を次のように書き換えてください。


BOOL CKeyboardHook::Set()
{
	m_hHook = ::SetWindowsHookEx( WH_KEYBOARD
					, (HOOKPROC)CKeyboardHook::KeyboardProc
					, GetThisHInst()	//この行を変更。
					, 0 );

	if( !m_hHook )
		return FALSE;

	return TRUE;
}
	

 このような仕組みで、グローバル変数にしたDLLのインスタンスハンドルを取得して、SetWindowsHookEx()に使用します。

DLLの中でのグローバル化
 さて、あともう一息です。
 クラスが持つスタティック変数、フックハンドルはすべてのDLLで一意でなければなりません。そのための処理をします。
 同じくKeyboardHook.cppの上の方で、スタティック変数の初期化を行っているはずです。その部分を次のように書き換えてください。


#pragma data_seg( ".CKHookData" )
	HHOOK	CKeyboardHook::m_hHook = NULL;
#pragma data_seg()
	

 #pragmaはすでに使っているでしょう。ライブラリファイルを検索リストに加えるのに使っています。今回は別の使い道です。
 #pragma data_seg()のネストに含まれる変数は、指定されたセクション名(ここでは.CKHookData、ピリオドを忘れずに)が付けられます。
 「セクション」は、マシン語のコードの指定された一部のことです。ここでは、スタティック変数の初期化部分にあたるセクションに名前が付けられたことになります。
 でも、名前が付けられただけでは意味がありません。次のように定義ファイル(プロジェクト名.def)へと書き加える必要があります。


; すべてのDLLで共通のグローバルな変数です。
SECTIONS
	.CKHookData	READ WRITE SHARED
	

 こうすることで、.CKHookDataのセクションは「READ WRITE SHARED」、つまり書き込みと読み込みを共有という指定になります。こうすることで、すべてのDLLで共通の変数を使用できることになります。このおかげで、フックのチェーンを切らさずに済むというわけです。

クラスのエクスポート
 最後に、CKeyboardHookクラスをエクスポートしましょう。KeyboardHook.hのクラス定義部分を次のように書き換えてください。


class AFX_EXT_CLASS CKeyboardHook  
{
	// 以下略。
	

 「DLLを作ろう!(関数編)」で簡単に触れましたが、実はAFX_EXT_CLASSを使うだけで簡単にエクスポートできてしまいます。
 ただ、このマクロが__declspec(dllexport)に置き換えられるのは_AFXDLL_AFXEXTのふたつのフラグが立てられている場合です。今回は拡張DLLを作製しているので、「設定」ダイアログの「C/C++」−「一般」ページの「プリプロセッサの定義」にこのふたつが書かれているはずです。
 もしこのDLLを、他の拡張DLLから使用すると、同じようにこのフラグが立てられてしまうため、さらにエクスポートされてしまいます。今回はExeからのみ呼び出すので、こういった横着をしました。この辺をうまく使い分けてください。

 さて、ここまででDLL側は完成です。ビルドして、DLLをExeへとコピーしてください。今度は、Exe側です。

「隣のDLL」を使う
 さて、Exe側です。これまでにもDLLの使い方は説明しましたが、今回は「隣」、つまりこのExeでしか使わないDLLの使い方を説明しましょう。
 まず、今回DLL側に移動したクラスがまだ前のプロジェクトに残っていると思います。それをプロジェクトから外しておきましょう。
 「プロジェクトワークスペース」の「ファイルビュー」で、KeyboardHook.cppKeyboardHook.hを削除してください(クリックしてデリートキーを押してください)。
 次に、プロジェクト中のファイルでKeyboardHook.hをインクルードしている部分がいくつかあると思うので、それをすべて削除してください。
 最後に、StdAfx.hの最後の方に次のコードを追加してください。


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

 まず#pragmaを使ってライブラリファイルを検索リストに加えます。これはおさらいですね。あ、今回、DLL側のプロジェクト名を指定していなかったので、皆さんが作製したものに書き換えてください。
 次にフッククラスのヘッダーファイルをインクルードします。ここで注意するのは、相対パスを書き込んでいること、そして""(ダブルクォーテーション)で囲んでいることです。

 これまでは、DLLのヘッダーファイルは「オプション」ダイアログの「ディレクトリ」ページでフォルダを指定していました。が、今回のような「特定のExeでしか使わないDLL」の場合にはそういった設定は邪魔になるので、DLLを作る段階でプロジェクトをExeの近くに作製し、このように相対パスで指定する方法を採ったというわけです。
 さらに、ライブラリファイルも「ディレクトリ」の指定を使わずに使用しましょう。「設定」ダイアログの「リンク」−「インプット」ページの「追加ライブラリのパス」に、ライブラリファイルを置いてあるフォルダへのフルパスを書き込んでください。(注:以前「ライブラリファイルへのフルパス」と書いてあったのは間違いです。フォルダへのパスなのでご注意を)
 ここでの指定は、そのまま「ディレクトリ」ページでの指定とまったく同じものになります。このように指定することで、このExeのプロジェクトからのみのライブラリファイルを指定することができるというわけです。

 で、ここまで来ればOK。ビルドして実行してみましょう。ローカルフックのときにできたこと、つまり「キーのインクリメント」がすべてのアプリケーションで機能するはずです。

まとめ
 実は今回のサブタイトルは「DLLの使い方・特別編」の方が合ってると思います。というわけでまとめてみましょう。

 システムフックを作製する、という点で重要なのは次のみっつ。
 フックプロシージャをDLLに置くことがまず重要。これをしないと単なるローカルフックです。
 セット時にインスタンスハンドルを使うのも重要。でも、このふたつは単に自分が死ぬだけです。
 フックハンドルをシステムグローバルにすることは、おそらく最も重要なこと。これをしないと他のフックを殺しますので、これだけは忘れないでください。
 それ以外の部分はすべてローカルフックで解説しています。フックのセットとリセット、フックプロシージャのチェーン化等、まずローカルフックで理解して、それからシステムフックに使いましょう(僕も耳が痛い(泣))。

 さて、今回はDLLの使い方についてもかなり書きました。
 エクスポートにAFX_EXT_CLASSを使うのは、まぁMFCを使うかどうか、みたいなところです。逆に言えば、MFCのコードを見るということがかなり勉強になる、ということでもあります。
 DLL内の変数の公開方法も、MFCに似せてあります。が、この方が安全でしょう。直接グローバル変数にアクセスできると、書き換えたりできてしまって安全性が下がります。なら、こういった関数で「リードオンリー」にするのが一番でしょう。
 特別なDLLの指定は比較的使い道のある方法でしょう。KCL9542のようないくつものプロジェクトから使うDLLと、今回のようなDLLとで設定を使い分けると分かりやすくなると思います。
 DLL間でのグローバル化もDLLの話ですが、実際にはシステムフックぐらいでしか使わないでしょう(汗)

 と、以上でフックの使い方について見てみました。ここまでが「DLL・フック」編というカテゴリーに属してるように、ここまでをひとつの流れとしています。皆さんの中には「システムフックのことだけ分かればいい」という方もいると思います。そういう方には、分かりにくい書き方をしてしまったなと感じてます。
 ただ、システムフックを使う上でここまでを理解することは決して無駄ではないと思います。システムフックの中にはフックプロシージャを実行中リンクするサンプルプログラムもあります。そういったとき、「DLLの実行中リンク」やそれ以降の装飾名の話は役立つはずです。
 ま、実際には色々な情報をいろんなところに散りばめてしまっているので、一度流し読んで、分からなくなったらまた最初から、というのも悪くないかなと思います。

 さて、次回は「サブクラス化」について見てみようと思います。
 サブクラス化もフックと同様、ユーティリティーアプリケーションには欠かせない機能のひとつとなっています。ところが、ユーティリティーに使うような「他プロセスへのサブクラス化」には実はシステムフックを使わなければなりません。というわけで、この目標があったからこそ、ここまでがひとつのカテゴリになっていたわけです。
 都合によりだいぶ先になってしまいますが、そのときまでお待ちください。

(C)KAB-studio 1998 ALL RIGHTS RESERVED.