SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

特集記事

デスクトップ上のウィンドウをグリッドに沿って合わせるユーティリティ

デスクトップユーティリティ作成のヒント


フックの簡単なサンプル

 では、フックでどんなイベントを取得できるのか、簡単なサンプルを作って確かめてみましょう。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言語のソースから見てみましょう。

すべてのウィンドウのメッセージをフックする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から関数をエクスポートするためのモジュール定義ファイルを書いて完成です。

DLLのモジュール定義ファイル
SECTIONS
    .mhook_data READ WRITE SHARED
EXPORTS
    HookInstall
    HookUninstall

 DLLの共有変数を指定するには、ソースファイルに「#pragma data_seg(".mhook_data")」を記述し、モジュール定義ファイルで「SECTIONS .mhook_data READ WRITE SHARED」と書いておきます。

次のページ
Delphiで共有変数ユニットの作成

この記事は参考になりましたか?

特集記事連載記事一覧

もっと読む

この記事の著者

クジラ飛行机(クジラヒコウヅクエ)

ソフト企画「くじらはんど」にて、多数のフリーソフトを公開しています。日本語プログラミング言語「なでしこ」、テキスト音楽「サクラ」、日本語Wiki記法が特徴の「KonaWiki」などを公開しています。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/369 2007/12/10 13:50

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー