・実例 〜 グローバルフック - VC++ 2005
最後にグローバルフックの例として、マウスフックの簡単な例を掲載します。 マウスを右クリックしたら、そのウィンドウが最大化されていたら元のサイズに、 そうでなければ最大化するようにします。グローバルフックなのでデスクトップ上のすべての ウィンドウで右クリックによる振る舞いが変更されますのでその効果を確認してください。
<DLLプロジェクト作成>
前に解説したようにグローバル
フックではフックプロシージャはDLLに書かないといけません。一方、このフックプロシージャを
利用するため(フックプロシージャをインストールする)にはSetWindowsHookExを
コールしなければなりませんが、これはDLLでなくてもよいです。
しかし、フックプロシージャがあるDLL以外にSetWindowHookExを記述しようとすると、その前に
DLLをロードしてフックプロシージャへのポインタを取得する必要があります。
(DLLのリンク参照)
ということで、SetWindowsHookExのコールを含めてフック関連のコードをDLL内の
クラスにまとめてみます。
まず、以下のようにDLLプロジェクトを作成してください。
ツールーバーのファイル(F)→新規作成(N)→プロジェクト(P)の「新しいプロジェクト」
ダイアログボックスで以下のように入力してください。
プロジェクトの種類(P) : Win32 テンプレート(T) : Win32プロジェクト プロジェクト名 : GHook(任意) 後はデフォルトで→OK
続いて起動されるアプリケーションウィザードで以下のように入力してください。
(アプリケーションウィザードへようこそ)
→次へ
(アプリケーションの設定)
アプリケーションの種類 : DLL
追加のオプション : シンボルのエクスポートにチェック
→完了
これでGHook.hとGHook.cppにスケルトンが書かれています。ヘッダファイルには DLLのエクスポートスイッチが書き込まれ、 実装ファイルにはDLLのエントリポイントDllMainが定義されています。まず、この2つのファイルを 修正します。
// ------------- GHook.h --------------- #ifdef GHOOK_EXPORTS #define GHOOK_API __declspec(dllexport) #else #define GHOOK_API __declspec(dllimport) #endif |
ヘッダファイルは上記の エクスポートスイッチ以外はコメントアウト または削除してください。declspec(dllexport)が有効になっているので、GHOOK_EXPORTSが 定義されているはずですがコード上では見つけられません。実は、cl.exeのオプションで マクロ指定しているのですが以下で確認してください。
ソリューションエクスプローラでGHookプロジェクトを選択して右クリックで プロパティ(R)を選択してGHookプロジェクトのプロパティページを表示させてください。
構成(C) : 構成プロパティ - C/C++ - プリプロセッサ
ここのプリプロセッサの定義にGHOOK_EXPORTSを含めてマクロ定義があるので
確認してください。
次にGHook.cppです。
// ------------- GHook.cpp --------------- // GHook.cpp : DLL アプリケーションのエントリ ポイントを定義します。 // #include "stdafx.h" #include "GHook.h" #ifdef _MANAGED #pragma managed(push, off) #endif //DLLインスタンスハンドル---------- HINSTANCE g_hDllInst = NULL; //--------------------------------- //DLLエントリポイント BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: // DLLインスタンスハンドルをg_hDllInstに代入 g_hDllInst = (HINSTANCE)hModule; break; case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; } #ifdef _MANAGED #pragma managed(pop) #endif |
フックをセットするときにコールするSetWindowsHookExの第三引数には フックプロシージャがあるDLLのインスタンスハンドルを指定します。この値を保持するために、 DllMainで渡されてくるhModuleをグローバル変数に代入しています。
次に本題のフックを管理するクラスを作成します。といっても外部から このクラスのメンバにアクセスすることになり、 エクスポートしなければならないので 注意点で書いたように インターフェイスクラスと実装クラスに分離します。まず、インターフェイスクラスを 以下のように作成してください。
ツールバーのプロジェクト(P)→クラスの作成(C)で「クラスの追加」ダイアログを 表示して次のようにクラスを作成してください。
カテゴリ : C++ テンプレート : C++クラス →追加 クラス名 : CMouseHook →完了
CMouseHookクラス(インターフェイス)のスケルトンが出来ているので 以下のように修正・加筆してください。
// ---------------- MouseHook.h - ヘッダファイル------------- #pragma once #include "GHook.h" // GHOOK_APIエクスポートスイッチ //マウスフック用<インターフェイス>クラス class /*GHOOK_API*/ CMouseHook { public: virtual BOOL Start() = 0; //フックスタート virtual BOOL End() = 0; //フック終了 virtual void Destroy() = 0; //インスタンス破壊 static GHOOK_API CMouseHook * Create(); //CMouseHookインスタンス作成 }; |
// --------------- MouseHook.cpp - 実装ファイル-------------- #include "StdAfx.h" #include "MouseHookImpl.h" //MouseHook.hはインクルードされている //CMouseHookインスタンス作成 /*static*/ CMouseHook *CMouseHook::Create() { return new CMouseHookImpl; } |
ヘッダファイルはフック管理クラス(CMouseHook)のフック開始、フック終了、 インスタンス破棄の各関数を純粋仮想関数で宣言しています。これらがインターフェイスを構成する 関数ですが、CMouseHook::Createはstaticで宣言しているので実際はCMouseHookのメンバ関数では ありません。グローバル関数で宣言・定義してもいいですが、CMouseHookのインスタンスを作成する ための関数なのでstaticで宣言しています。なお、このクラスはデータメンバも持っていなくて、 さらに純粋仮想関数があり抽象クラスなのでインスタンスは作成できません。したがって、 コンストラクタもデストラクタも定義する必要はないでしょう。
また、エクスポートについてですが、このクラスをエクスポートしなければ なりませんが、CMouseHook::Create以外は仮想関数でdeclspec(export)で修飾しなくても エクスポートできてしまうので (仮想関数のエクスポート参照) クラスのエクスポートスイッチ(GHOOK_API)をコメントアウトして、CMouseHook::Createのみに エクスポートスイッチを付けています。
実装ファイルはCMouseHook::Createの実装を行っています。CMouseHookの 実装クラスであり、派生クラスであるCMouseHookImpl(後でコーディングします)のインスタンスを 作成して、CMouseHookへのポインタにアップキャストして返却しているだけです。通常、 インターフェイスクラスには実装はないですが、この関数はstatic関数で、CMouseHookのメンバでは ないのでここで実装しています。
それではCMouseHookを派生させて実装クラスであるCMouseHookImplを作成します。 C++クラスウィザードを起動して、以下のように C++クラスを作成してください。
クラス名(L) : CMouseHookImpl 基本クラス(B) : CMouseHook 仮想デストラクタ(V) : チェックする →完了
スケルトンができたら以下のように修正・加筆してください。
// -------------- MouseHookImpl.h - ヘッダファイル ------------------- #pragma once #include "MouseHook.h" //マウスフック用<実装>クラス class CMouseHookImpl : public CMouseHook { public: CMouseHookImpl(); //コンストラクタ virtual ~CMouseHookImpl(); //デストラクタ //メンバ関数///////////////////////////////////////////// /*virtual*/ BOOL Start(); //フックスタート /*virtual*/ BOOL End(); //フック終了 /*virtual*/ void Destroy(); //インスタンス破壊 private: static LRESULT CALLBACK MouseHookProc(int nCode, //マウスフックプロシージャ WPARAM wParam, LPARAM lParam); //メンバ変数///////////////////////////////////////////// private: static HHOOK m_hHook; //フックハンドル }; |
// ------------------- MouseHookImpl.cpp - 実装ファイル ------------- #include "StdAfx.h" #include "MouseHookImpl.h" //DLLインスタンスハンドルextern宣言 extern HINSTANCE g_hDllInst; //------------------------------------------------------------------------ //名前付データセグメント宣言 #pragma data_seg(".MHShare") HHOOK CMouseHookImpl::m_hHook = NULL; //フックハンドル初期化 #pragma data_seg() //.MHShareセクションを読み・書き・共有に属性を変更(システムグローバル) #pragma comment(linker, "/SECTION:.MHShare,RWS") //defファイルでも指定可 //------------------------------------------------------------------------ //コンストラクタ CMouseHookImpl::CMouseHookImpl() { } //デストラクタ CMouseHookImpl::~CMouseHookImpl() { End(); } //Mouseフックスタート //戻値 フック成功時TRUE BOOL CMouseHookImpl::Start() { if (m_hHook) ::UnhookWindowsHookEx(m_hHook); m_hHook = ::SetWindowsHookEx(WH_MOUSE, CMouseHookImpl::MouseHookProc, g_hDllInst, 0); return m_hHook ? TRUE : FALSE; } //フック終了 BOOL CMouseHookImpl::End() { BOOL bRet = TRUE; if (m_hHook) { bRet = ::UnhookWindowsHookEx(m_hHook); m_hHook = NULL; } return bRet; } //CMouseHookImplインスタンス破壊 void CMouseHookImpl::Destroy() { delete this; //デストラクタでCMouseHookImpl::Endが呼ばれる } //マウスフックプロシージャ //引数 nCode フックコード(HC_ACTION or HC_NOREMOVE) // wParam マウスメッセージ // lParam MOUSEHOOKSTRUCT構造体へのポインタ /*static*/ LRESULT CALLBACK CMouseHookImpl::MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam) { // nCodeが負 or メッセージキューからメッセージが削除されていない if (nCode < 0 || nCode == HC_NOREMOVE) return ::CallNextHookEx(m_hHook, nCode, wParam, lParam); //右ボタンの場合 if (wParam == WM_RBUTTONUP) { //マウスデータ LPMOUSEHOOKSTRUCT pMhs = reinterpret_cast<LPMOUSEHOOKSTRUCT>(lParam); //右クリックした時のウィンドウのルートウィンドウ HWND hWnd = ::GetAncestor(pMhs->hwnd, GA_ROOT); if (hWnd) ::ShowWindow(hWnd, ::IsZoomed(hWnd) ? SW_RESTORE : SW_MAXIMIZE); } return ::CallNextHookEx(m_hHook, nCode, wParam, lParam); } |
インターフェイスクラスCMouseHookを継承したCMouseHookImplでフックの
セット(Start)、フックの解除(End)、フックプロシージャ(MouseHookProc)、つまりフック関連の
すべての実装をしています。特にコメントしたい点は、
変数の共有化の部分です。ここでは
フックプロシージャに注目してください。グローバルフックなのでデスクトップ上の
すべてのスレッドでフックプロシージャ(MouseHookProc)がシステムによってコールされます。
ですから、ここで使用される変数に関しては注意してください。各スレッドで独立している場合は
いいですが、この例のように少なくともフックハンドル(m_hHook)は共有化しないといけません。
ですから、.MHShareセクションを作成してそこにm_hHookの値を格納し、セクションの属性を
共有化しています(#pragma data_seg)。
また、フックプロシージャはコールバック関数なのでグローバル関数、またはクラスのstatic関数に
する必要があるので注意してください。
フック処理をインターフェイスクラスと実装クラスに分離したので、もし、変更が あった場合でも、CMouseHookImplを変更している分には、利用するexe側はリビルドする必要は なく、単にdllをスワップすれば問題ないです。逆に言うと、CMouseHookクラスを変更したら、 dllを利用しているexeはリビルドしなければなりません。
フック関連をインターフェイスクラスと実装クラスに分離することによって カプセル化が強固になります。このdllを使うexeにはdll本体、CMouseHook.h、GHook.libの 3つファイルを配布すればよく、実装関連は一切外部に提示する必要はありません。
<dllを利用するexeプロジェクト作成>
以上でDLLの記述は済みました。あとはこれを使えばいいだけですが、DLLは単独で
実行できないので、これをロードして利用するEXEプロジェクトを作成します。DLLをリンクして
CMouseHookクラスを使えばいいのでどんなEXEでもいいのですが、できるだけ簡単に作成するために
MFCのダイアログベースのプロジェクトを作成します。
ツールバーのファイル(F)→新規作成(N)→プロジェクト(P)で「新しいプロジェクト」ダイアログを
表示して以下のように入力してください。
プロジェクトの種類 : Visual C++
- MFC
テンプレート : MFCアプリケーション
プロジェクト名 : testHook(任意)
ソリューション : ソリューションに追加
→OK
MFCアプリケーションウィザードが起動しますので次のように入力してください。
(MFCアプリケーションウィザードへようこそ) →次へ (アプリケーションの種類) アプリケーションの種類 : ダイアログベース →次へ (ユーザーインターフェイス機能) メインフレームのスタイル : システムメニューのみにチェック →次へ (高度な機能) →次へ (生成されたクラス) →完了
次にリソースビューからダイアログエディタをを開いて以下のように ダイアログを作成してください。
番号 | コントロールタイプ | ID |
[1] | ボタンコントロール | IDC_BTN_START |
[2] | ボタンコントロール | IDC_BTN_END |
[3] | スタティックコントロール | IDC_STC_STATUS |
[4] | ボタンコントロール | IDOK |
ダイアログリソースができたら、アプリケーションウィザードで作成された スケルトンの内、ダイアログのヘッダ・実装ファイルを以下のように加筆・修正してください。
// testHookDlg.h : ヘッダー ファイル // #pragma once //------------------------------------ class CMouseHook; //前方宣言 //------------------------------------ // CtestHookDlg ダイアログ class CtestHookDlg : public CDialog { // ・ // ・ // ----------------- メンバ変数 ---------------------------- protected: CMouseHook *m_pMouseHook; //マウスフック管理クラス // ----------------- メンバ関数 ---------------------------- protected: virtual void OnOK(); //OKボタン virtual void OnCancel(); //Cancelボタン public: afx_msg void OnBnClickedBtnStart(); //フック開始ボタン afx_msg void OnBnClickedBtnEnd(); //フック終了ボタン }; |
// testHookDlg.cpp : 実装ファイル // #include "stdafx.h" #include "testHook.h" #include "testHookDlg.h" #include "../GHook/MouseHook.h" #ifdef _DEBUG #pragma comment(lib, "../debug/GHook.lib") #else #pragma comment(lib, "../release/GHook.lib") #endif #ifdef _DEBUG #define new DEBUG_NEW #endif // CtestHookDlg ダイアログ CtestHookDlg::CtestHookDlg(CWnd* pParent /*=NULL*/) : CDialog(CtestHookDlg::IDD, pParent) , m_pMouseHook(NULL) { m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); } // ・ // ・ BEGIN_MESSAGE_MAP(CtestHookDlg, CDialog) // ・ // ・ ON_BN_CLICKED(IDC_BTN_START, &CtestHookDlg::OnBnClickedBtnStart) ON_BN_CLICKED(IDC_BTN_END, &CtestHookDlg::OnBnClickedBtnEnd) END_MESSAGE_MAP() // CtestHookDlg メッセージ ハンドラ BOOL CtestHookDlg::OnInitDialog() { CDialog::OnInitDialog(); // ・ // ・ m_pMouseHook = CMouseHook::Create(); return TRUE; // フォーカスをコントロールに設定した場合を除き、TRUE を返します。 } // CDialog::OnOKオーバーライド void CtestHookDlg::OnOK() { if (m_pMouseHook) { m_pMouseHook->Destroy(); m_pMouseHook = NULL; } CDialog::OnOK(); } // CDialog::OnCancelオーバーライド void CtestHookDlg::OnCancel() { // CDialog::OnCancel(); } //フック開始 void CtestHookDlg::OnBnClickedBtnStart() { SetWindowPos(&wndTopMost, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); if (m_pMouseHook) { if ( m_pMouseHook->Start() ) GetDlgItem(IDC_STC_STATUS)->SetWindowText(_T("フック実行中・・・")); else GetDlgItem(IDC_STC_STATUS)->SetWindowText(_T("")); } } //フック終了 void CtestHookDlg::OnBnClickedBtnEnd() { SetWindowPos(&wndNoTopMost, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); if (m_pMouseHook) m_pMouseHook->End(); GetDlgItem(IDC_STC_STATUS)->SetWindowText(_T("")); } |
以上ですべてできたので、ツールバーのビルド(B)→ソリューションのビルド(B)で 2つのプロジェクトをビルドしたのち、testHook.exeを実行して「フック開始ボタン」を 押下してフックを起動してください。デスクトップ上のどのウィンドウを右クリックしても ウィンドウの最大化⇔元に戻すがされるはずです。
参考文献: Hooks フックの仕組み DLL の中のC++クラスの安全なエクスポート