C/C++


グローバルフックの例

今回はグローバルフックの例を示します。
グローバルフックということで、あるウィンドウに送られるメッセージを真っ先に横取りします。
そのため、プログラムでミスすると、PCの電源を手動で切らなくてはならない事態になります。
今回はかなり扱いに注意が必要なので、もう一度いっておきます。
自己責任でやれ!!責任は取らないぞ!!!

承諾いただけましたら解説を始めましょう。
今回作るものは、今までアクティブ状態になってきたウィンドウの名前(履歴)を表示する機能と、 終了する際にメッセージボックスを表示する機能を持ちます。
これを実現するために、グローバルフックを用います。
なお、フックの開始、終了、コールバック関数はDllに実装します。

では、実際のコードを示しましょう。

DLLの作成

まずは、dllの作成を行ないます
win32プロジェクトを作り、アプリケーションの設定でウィンドウアプリケーションではなく、
dllに変更してください。
作成するのは次のhook_msg_dll.hとhook_msg_dll.cppです。
ビルドして〜.dll, 〜.libを作ってください。

hook_msg_dll.h

001 002 003 004 005 006 007 008 009 010 011 012 013

#ifdef HOOK_EXPORTS #define EXPORT __declspec(dllexport) #else #define EXPORT __declspec(dllimport) #endif //プライベートウィンドウクラス使用:WM_USER(0x0400)〜0x7FFF #define WM_HOOKACT (WM_USER+10) //activeになった #define WM_HOOKCLS (WM_USER+11) //close EXPORT LRESULT CALLBACK HookProc(int, WPARAM, LPARAM); EXPORT BOOL StartHook(HWND); EXPORT BOOL EndHook(void);

hook_msg_dll.cpp

001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076

//2010/06/18 Shirao //Hookの開始と終了 //WM_ACTIVATEとWM_CLOSEを処理 //WM_ACTIVATE => メッセージを送る //WM_CLOSE => メッセージボックスを表示 #define WIN32_LEAN_AND_MEAN #define HOOK_EXPORTS #include <windows.h> #include <tchar.h> #include "hook_msg_dll.h" #pragma data_seg(".HShared")//メモリ共有 HWND g_hWnd = NULL; HHOOK g_hHook = NULL; #pragma data_seg() #pragma comment(linker, "/Section:.HShared,rws")//defの代わりに HINSTANCE g_hInst; BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { switch(ul_reason_for_call) { case DLL_PROCESS_ATTACH: g_hInst = (HINSTANCE)hModule; break; } return TRUE; } //SetWindowsHookExに指定する EXPORT LRESULT CALLBACK HookProc( int nCode, //フックコード WPARAM wParam, //現在のプロセスに関するフラグ LPARAM lParam) //メッセージデータ { if(nCode == HC_ACTION && wParam == 0){ //このときこれは処理すべし CWPSTRUCT *pcwp = (CWPSTRUCT *)lParam; TCHAR str[512]; if(pcwp->message == WM_ACTIVATE){ //WM_HOOKACTメッセージでデータを送る SendMessage(g_hWnd, WM_HOOKACT, pcwp->wParam, pcwp->lParam); } else if(pcwp->message == WM_CLOSE){ wsprintf(str, _T("ウィンドウ(hwnd=%X)を閉じます"), pcwp->hwnd); MessageBox(pcwp->hwnd, str, _T("-- Hook Window --"), MB_OK); } } //現在のフックチェーン内の次のフックプロシージャに、フック情報を渡す return (CallNextHookEx(g_hHook, nCode, wParam, lParam)); } //フック開始 EXPORT BOOL StartHook(HWND hWnd) { g_hWnd = hWnd; //呼び出しもとのハンドル g_hHook = SetWindowsHookEx( WH_CALLWNDPROC, //フックのタイプ (HOOKPROC)HookProc, //proc g_hInst, //これのインスタンスハンドル 0);//関連付けるスレッドの識別子(0 : 全部) if(g_hHook == NULL) return TRUE; return FALSE; } //フック終了 EXPORT BOOL EndHook() { if(UnhookWindowsHookEx(g_hHook) == FALSE) return TRUE; return FALSE; }

dll呼び出しアプリケーション本体の作成

先ほど作ったdllを呼び出す側を作ります。
さっきのdllを実行ファイルと同フォルダに移動してください。
そして、先ほど作ったhook_msg_dll.hとlibファイルをプロジェクトに関連付けてください。
(libファイルの関連付けは、プロジェクトのプロパティのリンカ項目からできますが、
#pragma comment(lib, "HookMsgDll.lib")とすることでも可能です)
あと、今回一応GUIですから、適当にインタフェースを作ってもらいます。

hook_msg.cpp

001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082

//2010/06/18 Shirao //グローバルフックのサンプル #define WIN32_LEAN_AND_MEAN #include <windows.h> #include <windowsx.h> #include <tchar.h> #include "resource.h" #include "hook_msg_dll.h" #pragma comment(lib, "HookMsgDll.lib") //プロパティをいじる代わりに BOOL CALLBACK Dialog_Proc(HWND, UINT, WPARAM wParam, LPARAM); void addText(HWND, TCHAR *, DWORD); void getHookMsg(HWND, DWORD); int APIENTRY _tWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { HWND hWnd = FindWindow( _T(""), _T("HookMsgSmpl")); //自分のハンドルはどこ? if(IsWindow(hWnd)) return 1; //すでに作られていた DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, Dialog_Proc); //無いから作る return 0; } //テキストボックス(dwID)に文字列szTextを書き込む void addText(HWND hWnd, TCHAR *szText, DWORD dwID) { HWND hTarget = GetDlgItem(hWnd, dwID); DWORD dwLen = Edit_GetTextLength(hTarget); //テキストボックス中の文字列長 TCHAR *szOutput = new TCHAR[dwLen + lstrlen(szText)+256]; //すでにある+新しい+余裕(256) Edit_GetText(hTarget, &szOutput[0], dwLen + 1); lstrcat(szOutput, szText); //新しいテキストを後に接続 Edit_SetText(hTarget, szOutput); delete [] szOutput; } void getHookMsg(HWND hWnd, DWORD dwID) { static TCHAR buff[1024] = _T(""); //今のウィンドウタイトル static TCHAR prev[1024] = _T(""); TCHAR str[1024]; GetWindowText(GetForegroundWindow(), buff, sizeof(buff)); if(!lstrcmp(buff, prev)) //同じ名前なら連続させない return; lstrcpy(prev, buff); wsprintf(str, _T("%s\r\n"), buff); addText(hWnd, str, dwID); //書き込む } BOOL CALLBACK Dialog_Proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch(uMsg) { case WM_INITDIALOG: StartHook(hWnd); //GlobalHook開始 break; case WM_HOOKACT: //HookProc(in dll)が投げる if(LOWORD(wParam)!=WA_INACTIVE){ //とりあえずアクティブになったら getHookMsg(hWnd, IDC_EDIT); //書き込む } break; case WM_COMMAND: switch(LOWORD(wParam)) { case IDOK: case IDCANCEL: EndHook(); EndDialog(hWnd, NULL); break; } default: break; } return FALSE; }

インタフェース resource.h

別にどんなものを作ってもかまいませんが、ここでは以下のようなものを作りました。
流用する要件としては、エディットコントロールを配置、ボタンを最低1つ配置することです。
キャプションを変更した場合は、それに合わせてコードも変更してください。
なお、今回は複数行に書き込むので、エディットコントロールのMultilineとVertical ScrollをTrueにしておいてください。
以下のresource.hは自動生成されたものです。

001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018

//{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by HookMsgSmpl.rc // #define IDC_EDIT 1002 #define IDD_Dialog 58112 #define IDD_DIALOG1 58112 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 103 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1003 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif

ポイント

WM_USER

WM_USER(0x0400)以前はシステムに予約されており、0x0400から0x7FFFまでが アプリケーションのプライベートメッセージとして使用できます。
よって、ここではWM_USER+10をWM_HOOKACT、WM_USER+11をWM_HOOKCLSと定義しました。
10, 11という数にそれほど意味はありません。

共有メモリ領域

hook_msg_dll.cppの
15から19行
#pragma data_seg(".HShared")
HWND g_hWnd = NULL;
HHOOK g_hHook = NULL;
#pragma data_seg()
#pragma comment(linker, "/Section:.HShared,rws")
で、
g_hWnd, g_HookをDll間で共有するように設定しています。
つまり、各プロセスにマッピングされたDllそれぞれが、この二つの変数に関して同じメモリ領域を共有するということです。
よって、各Dllを例えば関数と考え、その中の変数をローカル変数とすると、この共有メモリ領域の変数はそれぞれから見える グローバル変数のような役割をしているといえます。

SetWindowsHookEx

パラメータの意味はコメントで書いておきましたので省略します。
今回はフックタイプとしてWH_CALLWNDPROCを指定しました。
このため、メッセージの改変、削除はできません。見るだけです。
改変、削除などを行なうのは確かWH_GETMESSAGEでした。
今回のプログラムでは終了間際にメッセージボックスを表示していますが、
結局は消すしかなくなっています。
メッセージを捨てるようにすれば、ウィンドウの削除を防ぐことができます(副作用については関知しませんが)。

動作サンプル

実際に動かしたところです。メモ帳を終了させようとすると、
コールバック関数で指定したようにメッセージボックスが現れ、
本体のUIにはアクティブになったウィンドウのタイトルが列挙されます。

おわりに

今回はグローバルフックのサンプルとして上記のようなプログラムを作りました。
実は色々いじっている途中で、2回ほど物理的にシャットダウンせざるを得ない状況になっちゃいました。
結構便利で、アプリによっては必須の技術ですが、危険を伴うことも自覚して使ってください。
できれば仮想マシンで開発したほうがいいですよ。

ソース・実行ファイル

ソースコード等を置いておきます。


プログラムに戻る