フックの簡単なサンプル
では、フックでどんなイベントを取得できるのか、簡単なサンプルを作って確かめてみましょう。Delphiで新規アプリケーションを作成し、フォームにTMemoを1つとTButtunを2つを作ります。

そして、ボタンを押した時のイベントとフック関数を記述します。まずは、フックをインストールする部分を見てみましょう。
var HookHandle: THandle; procedure TForm1.btnInstallClick(Sender: TObject); begin HookHandle := SetWindowsHookEx(WH_MOUSE, @HookProc, HInstance, 0); end;
ここでは、ボタン[btnInstall]をクリックした時にフック関数をインストールしています。SetWindowsHookEx()の第1引数にWH_MOUSEを指定しているので、マウスに関するイベントをフックするようになります。第2引数には、後述しますがイベントがフックされた時に呼び出される関数を指定します。第3引数には、「HInstance」を指定します。これは、DelphiのSysInitユニットクラスで自動的に定義されているものです。
次に、フックを解除するボタンのイベントを作ります。
procedure TForm1.btnUninstallClick(Sender: TObject); begin UnhookWindowsHookEx(HookHandle); end;
フックを解除するにはUnhookWindowsHookEx()を使います。引数には、SetWindowsHookEx()関数の戻り値で得られるハンドルを指定します。
以下は、SetWindowsHookEx()関数の第2引数で指定したフック関数です。Windows APIで指定するコールバック関数には、関数の最後に「stdcall」をつけることになってますので、関数宣言の最後に「stdcall」の指定をしています。
function HookProc(code:Integer; wp:Integer; lp: Integer): LRESULT; stdcall; begin SendMessage(Form1.Handle, WM_USER_MOUSE_ACTION, wp, lp); Result := CallNextHookEx(HookHandle, code, wp, lp); end;
このフック関数では、メインウィンドウにイベントがあったことをSendMessage()で通知しています。その後CallNextHookEx()関数を呼んでいます。この関数はフック関数の最後で必ず呼ぶ必要があります。
以下がテストアプリケーションのすべてのプログラムです。ボタン[btnInstall]を押すとマウスメッセージをフックするようになり、マウスイベントの様子をMemo1に表示します。
unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; const WM_USER_MOUSE_ACTION = WM_USER + $1001; type TForm1 = class(TForm) Memo1: TMemo; btnInstall: TButton; btnUninstall: TButton; procedure btnInstallClick(Sender: TObject); procedure btnUninstallClick(Sender: TObject); private procedure onMouseAction(var msg: TMessage); message WM_USER_MOUSE_ACTION; end; var Form1: TForm1; implementation {$R *.dfm} var HookHandle: THandle; function HookProc(code:Integer; wp:WPalram; lp: LPalram): LRESULT; stdcall; begin SendMessage(Form1.Handle, WM_USER_MOUSE_ACTION, wp, lp); Result := CallNextHookEx(HookHandle, code, wp, lp); end; procedure TForm1.btnInstallClick(Sender: TObject); begin HookHandle := SetWindowsHookEx(WH_MOUSE, @HookProc, HInstance, 0); end; procedure TForm1.btnUninstallClick(Sender: TObject); begin UnhookWindowsHookEx(HookHandle); end; procedure TForm1.onMouseAction(var msg: TMessage); var s: string; pms: PMouseHookStruct; begin // Window Message case msg.WParam of WM_NCMOUSEMOVE: s := 'WM_NCMOUSEMOVE'; WM_NCLBUTTONDOWN: s := 'WM_LBUTTONDOWN'; WM_NCLBUTTONUP: s := 'WM_LBUTTONUP'; WM_MOUSEMOVE: s := 'WM_MOUSEMOVE'; WM_LBUTTONDOWN: s := 'WM_LBUTTONDOWN'; WM_LBUTTONUP: s := 'WM_LBUTTONUP'; else s := 'msg=' + IntToStr(msg.WParam); end; // Mouse Data pms := PMouseHookStruct(msg.LParam); s := s + Format(' (%d,%d)',[pms.pt.X, pms.pt.Y]); Memo1.Lines.Add(s); end; end.

実行して、フォームの上で、マウスを動かしたり、クリックしてみると、マウスメッセージが表示されます。しかし、このプログラムでは、自分自身のウィンドウに関するメッセージしかフックできません。
すべてのメッセージをフックする
すべてのウィンドウに送られるメッセージをフックするにはどうしたら良いのでしょうか。そのためには、メッセージをフックするための専用DLLを作る必要があります。そして、DLLの中でメッセージをフックして、クライアントプログラムへメッセージがあったことを伝えるという仕組みにします。

DLLを作るときの注意なのですが、ただDLLの中でフック関数を作るだけではダメで、共有変数を使ってすべてのプロセスから利用できるようにする必要があります。というのも、Win32では、DLL内のグローバル変数はプロセスごとに別に確保されてしまうからです。共有変数を使うことで、すべてのウィンドウをフックできるように工夫しなくてはなりません。
Visual C++を使えば共有変数を簡単に指定できるのですが、Delphiでは共有変数が利用できないため、メモリマップドファイルを使って変数の共有を行わなくてはなりません。Visual C++のコードの方が分かりやすいので、DelphiでDLLを作る前に、まずC言語のソースから見てみましょう。
#include <windows.h> /** 共有メモリに置く変数の指定 */ #pragma data_seg(".mhook_data") HHOOK g_hook = NULL; HWND g_parent = NULL; #pragma data_seg() #define WM_USER_MOUSE_ACTION (WM_USER + 0x1001) HINSTANCE hInst; /** フック関数 */ LRESULT CALLBACK HookProc(int code, WPARAM wp, LPARAM lp) { MOUSEHOOKSTRUCT* pms = (MOUSEHOOKSTRUCT*)lp; PostMessage(g_parent, WM_USER_MOUSE_ACTION, wp, pms->wHitTestCode); return CallNextHookEx(g_hook, code, wp, lp); } /** * フックのインストールを行う * @param parent フックしたメッセージを送る親ウィンドウのハンドル * @return 成功すれば TRUE を返す */ BOOL __stdcall HookInstall(HWND parent) { g_parent = parent; g_hook = SetWindowsHookEx(WH_MOUSE, HookProc, hInst, 0); return (g_hook > 0); } /** * フックのアンインストールを行う */ BOOL __stdcall HookUninstall(void) { if (g_hook) UnhookWindowsHookEx(g_hook); return TRUE; } int WINAPI DllMain(HINSTANCE hInstance, DWORD fdReason, PVOID pvReserved) { hInst = hInstance; return TRUE; }
そして、DLLから関数をエクスポートするためのモジュール定義ファイルを書いて完成です。
SECTIONS
.mhook_data READ WRITE SHARED
EXPORTS
HookInstall
HookUninstall
DLLの共有変数を指定するには、ソースファイルに「#pragma data_seg(".mhook_data")」を記述し、モジュール定義ファイルで「SECTIONS .mhook_data READ WRITE SHARED」と書いておきます。
