さて、ユーティリティーアプリケーションにとって欠かすことのできない「システムフック」について見てみましょう。
と、今回のコードは前回のものをかなり流用するのでご注意を。それだけ、ローカルフックとシステムフックはほぼ同じということでもありますね。それに、今回はDLLを作製するので、その辺もマスターしてからお読みください。 |
ローカルフックとの違い
グローバルフックは、単純に言えばすべてのスレッドにセットされるローカルフックです。基本的なシステム、方法はローカルフックとほとんど同じですので、フックをセットすること自体はそれほど難しいことではありません。
ただ、次のふたつの点に注意してください。
ひとつめの問題は、SetWindowsHookEx()を適正に呼べば問題はないでしょう。というわけで、今回はふたつめの点について具体的に見ていこうと思います。 |
DLLへの移植
では、前回作製したCKeyboardHookクラスをDLLに移植しましょう。
まずDLLのプロジェクトを作製します。今回のような「特定のアプリでしか使わないDLL」は、Exeのプロジェクトの隣くらいに作製するといいでしょう。プロジェクトは「MFCの拡張DLL」、前にDLLを作製したときと同じです。 次に、前回作製したExeのプロジェクトフォルダからフックの入ったファイル、KeyboardHook.cppとKeyboardHook.hを今回作製したDLL側のプロジェクトフォルダへとコピーしてください。エクスプローラー等を開いてそのままコピー、です。移動したら、「プロジェクト」−「プロジェクトへ追加」−「ファイル」でこのふたつのファイルを追加してください。 移動などをしたくない場合には、前回と同じように、プロジェクトワークスペースのクラスビューの右クリックを使って新規にクラスを作製してください。 さて、ここまでがいわゆる「ローカルフック」と同じ手順です。ここからはシステムフック独自の処理をしていきます。 |
インスタンスハンドルの取得
SetWindowsHookEx()の第3引数、前回はNULLを渡しました。システムフックの場合、ここにDLLのインスタンスハンドルを渡す必要があります。 ところが、AfxGetInstanceHandle()ではExeのインスタンスハンドルが返ってしまうため、DLLのインスタンスハンドルを取得する関数を作製する必要があります。 DLLのインスタンスハンドルはDllMain()の第1引数に渡されます。プロジェクト名.cppを開いて確認してみてください。このインスタンスハンドルを、KeyboardHook.cppから取得できるようにすればいいわけです。
というわけで、プロジェクト名.cppのDllMain()の前半部分を次のようなコードに書き変えてください。 |
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.cppのCKeyboardHook::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.cppとKeyboardHook.hを削除してください(クリックしてデリートキーを押してください)。 次に、プロジェクト中のファイルでKeyboardHook.hをインクルードしている部分がいくつかあると思うので、それをすべて削除してください。 最後に、StdAfx.hの最後の方に次のコードを追加してください。 |
// ライブラリファイルを読み込みます。
#pragma comment(lib, "KHook.lib")
#include "../KHook/KeyboardHook.h"
まず#pragmaを使ってライブラリファイルを検索リストに加えます。これはおさらいですね。あ、今回、DLL側のプロジェクト名を指定していなかったので、皆さんが作製したものに書き換えてください。
次にフッククラスのヘッダーファイルをインクルードします。ここで注意するのは、相対パスを書き込んでいること、そして""(ダブルクォーテーション)で囲んでいることです。
これまでは、DLLのヘッダーファイルは「オプション」ダイアログの「ディレクトリ」ページでフォルダを指定していました。が、今回のような「特定のExeでしか使わないDLL」の場合にはそういった設定は邪魔になるので、DLLを作る段階でプロジェクトをExeの近くに作製し、このように相対パスで指定する方法を採ったというわけです。
で、ここまで来ればOK。ビルドして実行してみましょう。ローカルフックのときにできたこと、つまり「キーのインクリメント」がすべてのアプリケーションで機能するはずです。 |
まとめ
実は今回のサブタイトルは「DLLの使い方・特別編」の方が合ってると思います。というわけでまとめてみましょう。
システムフックを作製する、という点で重要なのは次のみっつ。
さて、今回はDLLの使い方についてもかなり書きました。
と、以上でフックの使い方について見てみました。ここまでが「DLL・フック」編というカテゴリーに属してるように、ここまでをひとつの流れとしています。皆さんの中には「システムフックのことだけ分かればいい」という方もいると思います。そういう方には、分かりにくい書き方をしてしまったなと感じてます。
さて、次回は「サブクラス化」について見てみようと思います。 |
(C)KAB-studio 1998 ALL RIGHTS RESERVED. |