후크 사용
다음 코드 예제에서는 후크와 연결된 다음 작업을 수행하는 방법을 보여 줍니다.
후크 프로시저 설치 및 해제
SetWindowsHookEx 함수를 호출하고 프로시저를 호출하는 후크 유형을 지정하여 후크 프로시저를 설치할 수 있습니다. 프로시저를 호출 스레드와 동일한 데스크톱의 모든 스레드와 연결해야 하는지 또는 특정 스레드와 연결해야 하는지 여부와 프로시저 진입점에 대한 포인터를 지정할 수 있습니다.
후크 프로시저를 설치하는 애플리케이션과 별도로 DLL에 전역 후크 프로시저를 배치해야 합니다. 후크 프로시저를 설치하려면 먼저 설치 애플리케이션에 DLL 모듈에 대한 핸들이 있어야 합니다. DLL 모듈에 대한 핸들을 검색하려면 DLL 이름으로 LoadLibrary 함수를 호출합니다. 핸들을 가져온 후 GetProcAddress 함수를 호출하여 후크 프로시저에 대한 포인터를 검색할 수 있습니다. 마지막으로 SetWindowsHookEx를 사용하여 적절한 후크 체인에 후크 프로시저 주소를 설치합니다. SetWindowsHookEx 는 모듈 핸들, 후크 프로시저 진입점에 대한 포인터 및 스레드 식별자에 대해 0을 전달합니다. 이는 후크 프로시저가 호출 스레드와 동일한 데스크톱의 모든 스레드와 연결되어야 함을 나타냅니다. 이 시퀀스는 다음 예제에 나와 있습니다.
HOOKPROC hkprcSysMsg;
static HINSTANCE hinstDLL;
static HHOOK hhookSysMsg;
hinstDLL = LoadLibrary(TEXT("c:\\myapp\\sysmsg.dll"));
hkprcSysMsg = (HOOKPROC)GetProcAddress(hinstDLL, "SysMessageProc");
hhookSysMsg = SetWindowsHookEx(
WH_SYSMSGFILTER,
hkprcSysMsg,
hinstDLL,
0);
UnhookWindowsHookEx 함수를 호출하고 해제할 후크 프로시저에 대한 핸들을 지정하여 스레드별 후크 프로시저(후크 체인에서 해당 주소를 제거)를 해제할 수 있습니다. 애플리케이션에 더 이상 필요하지 않은 즉시 후크 프로시저를 해제합니다.
UnhookWindowsHookEx를 사용하여 전역 후크 프로시저를 해제할 수 있지만 이 함수는 후크 프로시저가 포함된 DLL을 해제하지 않습니다. 이는 전역 후크 프로시저가 데스크톱의 모든 애플리케이션의 프로세스 컨텍스트에서 호출되어 모든 프로세스에 대해 LoadLibrary 함수를 암시적으로 호출하기 때문입니다. 다른 프로세스에 대해 FreeLibrary 함수를 호출할 수 없으므로 DLL을 해제할 방법이 없습니다. DLL에 명시적으로 연결된 모든 프로세스가 종료되었거나 FreeLibrary 라고 하고 후크 프로시저를 호출한 모든 프로세스가 DLL 외부에서 처리를 재개한 후 시스템은 결국 DLL을 해제합니다.
전역 후크 프로시저를 설치하는 다른 방법은 후크 프로시저와 함께 DLL에 설치 함수를 제공하는 것입니다. 이 메서드를 사용하면 설치 애플리케이션에 DLL 모듈에 대한 핸들이 필요하지 않습니다. DLL과 연결하면 애플리케이션이 설치 함수에 액세스할 수 있습니다. 설치 함수는 SetWindowsHookEx 호출에서 DLL 모듈 핸들 및 기타 세부 정보를 제공할 수 있습니다. DLL에는 전역 후크 프로시저를 해제하는 함수도 포함될 수 있습니다. 애플리케이션은 종료 시 이 후크 해제 함수를 호출할 수 있습니다.
시스템 이벤트 모니터링
다음 예제에서는 다양한 스레드별 후크 프로시저를 사용하여 스레드에 영향을 주는 이벤트에 대해 시스템을 모니터링합니다. 다음 유형의 후크 프로시저에 대한 이벤트를 처리하는 방법을 보여 줍니다.
- WH_CALLWNDPROC
- WH_CBT
- WH_DEBUG
- WH_GETMESSAGE
- WH_KEYBOARD
- WH_MOUSE
- WH_MSGFILTER
사용자는 메뉴를 사용하여 후크 프로시저를 설치하고 제거할 수 있습니다. 후크 프로시저가 설치되고 프로시저에서 모니터링되는 이벤트가 발생하면 프로시저는 이벤트에 대한 정보를 애플리케이션의 기본 창의 클라이언트 영역에 씁니다.
#include <windows.h>
#include <strsafe.h>
#include "app.h"
#pragma comment( lib, "user32.lib")
#pragma comment( lib, "gdi32.lib")
#define NUMHOOKS 7
// Global variables
typedef struct _MYHOOKDATA
{
int nType;
HOOKPROC hkprc;
HHOOK hhook;
} MYHOOKDATA;
MYHOOKDATA myhookdata[NUMHOOKS];
HWND gh_hwndMain;
// Hook procedures
LRESULT WINAPI CallWndProc(int, WPARAM, LPARAM);
LRESULT WINAPI CBTProc(int, WPARAM, LPARAM);
LRESULT WINAPI DebugProc(int, WPARAM, LPARAM);
LRESULT WINAPI GetMsgProc(int, WPARAM, LPARAM);
LRESULT WINAPI KeyboardProc(int, WPARAM, LPARAM);
LRESULT WINAPI MouseProc(int, WPARAM, LPARAM);
LRESULT WINAPI MessageProc(int, WPARAM, LPARAM);
void LookUpTheMessage(PMSG, LPTSTR);
LRESULT WINAPI MainWndProc(HWND hwndMain, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static BOOL afHooks[NUMHOOKS];
int index;
static HMENU hmenu;
gh_hwndMain = hwndMain;
switch (uMsg)
{
case WM_CREATE:
// Save the menu handle
hmenu = GetMenu(hwndMain);
// Initialize structures with hook data. The menu-item identifiers are
// defined as 0 through 6 in the header file app.h. They can be used to
// identify array elements both here and during the WM_COMMAND message.
myhookdata[IDM_CALLWNDPROC].nType = WH_CALLWNDPROC;
myhookdata[IDM_CALLWNDPROC].hkprc = CallWndProc;
myhookdata[IDM_CBT].nType = WH_CBT;
myhookdata[IDM_CBT].hkprc = CBTProc;
myhookdata[IDM_DEBUG].nType = WH_DEBUG;
myhookdata[IDM_DEBUG].hkprc = DebugProc;
myhookdata[IDM_GETMESSAGE].nType = WH_GETMESSAGE;
myhookdata[IDM_GETMESSAGE].hkprc = GetMsgProc;
myhookdata[IDM_KEYBOARD].nType = WH_KEYBOARD;
myhookdata[IDM_KEYBOARD].hkprc = KeyboardProc;
myhookdata[IDM_MOUSE].nType = WH_MOUSE;
myhookdata[IDM_MOUSE].hkprc = MouseProc;
myhookdata[IDM_MSGFILTER].nType = WH_MSGFILTER;
myhookdata[IDM_MSGFILTER].hkprc = MessageProc;
// Initialize all flags in the array to FALSE.
memset(afHooks, FALSE, sizeof(afHooks));
return 0;
case WM_COMMAND:
switch (LOWORD(wParam))
{
// The user selected a hook command from the menu.
case IDM_CALLWNDPROC:
case IDM_CBT:
case IDM_DEBUG:
case IDM_GETMESSAGE:
case IDM_KEYBOARD:
case IDM_MOUSE:
case IDM_MSGFILTER:
// Use the menu-item identifier as an index
// into the array of structures with hook data.
index = LOWORD(wParam);
// If the selected type of hook procedure isn't
// installed yet, install it and check the
// associated menu item.
if (!afHooks[index])
{
myhookdata[index].hhook = SetWindowsHookEx(
myhookdata[index].nType,
myhookdata[index].hkprc,
(HINSTANCE) NULL, GetCurrentThreadId());
CheckMenuItem(hmenu, index,
MF_BYCOMMAND | MF_CHECKED);
afHooks[index] = TRUE;
}
// If the selected type of hook procedure is
// already installed, remove it and remove the
// check mark from the associated menu item.
else
{
UnhookWindowsHookEx(myhookdata[index].hhook);
CheckMenuItem(hmenu, index,
MF_BYCOMMAND | MF_UNCHECKED);
afHooks[index] = FALSE;
}
default:
return (DefWindowProc(hwndMain, uMsg, wParam,
lParam));
}
break;
//
// Process other messages.
//
default:
return DefWindowProc(hwndMain, uMsg, wParam, lParam);
}
return NULL;
}
/****************************************************************
WH_CALLWNDPROC hook procedure
****************************************************************/
LRESULT WINAPI CallWndProc(int nCode, WPARAM wParam, LPARAM lParam)
{
CHAR szCWPBuf[256];
CHAR szMsg[16];
HDC hdc;
static int c = 0;
size_t cch;
HRESULT hResult;
if (nCode < 0) // do not process message
return CallNextHookEx(myhookdata[IDM_CALLWNDPROC].hhook, nCode, wParam, lParam);
// Call an application-defined function that converts a message
// constant to a string and copies it to a buffer.
LookUpTheMessage((PMSG) lParam, szMsg);
hdc = GetDC(gh_hwndMain);
switch (nCode)
{
case HC_ACTION:
hResult = StringCchPrintf(szCWPBuf, 256/sizeof(TCHAR),
"CALLWNDPROC - tsk: %ld, msg: %s, %d times ",
wParam, szMsg, c++);
if (FAILED(hResult))
{
// TODO: writer error handler
}
hResult = StringCchLength(szCWPBuf, 256/sizeof(TCHAR), &cch);
if (FAILED(hResult))
{
// TODO: write error handler
}
TextOut(hdc, 2, 15, szCWPBuf, cch);
break;
default:
break;
}
ReleaseDC(gh_hwndMain, hdc);
return CallNextHookEx(myhookdata[IDM_CALLWNDPROC].hhook, nCode, wParam, lParam);
}
/****************************************************************
WH_GETMESSAGE hook procedure
****************************************************************/
LRESULT CALLBACK GetMsgProc(int nCode, WPARAM wParam, LPARAM lParam)
{
CHAR szMSGBuf[256];
CHAR szRem[16];
CHAR szMsg[16];
HDC hdc;
static int c = 0;
size_t cch;
HRESULT hResult;
if (nCode < 0) // do not process message
return CallNextHookEx(myhookdata[IDM_GETMESSAGE].hhook, nCode,
wParam, lParam);
switch (nCode)
{
case HC_ACTION:
switch (wParam)
{
case PM_REMOVE:
hResult = StringCchCopy(szRem, 16/sizeof(TCHAR), "PM_REMOVE");
if (FAILED(hResult))
{
// TODO: write error handler
}
break;
case PM_NOREMOVE:
hResult = StringCchCopy(szRem, 16/sizeof(TCHAR), "PM_NOREMOVE");
if (FAILED(hResult))
{
// TODO: write error handler
}
break;
default:
hResult = StringCchCopy(szRem, 16/sizeof(TCHAR), "Unknown");
if (FAILED(hResult))
{
// TODO: write error handler
}
break;
}
// Call an application-defined function that converts a
// message constant to a string and copies it to a
// buffer.
LookUpTheMessage((PMSG) lParam, szMsg);
hdc = GetDC(gh_hwndMain);
hResult = StringCchPrintf(szMSGBuf, 256/sizeof(TCHAR),
"GETMESSAGE - wParam: %s, msg: %s, %d times ",
szRem, szMsg, c++);
if (FAILED(hResult))
{
// TODO: write error handler
}
hResult = StringCchLength(szMSGBuf, 256/sizeof(TCHAR), &cch);
if (FAILED(hResult))
{
// TODO: write error handler
}
TextOut(hdc, 2, 35, szMSGBuf, cch);
break;
default:
break;
}
ReleaseDC(gh_hwndMain, hdc);
return CallNextHookEx(myhookdata[IDM_GETMESSAGE].hhook, nCode, wParam, lParam);
}
/****************************************************************
WH_DEBUG hook procedure
****************************************************************/
LRESULT CALLBACK DebugProc(int nCode, WPARAM wParam, LPARAM lParam)
{
CHAR szBuf[128];
HDC hdc;
static int c = 0;
size_t cch;
HRESULT hResult;
if (nCode < 0) // do not process message
return CallNextHookEx(myhookdata[IDM_DEBUG].hhook, nCode,
wParam, lParam);
hdc = GetDC(gh_hwndMain);
switch (nCode)
{
case HC_ACTION:
hResult = StringCchPrintf(szBuf, 128/sizeof(TCHAR),
"DEBUG - nCode: %d, tsk: %ld, %d times ",
nCode,wParam, c++);
if (FAILED(hResult))
{
// TODO: write error handler
}
hResult = StringCchLength(szBuf, 128/sizeof(TCHAR), &cch);
if (FAILED(hResult))
{
// TODO: write error handler
}
TextOut(hdc, 2, 55, szBuf, cch);
break;
default:
break;
}
ReleaseDC(gh_hwndMain, hdc);
return CallNextHookEx(myhookdata[IDM_DEBUG].hhook, nCode, wParam, lParam);
}
/****************************************************************
WH_CBT hook procedure
****************************************************************/
LRESULT CALLBACK CBTProc(int nCode, WPARAM wParam, LPARAM lParam)
{
CHAR szBuf[128];
CHAR szCode[128];
HDC hdc;
static int c = 0;
size_t cch;
HRESULT hResult;
if (nCode < 0) // do not process message
return CallNextHookEx(myhookdata[IDM_CBT].hhook, nCode, wParam,
lParam);
hdc = GetDC(gh_hwndMain);
switch (nCode)
{
case HCBT_ACTIVATE:
hResult = StringCchCopy(szCode, 128/sizeof(TCHAR), "HCBT_ACTIVATE");
if (FAILED(hResult))
{
// TODO: write error handler
}
break;
case HCBT_CLICKSKIPPED:
hResult = StringCchCopy(szCode, 128/sizeof(TCHAR), "HCBT_CLICKSKIPPED");
if (FAILED(hResult))
{
// TODO: write error handler
}
break;
case HCBT_CREATEWND:
hResult = StringCchCopy(szCode, 128/sizeof(TCHAR), "HCBT_CREATEWND");
if (FAILED(hResult))
{
// TODO: write error handler
}
break;
case HCBT_DESTROYWND:
hResult = StringCchCopy(szCode, 128/sizeof(TCHAR), "HCBT_DESTROYWND");
if (FAILED(hResult))
{
// TODO: write error handler
}
break;
case HCBT_KEYSKIPPED:
hResult = StringCchCopy(szCode, 128/sizeof(TCHAR), "HCBT_KEYSKIPPED");
if (FAILED(hResult))
{
// TODO: write error handler
}
break;
case HCBT_MINMAX:
hResult = StringCchCopy(szCode, 128/sizeof(TCHAR), "HCBT_MINMAX");
if (FAILED(hResult))
{
// TODO: write error handler
}
break;
case HCBT_MOVESIZE:
hResult = StringCchCopy(szCode, 128/sizeof(TCHAR), "HCBT_MOVESIZE");
if (FAILED(hResult))
{
// TODO: write error handler
}
break;
case HCBT_QS:
hResult = StringCchCopy(szCode, 128/sizeof(TCHAR), "HCBT_QS");
if (FAILED(hResult))
{
// TODO: write error handler
}
break;
case HCBT_SETFOCUS:
hResult = StringCchCopy(szCode, 128/sizeof(TCHAR), "HCBT_SETFOCUS");
if (FAILED(hResult))
{
// TODO: write error handler
}
break;
case HCBT_SYSCOMMAND:
hResult = StringCchCopy(szCode, 128/sizeof(TCHAR), "HCBT_SYSCOMMAND");
if (FAILED(hResult))
{
// TODO: write error handler
}
break;
default:
hResult = StringCchCopy(szCode, 128/sizeof(TCHAR), "Unknown");
if (FAILED(hResult))
{
// TODO: write error handler
}
break;
}
hResult = StringCchPrintf(szBuf, 128/sizeof(TCHAR), "CBT - nCode: %s, tsk: %ld, %d times ",
szCode, wParam, c++);
if (FAILED(hResult))
{
// TODO: write error handler
}
hResult = StringCchLength(szBuf, 128/sizeof(TCHAR), &cch);
if (FAILED(hResult))
{
// TODO: write error handler
}
TextOut(hdc, 2, 75, szBuf, cch);
ReleaseDC(gh_hwndMain, hdc);
return CallNextHookEx(myhookdata[IDM_CBT].hhook, nCode, wParam, lParam);
}
/****************************************************************
WH_MOUSE hook procedure
****************************************************************/
LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam)
{
CHAR szBuf[128];
CHAR szMsg[16];
HDC hdc;
static int c = 0;
size_t cch;
HRESULT hResult;
if (nCode < 0) // do not process the message
return CallNextHookEx(myhookdata[IDM_MOUSE].hhook, nCode,
wParam, lParam);
// Call an application-defined function that converts a message
// constant to a string and copies it to a buffer.
LookUpTheMessage((PMSG) lParam, szMsg);
hdc = GetDC(gh_hwndMain);
hResult = StringCchPrintf(szBuf, 128/sizeof(TCHAR),
"MOUSE - nCode: %d, msg: %s, x: %d, y: %d, %d times ",
nCode, szMsg, LOWORD(lParam), HIWORD(lParam), c++);
if (FAILED(hResult))
{
// TODO: write error handler
}
hResult = StringCchLength(szBuf, 128/sizeof(TCHAR), &cch);
if (FAILED(hResult))
{
// TODO: write error handler
}
TextOut(hdc, 2, 95, szBuf, cch);
ReleaseDC(gh_hwndMain, hdc);
return CallNextHookEx(myhookdata[IDM_MOUSE].hhook, nCode, wParam, lParam);
}
/****************************************************************
WH_KEYBOARD hook procedure
****************************************************************/
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
CHAR szBuf[128];
HDC hdc;
static int c = 0;
size_t cch;
HRESULT hResult;
if (nCode < 0) // do not process message
return CallNextHookEx(myhookdata[IDM_KEYBOARD].hhook, nCode,
wParam, lParam);
hdc = GetDC(gh_hwndMain);
hResult = StringCchPrintf(szBuf, 128/sizeof(TCHAR), "KEYBOARD - nCode: %d, vk: %d, %d times ", nCode, wParam, c++);
if (FAILED(hResult))
{
// TODO: write error handler
}
hResult = StringCchLength(szBuf, 128/sizeof(TCHAR), &cch);
if (FAILED(hResult))
{
// TODO: write error handler
}
TextOut(hdc, 2, 115, szBuf, cch);
ReleaseDC(gh_hwndMain, hdc);
return CallNextHookEx(myhookdata[IDM_KEYBOARD].hhook, nCode, wParam, lParam);
}
/****************************************************************
WH_MSGFILTER hook procedure
****************************************************************/
LRESULT CALLBACK MessageProc(int nCode, WPARAM wParam, LPARAM lParam)
{
CHAR szBuf[128];
CHAR szMsg[16];
CHAR szCode[32];
HDC hdc;
static int c = 0;
size_t cch;
HRESULT hResult;
if (nCode < 0) // do not process message
return CallNextHookEx(myhookdata[IDM_MSGFILTER].hhook, nCode,
wParam, lParam);
switch (nCode)
{
case MSGF_DIALOGBOX:
hResult = StringCchCopy(szCode, 32/sizeof(TCHAR), "MSGF_DIALOGBOX");
if (FAILED(hResult))
{
// TODO: write error handler
}
break;
case MSGF_MENU:
hResult = StringCchCopy(szCode, 32/sizeof(TCHAR), "MSGF_MENU");
if (FAILED(hResult))
{
// TODO: write error handler
}
break;
case MSGF_SCROLLBAR:
hResult = StringCchCopy(szCode, 32/sizeof(TCHAR), "MSGF_SCROLLBAR");
if (FAILED(hResult))
{
// TODO: write error handler
}
break;
default:
hResult = StringCchPrintf(szCode, 128/sizeof(TCHAR), "Unknown: %d", nCode);
if (FAILED(hResult))
{
// TODO: write error handler
}
break;
}
// Call an application-defined function that converts a message
// constant to a string and copies it to a buffer.
LookUpTheMessage((PMSG) lParam, szMsg);
hdc = GetDC(gh_hwndMain);
hResult = StringCchPrintf(szBuf, 128/sizeof(TCHAR),
"MSGFILTER nCode: %s, msg: %s, %d times ",
szCode, szMsg, c++);
if (FAILED(hResult))
{
// TODO: write error handler
}
hResult = StringCchLength(szBuf, 128/sizeof(TCHAR), &cch);
if (FAILED(hResult))
{
// TODO: write error handler
}
TextOut(hdc, 2, 135, szBuf, cch);
ReleaseDC(gh_hwndMain, hdc);
return CallNextHookEx(myhookdata[IDM_MSGFILTER].hhook, nCode, wParam, lParam);
}