はじめに
その1の2では、マウスイベントをフックするDLLを作成しました。今回は、そのフックを使用してマウスイベントを記録するEXEを作成していきます。
環境
私の使用している環境を以下に挙げます。環境が異なる場合は適宜読み替えてください。
- Windows 10 pro
- Visual Studio 2019 Community Edition
今回やること
- Visual Studioで画面を作成する
- フックを開始する処理を呼び出す
- フックからのイベントを処理する
- マウスイベントをファイルに保存する
画面作成
マウスイベントの記録を開始するための画面を作成します。
Visual Studio 2019 でプロジェクトを作成
前回はDLL用のプロジェクトを作成しましたが、今回はEXE用のプロジェクトを作成します。
DLLの時に作成したソリューションのソリューションエクスプローラーで、DaphRPAソリューションを右クリックし、「追加」-「新しいプロジェクト」を選択します。
すると以下の画面が表示されるので、
Windowsデスクトップアプリケーションを選択し、「次へ」をクリックします。
プロジェクト名はいい感じの名前を付けてください。
自分はDLLのほうにDaphRPAという名前を付けてしまったので、DaphRPAppという名前にしました。
「作成」をクリックします。
デフォルトのプロジェクトが出来上がりました。
ダイアログ作成
リソースビューのDialogで右クリックして「Dialogを挿入」を選択すると、
ダイアログが作成されます。このダイアログをいい感じに編集して記録ボタンを追加していきます。
「OK」やら「キャンセル」やらは削除して、小さくして、記録ボタンを配置しましょう。
※記録再生の実験用画面なので、最低限のボタンだけの画面を作ってます。将来的には、RPAっぽい画面に変更していきますのでお楽しみに。
プロパティは以下のように変えています
対象 | プロパティ | 値 |
---|---|---|
ダイアログ | ID | IDD_DAPH_DIALOG |
ダイアログ | Caption | ゼロRPA |
記録ボタン | ID | IDC_REC_BTN |
記録ボタン | Caption | 記録 |
フック開始処理実装
DaphRPApp.cppにデフォルトで追加されたウィンドウは今回は使用しないので、いったん、wWinMain
のコードを全部消してしまいます。
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
return (int) 1;
}
ほかの不要ファイルも適当に消してください。
先ほど作ったダイアログを表示するには、DialogBox
マクロを使用します。
void DialogBoxA(
hInstance,
lpTemplate,
hWndParent,
lpDialogFunc
);
wWinMainに渡されたインスタンスと、先ほど作成したダイアログリソース、親ウィンドウのハンドルとダイアログプロシージャを指定してダイアログを表示します。
コールバック関数を定義する
第4引数に指定するコールバック関数のダイアログプロシージャを定義します。
LRESULT CALLBACK DialogProc(HWND hDlgWnd, UINT msg, WPARAM wp, LPARAM lp);
下記が、最低限の処理を追加したコールバック関数を実装したソースファイルです。
# include "framework.h"
# include "DaphRPApp.h"
# include "resource.h"
LRESULT CALLBACK DialogProc(HWND hDlgWnd, UINT msg, WPARAM wp, LPARAM lp);
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
DialogBox(hInstance, MAKEINTRESOURCE(IDD_DAPH_DIALOG), NULL, (DLGPROC)DialogProc);
return (int) 1;
}
LRESULT CALLBACK DialogProc(HWND hDlgWnd, UINT msg, WPARAM wp, LPARAM lp)
{
switch (msg) {
case WM_INITDIALOG:
return FALSE;
case WM_COMMAND:
switch (LOWORD(wp)) {
case IDC_REC_BTN:
return FALSE;
default:
return FALSE;
}
default:
return FALSE;
}
return TRUE;
}
ボタンクリックの処理部分にブレークポイントを置いて「記録」ボタンをクリックするとそこで止まります。きちんとボタンが機能していることが確認できます。
ここに、記録開始の処理を追加します。
まずは、DLLの関数を使用できるよう、ヘッダファイルをインクルードします。
インクルードするために、プロパティにDLLのヘッダファイルの在りかを指定します。
ソースファイルにも、インクルード指定を追加します。
# include "framework.h"
# include "DaphRPApp.h"
# include "resource.h"
# include "DaphRPAHook.h"
LRESULT CALLBACK DialogProc(HWND hDlgWnd, UINT msg, WPARAM wp, LPARAM lp);
#include "resource.h"
の部分が追加した部分です。
インクルードを追加したら、「録画」ボタンの処理部分に、フックの開始コードを追加します。
LRESULT CALLBACK DialogProc(HWND hDlgWnd, UINT msg, WPARAM wp, LPARAM lp)
{
switch (msg) {
case WM_INITDIALOG:
return FALSE;
case WM_COMMAND:
switch (LOWORD(wp)) {
case IDC_REC_BTN:
StartMouseHook(hDlgWnd);
return FALSE;
default:
return FALSE;
}
default:
return FALSE;
}
return TRUE;
}
これで、ボタンクリックするとフックが開始されます。ちょっと実験してみましょう。
DLLのコールバック関数にブレークポイントを置いてEXEを実行し、「記録」ボタンを押してみましょう。
ブレークポイントを置いて、
EXEを起動して、「記録」ボタンをクリックします。
そして、マウスを少し動かすと・・・
マウスフックのコールバック関数処理が呼び出されるようになります。
マウスからのイベント処理
マウスフックのコールバック関数が呼び出されるようになると、マウスを動かすたびに、PostMessageによりDAPHRPA_WM_MOUSEHOOK
イベントがダイアログに通知されるようになります。
その処理をダイアログのコールバック関数に記述して、マウスからのイベントを処理します。
ダイアログのコールバック関数に、以下のようにイベントの処理を追加します。
追加したのは、case DAPHRPA_WM_MOUSEHOOK:
の部分です。
LRESULT CALLBACK DialogProc(HWND hDlgWnd, UINT msg, WPARAM wp, LPARAM lp)
{
switch (msg) {
case WM_INITDIALOG:
return FALSE;
case WM_COMMAND:
switch (LOWORD(wp)) {
case IDC_REC_BTN:
StartMouseHook(hDlgWnd);
return FALSE;
default:
return FALSE;
}
case DAPHRPA_WM_MOUSEHOOK:
return FALSE;
default:
return FALSE;
}
return TRUE;
}
ここにもブレークポイントを置いてみて、止まるかどうかを確認しましょう。
記録を開始してマウスを動かすと、まず、DpahRPA.dll内のマウスフック用コールバック関数でマウスのイベントメッセージがフックされ、その中のPostMessageでダイアログコールバック関数にメッセージがポストされ、最終的に上記ソースコードに到達します。
あとは、ここに、取得したメッセージを再生用のファイルに出力するだけです。
イベントの保存
イベントを保存します。ここでは一旦、固定ファイルrec.log
に保存するコードで試してみます。本格作成の時にいろいろと修正します。
void SaveMouseEvent(DWORD message, LPMSLLHOOKSTRUCT mousell)
{
std::ofstream fout;
fout.open(L"rec.log", std::ios::app);
fout << message << "," << mousell->pt.x << "," << mousell->pt.y << "," << mousell->time << std::endl;
fout.close();
}
第1引数にマウスメッセージ、第2引数にマウスのローレベルフックで得られた構造体を受け取ります。
std::ofstream fout
でSTLの書き込み用ファイルストリームを定義し、fout.open
でファイルを追加モードで開きます。
ファイルにマウスイベントの情報を書き込んだのち、ファイルをクローズします。
※この処理では、毎回ファイルのオープンクローズが実行されるので遅いです。停止ボタンを追加した際に、この関数は修正します。
これで、CSV形式のファイルが出力されます。
マウスフックのコールバック関数からのメッセージを処理する部分に、この保存処理を追加します。
case DAPHRPA_WM_MOUSEHOOK:
mousell = (LPMSLLHOOKSTRUCT)lp;
SaveMouseEvent(wp, mousell);
delete mousell;
return FALSE;
受け取ったLPARAMをLPMSLLHOOKSTRUCTにキャストし、ファイル保存関数に渡します。
ファイルの出力結果は以下のようになることでしょう。
512,46,74,93257000
512,46,74,93257000
512,46,76,93257015
512,46,77,93257015
512,48,81,93257031
512,53,86,93257046
512,68,101,93257046
512,75,107,93257062
512,85,118,93257078
512,105,141,93257093
512,148,193,93257109
512,157,204,93257125
512,188,238,93257125
512,200,249,93257140
512,219,265,93257156
512,231,276,93257171
512,239,280,93257187
512,242,283,93257203
512,248,287,93257218
512,253,289,93257250
512,256,291,93257265
512,263,294,93257296
一番左がマウスメッセージ、次の二つがマウスの座標、最後がタイムスタンプです。
記録の停止は、後ほど停止ボタンを追加しますが、とりあえずはデバッガの停止により行ってください。
DLLを少し修正
その1の2で作成したDLLにバグがありました。以下の通り修正しました。
LRESULT CALLBACK MouseHookProc(int code, WPARAM wParam, LPARAM lParam)
{
if (!g_hparent || code < 0)
return CallNextHookEx(g_hhook, code, wParam, lParam);
// MSLLHOOKSTRUCTをそのままPostMessageすると受取先でメモリがクリアされた
// 状態になるので、別のメモリにコピーして渡す。そして、呼び出し先で使用後解放する
LPMSLLHOOKSTRUCT lpmshook = new MSLLHOOKSTRUCT;
memcpy(lpmshook, (LPMSLLHOOKSTRUCT)lParam, sizeof(MSLLHOOKSTRUCT));
PostMessage(g_hparent, DAPHRPA_WM_MOUSEHOOK, wParam, (LPARAM)lpmshook);
return CallNextHookEx(g_hhook, code, wParam, lParam);
}
MouseHookProc
のlParamで受け取ったMSLLHOOKSTRUCT
のポインタをそのままPostMessage
の引数としていましたが、スコープを抜けると構造体のメモリが解放されてしまうため、呼び出し先で不正なメモリとなってしまっていました。
構造体のメモリを別途確保し、そこにコピーしたうえで渡すようにしました。
つづく
今回で、マウスイベントの記録ができました。次回は、記録したマウスイベントを再生します。
続きはこちらです。
その1の4
https://qiita.com/yasunari_matsuo/items/8a528066cda871c345d4
この記事のプロジェクトは以下にアップしています。記事が進むごとに下記プロジェクトも成長します。
https://github.com/yasunarim/DaphRPA
その1の1は以下のリンクから参照できます。
https://qiita.com/yasunari_matsuo/items/b1e56ad06c6a7843dfae
その1の2は以下のリンクから参照できます。
https://qiita.com/yasunari_matsuo/items/a1d294f09ac21e9508b6