#はじめに
その1の1では、ウィンドウメッセージを横取りするグローバルフックについての概念的な説明を行いました。
ここでは実際にVisula Studio 2019 Community Editionを使ってマウス動作記録を実装していきたいと思います。
#環境
私の使用している環境を以下に挙げます。環境が異なる場合は適宜読み替えてください。
- Windows 10 pro
- Visual Studio 2019 Community Edition
#フック用DLLの作成
DLLを作成し、マウスメッセージをフックするコードを追加します。
その1の1でも書きましたが、グローバルフックは実行モジュールとは別のDLLとして作成する必要があるので、ここでは、DLL用のプロジェクトとして作成していきます。
####Visual Studio 2019 でプロジェクトを作成
Visual Studio を起動するとプロジェクト作成・選択用の画面が表示されます。
「新しいプロジェクトの作成」を選んで、新規にプロジェクトを作成します。
言語でC++を選んで、「ダイナミック リンク ライブラリ」を選択して、
「次へ」をクリックしてください。
プロジェクト名と場所とソリューション名を指定して「次へ」をクリックすると、
がんばれがんばれ!
####インターフェイスを実装
今作成したDLLで以下のインターフェイスを公開します。
- フックの開始
- フックの終了
#####フックの開始
フックの開始は、記録ボタンがクリックされたときにマウスイベントのフックを呼び出すためのインターフェイスです。
以下がフックを開始するインターフェイスの定義です。
#pragma once
#ifdef DAPHRPA_EXPORTS
#define DAPHRPAAPI __declspec(dllexport)
#else
#define DAPHRPAAPI __declspec(dllimport)
#endif
extern "C"
{
DAPHRPAAPI void __cdecl StartMouseHook(HWND parent);
}
第一引数は、呼び出しもとウィンドウのハンドルを指定します。
マウスイベントをフックした際に、呼び出しもとウィンドウにフックしたマウスイベントを通知するため、ハンドルを渡してもらいます。
DAPHRPAAPI
はインポートとエクスポートを切り替えるための定義です。プロジェクトのプロパティのプロセッサの定義にDAPHRPA_EXPORTS
の定義が追加されていない場合は追加する必要があります。
extern "C"
は、C++でDLLを公開する際に、命名規則をC言語風にするために記述が必要です。これがないと、変な名前(ちょっと語弊あり)になって、呼び出しもとから呼び出しにくくなるので、この定義が必要です。
ビルドしたDLLをDependency Walkerで見てみるとわかりやすいです。
なしの場合は、?StartMouseHook@@YAXPAUHWND_@@@Z
という名前で呼び出す必要があります。
※Dependency Walkerは、モジュールの依存関係等をGUIで確認できる便利ツールです。
以下がフック開始の実装です。コールバック関数の実装は後述します。
#include "pch.h"
#include "DaphRPAHook.h"
// マウスフックのコールバック関数
LRESULT CALLBACK MouseHookProc(int code, WPARAM wParam, LPARAM lParam);
// フックハンドルを保持する変数
HHOOK g_hhook = nullptr;
// 呼び出しもとウィンドウのハンドルを保持する変数
HWND g_hparent = nullptr;
/// マウスフックを開始する
void __cdecl StartMouseHook(HWND parent)
{
if (!g_hhook)
{
g_hparent = parent;
g_hhook = SetWindowsHookEx(WH_MOUSE_LL, MouseHookProc, nullptr, 0);
}
}
SetWindowsHookEx
関数を呼び出し、マウスイベントのフックを開始します。
戻り値のハンドルは、フック終了の際に必要なので、グローバル変数に保持しておきます。
HHOOK SetWindowsHookEx(
int idHook,
HOOKPROC lpfn,
HINSTANCE hmod,
DWORD dwThreadId
);
第1引数で、どのイベントをフックするかを指定します。ここでは、WH_MOUSE_LL
を指定し、ローレベルマウスイベントをフックするように指定します。
第2引数には、コールバック関数を指定します。ここでは、MouseHookProc
を指定しています。
第3引数にnullptr
、第4引数に0を指定することにより、グローバルフックとして動作させます。
#####フックの終了
フックの終了では、停止ボタンが押された際にフックを終了するために呼び出されます。
ヘッダファイルにインターフェイスを追加します。
#pragma once
#ifdef DAPHRPA_EXPORTS
#define DAPHRPAAPI __declspec(dllexport)
#else
#define DAPHRPAAPI __declspec(dllimport)
#endif
extern "C"
{
DAPHRPAAPI void __cdecl StartMouseHook(HWND parent);
DAPHRPAAPI void __cdecl EndMouseHook();
}
フック終了関数を以下のように定義します。
void __cdecl EndMouseHook()
{
if (g_hhook)
UnhookWindowsHookEx(g_hhook);
g_hhook = nullptr;
g_hparent = nullptr;
}
UnhookWindowsHookEx
関数を使用し、フックを終了します。引数には、開始した時に保持したフックハンドルを指定します。
####コールバック関数の実装
コールバック関数では、マウスイベントが発生するたびにそのメッセージがフックされます。(ブレークポイントを置くとそのすごさが実感できると思います。)ここには、そのメッセージを親ウィンドウに横流しするための処理を記述します。
親ウィンドウには、PostMessage
関数を使用し、メッセージを送信します。
まずは、送信するユーザ定義メッセージをヘッダファイルに定義し、それを使用します。
#pragma once
#ifdef DAPHRPA_EXPORTS
#define DAPHRPAAPI __declspec(dllexport)
#else
#define DAPHRPAAPI __declspec(dllimport)
#endif
#define DAPHRPA_WM_MOUSEHOOK WM_APP + 21
extern "C"
{
DAPHRPAAPI void __cdecl StartMouseHook(HWND parent);
DAPHRPAAPI void __cdecl EndMouseHook();
}
#define DAPHRPA_WM_MOUSEHOOK WM_USER + 21
を追加しました。
WM_APP
は、アプリケーションが定義可能なプライベートメッセージの先頭番号(0x8000)が定義されています。今回は、マウスのイベントがフックされたことを親ウィンドウに通知するために使用しています。
WM_APP + 21
の21に関しては特に深い意味はありません。WM_APPは、0x8000から0xBFFFまでの数字が予約されているので、その範囲であればどのような数字でも大丈夫です。
コールバック関数を以下のように記述します。
// マウスフックコールバックプロシージャ
LRESULT CALLBACK MouseHookProc(int code, WPARAM wParam, LPARAM lParam)
{
if (!g_hparent || code < 0)
return CallNextHookEx(g_hhook, code, wParam, lParam);
PostMessage(g_hparent, DAPHRPA_WM_MOUSEHOOK, wParam, lParam);
return CallNextHookEx(g_hhook, code, wParam, lParam);
}
PostMessage
で親ウィンドウにパラメータを送信しています。
BOOL PostMessage(
HWND hWnd,
UINT Msg,
WPARAM wParam,
LPARAM lParam
);
ポストメッセージは、hWnd
で指定されたウィンドウに、Msg
で指定されたメッセージを送信します。SendMessage
とは異なり、処理の終了を待ちません。投げっぱなしです。まるでよくいる上司みたいですね。でも、PostMessage
はそんな上司たちとは違い、かなり有用です。
ここでは、先ほどヘッダに定義したDAPHRPA_WM_MOUSEHOOK
を、親ウィンドウに対して送信しています。
#ビルド
最後に一旦ビルドして、コンパイルエラーが発生しないかを確認しましょう。
#つづく
次回は、EXEを作成します。
次回の「その1の3」は以下のリンクから参照できます。
https://qiita.com/yasunari_matsuo/items/14fe987a162936f7a1ae
この記事のプロジェクトは以下にアップしています。記事が進むごとに下記プロジェクトも成長します。
https://github.com/yasunarim/DaphRPA
その1の1は以下のリンクから参照できます。
https://qiita.com/yasunari_matsuo/items/b1e56ad06c6a7843dfae
EXEが出来上がって実際に動かしたら、上記のコードに問題が見つかるかもしれません。その時は修正し、その旨を記述しますのでご了承ください。