2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ゼロから作るRPA その1の2 マウス動作記録編(フック用DLL)

Last updated at Posted at 2019-10-11

#はじめに
その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 を起動するとプロジェクト作成・選択用の画面が表示されます。
image.png
「新しいプロジェクトの作成」を選んで、新規にプロジェクトを作成します。

言語でC++を選んで、「ダイナミック リンク ライブラリ」を選択して、
image.png
「次へ」をクリックしてください。

image.png

プロジェクト名と場所とソリューション名を指定して「次へ」をクリックすると、
image.png
がんばれがんばれ!

image.png
ばばーん!っとプロジェクトが出来上がりました。

####インターフェイスを実装
今作成したDLLで以下のインターフェイスを公開します。

  • フックの開始
  • フックの終了

#####フックの開始
フックの開始は、記録ボタンがクリックされたときにマウスイベントのフックを呼び出すためのインターフェイスです。
以下がフックを開始するインターフェイスの定義です。

DaphRPAHook.h
#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で見てみるとわかりやすいです。

extern "C"ありの場合
image.png

extern "C"なしの場合
image.png

なしの場合は、?StartMouseHook@@YAXPAUHWND_@@@Zという名前で呼び出す必要があります。
※Dependency Walkerは、モジュールの依存関係等をGUIで確認できる便利ツールです。

以下がフック開始の実装です。コールバック関数の実装は後述します。

DaphRPAHook.cpp
#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関数を呼び出し、マウスイベントのフックを開始します。
戻り値のハンドルは、フック終了の際に必要なので、グローバル変数に保持しておきます。

SetWindowsHook
HHOOK SetWindowsHookEx(
  int       idHook,
  HOOKPROC  lpfn,
  HINSTANCE hmod,
  DWORD     dwThreadId
);

第1引数で、どのイベントをフックするかを指定します。ここでは、WH_MOUSE_LLを指定し、ローレベルマウスイベントをフックするように指定します。
第2引数には、コールバック関数を指定します。ここでは、MouseHookProcを指定しています。
第3引数にnullptr、第4引数に0を指定することにより、グローバルフックとして動作させます。

#####フックの終了
フックの終了では、停止ボタンが押された際にフックを終了するために呼び出されます。
ヘッダファイルにインターフェイスを追加します。

DaphRPAHook.h
#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();
}

フック終了関数を以下のように定義します。

DaphRPAHook.cpp
void __cdecl EndMouseHook()
{
	if (g_hhook)
		UnhookWindowsHookEx(g_hhook);
	g_hhook = nullptr;
	g_hparent = nullptr;
}

UnhookWindowsHookEx関数を使用し、フックを終了します。引数には、開始した時に保持したフックハンドルを指定します。

####コールバック関数の実装
コールバック関数では、マウスイベントが発生するたびにそのメッセージがフックされます。(ブレークポイントを置くとそのすごさが実感できると思います。)ここには、そのメッセージを親ウィンドウに横流しするための処理を記述します。
親ウィンドウには、PostMessage関数を使用し、メッセージを送信します。
まずは、送信するユーザ定義メッセージをヘッダファイルに定義し、それを使用します。

DaphRPAHook.h
#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までの数字が予約されているので、その範囲であればどのような数字でも大丈夫です。

コールバック関数を以下のように記述します。

DaphRPAHook.cpp
// マウスフックコールバックプロシージャ
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で親ウィンドウにパラメータを送信しています。

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が出来上がって実際に動かしたら、上記のコードに問題が見つかるかもしれません。その時は修正し、その旨を記述しますのでご了承ください。

2
3
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
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?