LoginSignup
1
1

More than 3 years have passed since last update.

ゼロから作るRPA その1の3 マウス動作記録編(記録用EXE)

Last updated at Posted at 2019-10-16

はじめに

その1の2では、マウスイベントをフックするDLLを作成しました。今回は、そのフックを使用してマウスイベントを記録するEXEを作成していきます。

環境

私の使用している環境を以下に挙げます。環境が異なる場合は適宜読み替えてください。

  • Windows 10 pro
  • Visual Studio 2019 Community Edition

今回やること

  1. Visual Studioで画面を作成する
  2. フックを開始する処理を呼び出す
  3. フックからのイベントを処理する
  4. マウスイベントをファイルに保存する

画面作成

マウスイベントの記録を開始するための画面を作成します。

Visual Studio 2019 でプロジェクトを作成

前回はDLL用のプロジェクトを作成しましたが、今回はEXE用のプロジェクトを作成します。
DLLの時に作成したソリューションのソリューションエクスプローラーで、DaphRPAソリューションを右クリックし、「追加」-「新しいプロジェクト」を選択します。
すると以下の画面が表示されるので、
image.png
Windowsデスクトップアプリケーションを選択し、「次へ」をクリックします。

プロジェクト名はいい感じの名前を付けてください。
自分はDLLのほうにDaphRPAという名前を付けてしまったので、DaphRPAppという名前にしました。
image.png

「作成」をクリックします。

image.png

デフォルトのプロジェクトが出来上がりました。

ダイアログ作成

リソースビューのDialogで右クリックして「Dialogを挿入」を選択すると、
image.png
ダイアログが作成されます。このダイアログをいい感じに編集して記録ボタンを追加していきます。
image.png
「OK」やら「キャンセル」やらは削除して、小さくして、記録ボタンを配置しましょう。
※記録再生の実験用画面なので、最低限のボタンだけの画面を作ってます。将来的には、RPAっぽい画面に変更していきますのでお楽しみに。

ちょちょいと編集して、以下のような画面にしてみました。
image.png

プロパティは以下のように変えています

対象 プロパティ
ダイアログ ID IDD_DAPH_DIALOG
ダイアログ Caption ゼロRPA
記録ボタン ID IDC_REC_BTN
記録ボタン Caption 記録

フック開始処理実装

DaphRPApp.cppにデフォルトで追加されたウィンドウは今回は使用しないので、いったん、wWinMainのコードを全部消してしまいます。

DaphRPApp.cpp
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マクロを使用します。

DialogBoxマクロ
void DialogBoxA(
   hInstance,
   lpTemplate,
   hWndParent,
   lpDialogFunc
);

wWinMainに渡されたインスタンスと、先ほど作成したダイアログリソース、親ウィンドウのハンドルとダイアログプロシージャを指定してダイアログを表示します。

コールバック関数を定義する

第4引数に指定するコールバック関数のダイアログプロシージャを定義します。

DaphRPApp.cpp
LRESULT CALLBACK DialogProc(HWND hDlgWnd, UINT msg, WPARAM wp, LPARAM lp);

下記が、最低限の処理を追加したコールバック関数を実装したソースファイルです。

DaphRPApp.cpp
#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;
}

起動するとダイアログが表示されます。
image.png

ボタンクリックの処理部分にブレークポイントを置いて「記録」ボタンをクリックするとそこで止まります。きちんとボタンが機能していることが確認できます。
image.png
ここに、記録開始の処理を追加します。
まずは、DLLの関数を使用できるよう、ヘッダファイルをインクルードします。
インクルードするために、プロパティにDLLのヘッダファイルの在りかを指定します。
image.png

ソースファイルにも、インクルード指定を追加します。

DaphRPApp.cpp
#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"の部分が追加した部分です。
インクルードを追加したら、「録画」ボタンの処理部分に、フックの開始コードを追加します。

DaphRPApp.cpp
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を実行し、「記録」ボタンを押してみましょう。
ブレークポイントを置いて、
image.png
EXEを起動して、「記録」ボタンをクリックします。
そして、マウスを少し動かすと・・・

image.png
マウスフックのコールバック関数処理が呼び出されるようになります。

マウスからのイベント処理

マウスフックのコールバック関数が呼び出されるようになると、マウスを動かすたびに、PostMessageによりDAPHRPA_WM_MOUSEHOOKイベントがダイアログに通知されるようになります。
その処理をダイアログのコールバック関数に記述して、マウスからのイベントを処理します。

ダイアログのコールバック関数に、以下のようにイベントの処理を追加します。
追加したのは、case DAPHRPA_WM_MOUSEHOOK:の部分です。

DaphRPApp.cpp
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;
}

ここにもブレークポイントを置いてみて、止まるかどうかを確認しましょう。

image.png

記録を開始してマウスを動かすと、まず、DpahRPA.dll内のマウスフック用コールバック関数でマウスのイベントメッセージがフックされ、その中のPostMessageでダイアログコールバック関数にメッセージがポストされ、最終的に上記ソースコードに到達します。

あとは、ここに、取得したメッセージを再生用のファイルに出力するだけです。

イベントの保存

イベントを保存します。ここでは一旦、固定ファイルrec.logに保存するコードで試してみます。本格作成の時にいろいろと修正します。

DaphRPApp.cpp
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形式のファイルが出力されます。

マウスフックのコールバック関数からのメッセージを処理する部分に、この保存処理を追加します。

DaphRPApp.cpp
    case DAPHRPA_WM_MOUSEHOOK:
        mousell = (LPMSLLHOOKSTRUCT)lp;
        SaveMouseEvent(wp, mousell);
        delete mousell;
        return FALSE;

受け取ったLPARAMをLPMSLLHOOKSTRUCTにキャストし、ファイル保存関数に渡します。

ファイルの出力結果は以下のようになることでしょう。

rc.log
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にバグがありました。以下の通り修正しました。

DaphRPAHook.cpp修正後
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

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1