他のウィンドウのメッセージを取得する

最終更新 2004 02/25

サンプルのダウンロード → BCB_MessageHook.lzh(136k)

サンプルプログラムのスクリーンショット

【 プロジェクトの構築方法 】

BCB_MessageHook フォルダと BCB_MessageHookDLL フォルダを適当な場所に作り
そこにソースファイルをコピーします。

●BCB_MessageHook フォルダ
BCB メニューの「アプリケーションの新規作成(T)」を選んだ後 「すべて保存(V)」を選びます。 ファイルを保存する場所を BCB_MessageHook フォルダに変更してから ソースファイル名はそのまま保存して、プロジェクトファイルだけ BCB_MessageHook.bpr に名前を変更して保存します。 BCB のイベントはソースをコピーするだけでは認識してくれないので FormCreate イベントと FormCloseQuery イベントの部分は オブジェクトインスペクタのイベントから呼び出して、別途ソースを 貼り付けないといけません。 フォーム1にラベルを次のように配置します。 フォームの概観 フォーム1はオブジェクトインスペクタで次のように設定します。 BorderIcons - biMinimize = false BorderStyle = bsSingle Caption = フック実行ウィンドウ Height = 110 Width = 300 Position = poDefaultPosOnly
●BCB_MessageHookDLL フォルダ
BCB メニューの「新規作成(N)」を選んだ後、新規作成ダイアログの 「新規作成」タブから「DLL ウィザード」を選択します。 「新規作成(N)」の「DLL ウィザード」を選択 DLL ウィザードダイアログで「ソースの種類」を C++(+)、VCL を使う(V) に設定します。 C++(+)、VCL を使う(V) に設定 BCB メニューの「すべて保存(V)」を選びます。 ファイルを保存する場所を BCB_MessageHookDLL フォルダに変更してから ソースファイル名はそのまま保存して、プロジェクトファイルだけ BCB_MessageHookDLL.bpr に名前を変更して保存します。 そうしたら Unit1.cpp にサンプルソースを貼り付けてプロジェクトを メイクします。
【 サンプルの実行方法 】 ●BCB_MessageHook.exe ●BCB_MessageHookDLL.dll ●API_SetWindowsHookEx1.exe を全て同じフォルダにコピーします。 そうしたら、API_SetWindowsHookEx1.exe を起動した後、BCB_MessageHook.exe を実行します。
BCB_MessageHook の Unit1.h のソースコード


BCB_MessageHook の Unit1.cpp のソースコード


BCB_MessageHookDLL の Unit1.cpp のソースコード
【 VC++ との違い 】


BCB のメッセージフックは VC とはやり方が異なります。
グローバルフックを行うのにDLLを使う点は同じですが、共有メモリの扱い方が
異なります。

フックについての基本的なことは VC でのフック方法もご一読頂くとよいと思います。

BCB では #pragma data_seg が使えないのと、#pragma comment で共有メモリの
設定ができないので、共有メモリは別の方法で実装しなければいけません。

BCB では共有メモリを使うために、ファイルマッピングオブジェクトというものを
使います。
ファイルマッピングオブジェクトの実装には API 関数を使うので、VC でも
利用できます。

VC で紹介した方法では BCB との互換性はありませんが、こちらで紹介する方法は
少しソースをいじれば VC でも使用可能です。



【 ファイルマッピングオブジェクトの使い方 】


ファイルマッピングオブジェクトは次の流れで処理を行います。

1.CreateFileMapping() でファイルマッピングオブジェクトを作成
2.OpenFileMapping() でファイルマッピングオブジェクトを開く
3.MapViewOfFile() でファイルマッピングオブジェクトのポインタを取得
4.取得したポインタに値を設定したり取り出したりする
5.UnmapViewOfFile() でファイルマッピングオブジェクトのポインタを解放
6.CloseHandle() で OpenFileMapping() で取得したハンドルを閉じる
7.CloseHandle() で CreateFileMapping() で取得したハンドルを解放

ファイルマッピングオブジェクトに登録する値が複数ある場合は、適当な構造体を
作ってそこに値を入れてしまえば、その構造体ひとつだけで値をやり取りできます。

struct ShareDataStruct
{
    HWND      hWnd;       //フックしたメッセージを返すウィンドウのハンドル
    HHOOK     hHook;      //フックハンドル
    WORD      messageId;  //フックしたメッセージのID
    MSG       message;    //フックしたメッセージの値を保存
};

構造体を使わない場合は、ひとつの変数に対してファイルマッピングオブジェクトを
ひとつ作らないといけません。
それはさすがに面倒なので、やはり構造体で管理する方がよいです。

この構造体の値を CreateFileMapping() 関数を使ってファイルマッピング
オブジェクトに登録します。


HANDLE hFMObject = CreateFileMapping(
                       (HANDLE)0xFFFFFFFF,
                       NULL,
                       PAGE_READWRITE,
                       0,
                       sizeof(ShareDataStruct),
                       "ファイルマッピングオブジェクトの登録名"
                       );
これでファイルマッピングオブジェクトが作成できます。 CreateFileMapping() でファイルマッピングオブジェクトを作成したら 通常のファイルと同じように OpenFileMapping() でファイルを開きます。 ファイルマッピングオブジェクトの登録名 は OpenFileMapping() でも 使うので、ここに指定する値は #define するか、定数にしておくとよいです。

HANDLE hFmo = OpenFileMapping(
                  FILE_MAP_ALL_ACCESS,
                  false,
                  "ファイルマッピングオブジェクトの登録名"
                  );
CreateFileMapping() と OpenFileMapping() で取得できるハンドルは 別物なので、それぞれ別の変数に保存しておきます。 ファイルマッピングオブジェクトを開くと、値を取り出すことができるように なります。 ファイルマッピングオブジェクトに登録した値を取り出すには MapViewOfFile() を使います。 MapViewOfFile() に指定するのは OpenFileMapping() で取得したハンドルです。

ShareDataStruct* p = (ShareDataStruct*)::MapViewOfFile(
                         hFmo,
                         FILE_MAP_ALL_ACCESS,
                         0,
                         0,
                         0
                         );
値はポインタで返ってくるので、このポインタに値を指定すればファイルマッピング オブジェクトに登録されている値も変わります。

MSG msg;
memset( &msg, 0, sizeof(msg) );

p->hWnd      = hWnd;
p->hHook     = SetWindowsHookEx(...);
p->messageId = 0;
p->message   = msg;
値の設定や取得が終わったら UnmapViewOfFile() でポインタを解放します。 その後、CloseHandle( hFmo ) とやって OpenFileMapping() で取得した ハンドルを閉じて、CloseHandle( hFMObject ) とやって、登録したファイル マッピングオブジェクトを削除します。 【 フック用メッセージを登録 】 VC++ のサンプルでは、フックしたメッセージをそのまま呼び出し元のウィンドウに 送信していましたが、それではさすがに呼び出し元ウィンドウのメッセージなのか フックしたウィンドウのメッセージなのか区別できないので少々問題があります。 そこでメッセージをフックした場合はこちら側で定義したメッセージを送信することで 呼び出し元で自分のウィンドウのメッセージなのか、フックしたメッセージなのかを 区別できるようにします。 一番簡単なのは WM_USER + 100 などのような値を使うのですが、DLLの場合は いろいろと問題があります。 簡単に説明すると、WM_USER + ? の値を使うと、メッセージIDの衝突が起きます。 そこで RegisterWindowMessage( "登録名" ) を使って取得したメッセージIDを 使います。 「DLL側」 メッセージをフック ↓ フックしたメッセージの情報をファイルマッピングオブジェクトに保存 ↓ RegisterWindowMessage() で取得したメッセージIDを呼び出し元に送信 ↓ 「呼び出し元」 受信したメッセージが RegisterWindowMessage() で取得したメッセージIDと 一致するか調べる ( DLL の GetHookMessageId() で取得 ) ↓ 一致したらファイルマッピングオブジェクトに保存したメッセージ情報を取得 ( DLL の GetHookMessage() で取得 ) ↓ そのメッセージごとに処理を行う 前回と違うのは、DLLから送信されるメッセージはフックしたメッセージ そのものでなくて、「メッセージをフックしましたよ」という目印だけです。 ですから、どんなメッセージをフックしたのかは分かりません。 どんなメッセージをフックしたのかは、DLLの GetHookMessage() を使って 調べます。 【 DLL のソースを VC++ でも使うには 】 今回のDLLのソースは #include<vcl.h>、#pragma hdrstop、#pragma argsused の行と 各関数の __stdcall を削除 int WINAPI DllEntryPoint を BOOL WINAPI DllMain に変更 以上のように変更すれば、VC++ でも使えます。 一応、呼び出し元のソースもDLLに合わせて変更する必要があるので VC 用に変更した呼び出し元とDLLのソースをのせておきます。 呼び出し元のソース( プロジェクト名:MessageHook ) DLL のソース( プロジェクト名:MessageHookDLL ) 【 TFormで受信したメッセージを処理するには 】 WndProc() メソッドをオーバーライドします。 BCB のフォーム(TForm)でメッセージを受信すると、最初にこの WndProc() メソッドが呼び出されます。 この関数は virtual 宣言されているので、オーバーライドして内容を 変更することができます。 このメソッドの中で、DLLから「メッセージをフックしたよ」という 意味のメッセージを受信したときの処理を書きます。 サンプルを見れば実装方法はお分かりいただけると思います。 注意する点は、一番最初に親クラスの WndProc() を実行するという点です。 Form1 の FormCreate イベントなんかの処理は、親クラスの WndProc() で 処理されるので、最初に TForm::WndProc( Message ); を呼び出して 親クラスの処理を実行しておかないと、Form1 で設定したイベントが全く 実行されなくなってしまいます。 Form1 は TForm を継承しているので、TForm に定義されているイベントは Form1 の WndProc() ではなく、TForm の WndProc() で処理されます。

| home |