1、前言
众所周知,Windows应用程序都是消息(事件)驱动的,任何一个窗口都能够接收消息,并对消息进行处理,处理完成后进入下一轮循环。
通常情况下,程序员可以在窗口过程中处理接收到的消息,但是在一些应用中常常需要获取和处理另外应用程序的消息,而实现此类功能的技术也就本文要讨论的主题――消息拦截技术。
2、消息机制
2.1 来源
Windows应用程序的消息来源有4种:输入消息,控制消息、系统消息、用户消息。
而根据消息产生的方式又可以分为两大类,即硬件消息和软件消息。
硬件消息,常指由硬件所产生的事件,通过系统消息队列中转,再转发给应用程序消息队列。
软件消息,常指由系统或其它应用程序发送的信息,它直接发送到应用程序消息队列。
2.2 构成
一个消息由一个消息名称[UINT],和两个参数[WPARAM, LPARAM]。不同的消息,对应的参数含义也不一致。
所有系统消息的定义在Winuser.h中都可以找到。常用消息参考
2.3 处理
一个消息通常必须由一个窗口接收。而窗口程序本身也会实现窗口过程函数来处理接收的消息。函数原型如下:
LRESULT Wndproc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam);
当然,程序也可以使用GetMessage
直接从消息队列中获取消息进行处理。
3、详解
3.1 WH_CALLWNDPRO、WH_CALLWNDPROCRET
- 功能:在消息发送到窗口过程前、窗口过程处理完消息后调用
- 参数:
wParam 含义 lParam 如果消息由当前线程发送,则为非零;否则为零 指向 CWPSTRUCT 结构的指针 - 示例:
LRESULT(CALLBACK HOOK_CALLWNDPROC)(int code, WPARAM wParam, LPARAM lParam) {
Ud_Print(L"[Hook] process = %ld, res = %ld\n", (DWORD)wParam,
(DWORD)((PCWPRETSTRUCT)lParam)->lResult);
return CallNextHookEx(NULL, code, wParam, lParam);
}
3.2 WH_CBT
-
功能:当窗口过程接收到系统事件消息调用
-
参数:
nCode 含义 wParam lParam HCBT_MOVESIZE 即将移动窗口或调整其大小 指定要移动或调整大小的窗口的句柄 指定指向包含窗口坐标的 RECT 结构的指针 HCBT_MINMAX 窗口即将最小化或最大化 指定要最小化或最大化的窗口的句柄 在低序字中,指定一个显示窗口值 (SW_) 指定操作 HCBT_QS 系统已从系统消息队列中检索WM_QUEUESYNC消息 必须为零 必须为零 HCBT_CREATEWND 即将创建一个窗口 指定新窗口的句柄 指向包含窗口初始化参数 的CBT_CREATEWND 结构的指针 HCBT_DESTROYWND 窗口即将被销毁 指定要销毁的窗口的句柄 必须设置为零 HCBT_ACTIVATE 系统即将激活窗口 指定要激活的窗口的句柄 指向 CBTACTIVATESTRUCT 结构的指针 HCBT_CLICKSKIPPED 系统已从系统消息队列中删除鼠标消息 指定从系统消息队列中删除的鼠标消息 指向 MOUSEHOOKSTRUCT 结构的指针 HCBT_KEYSKIPPED 系统已从系统消息队列中删除键盘消息 指定虚拟密钥代码 指定重复计数、扫描代码、键转换代码、以前的键状态和上下文代码 HCBT_SYSCOMMAND 即将执行系统命令 指定系统命令值 包含与WM_SYSCOMMAND消息的 lParam 值相同的数据 HCBT_SETFOCUS 窗口即将接收键盘焦点 指定获得键盘焦点的窗口的句柄 指定失去键盘焦点的窗口的句柄 -
示例:
LRESULT(CALLBACK HOOK_CBT)(int code, WPARAM wParam, LPARAM lParam) {
wchar_t* event = UnKnown;
switch (code) {
case HCBT_CLICKSKIPPED: {
event = str(HCBT_CLICKSKIPPED);
Ud_Print(L"[Hook] message = %s, wParam = %ld, point = (%d, %d)\n", event,
(DWORD)wParam, ((PMOUSEHOOKSTRUCT)lParam)->pt.x,
((PMOUSEHOOKSTRUCT)lParam)->pt.y);
return 0;
}
case HCBT_KEYSKIPPED:
event = str(HCBT_KEYSKIPPED);
break;
case HCBT_QS:
event = str(HCBT_QS);
break;
// allow the action with 0, otherwise forbidden
case HCBT_ACTIVATE: {
event = str(HCBT_ACTIVATE);
Ud_Print(L"[Hook] message = %s, Wnd = %ld, mouse = %d\n", event,
(DWORD)wParam, ((LPCBTACTIVATESTRUCT)lParam)->fMouse);
return 0;
}
case HCBT_CREATEWND: {
event = str(HCBT_CREATEWND);
Ud_Print(L"[Hook] message = %s, Name = %s, (x,y,l,h) = (%d,%d),(%d,%d)\n",
event, ((LPCBT_CREATEWND)lParam)->lpcs->lpszName,
((LPCBT_CREATEWND)lParam)->lpcs->x,
((LPCBT_CREATEWND)lParam)->lpcs->y,
((LPCBT_CREATEWND)lParam)->lpcs->cx,
((LPCBT_CREATEWND)lParam)->lpcs->cy);
return 0;
}
case HCBT_MOVESIZE: {
event = str(HCBT_MOVESIZE);
Ud_Print(L"[Hook] message = %s, Wnd = %ld, Point = (%d,%d), (%d,%d)\n",
event, (DWORD)wParam, ((PRECTL)lParam)->left,
((PRECTL)lParam)->top, ((PRECTL)lParam)->right,
((PRECTL)lParam)->bottom);
return 0;
}
case HCBT_SYSCOMMAND: {
event = str(HCBT_SYSCOMMAND);
Ud_Print(L"[Hook] message = %s, event = %ld, point = (%d, %d)\n", event,
(DWORD)wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
return 0;
}
case HCBT_DESTROYWND:
event = str(HCBT_DESTROYWND);
break;
case HCBT_MINMAX:
event = str(HCBT_MINMAX);
break;
case HCBT_SETFOCUS:
event = str(HCBT_SETFOCUS);
break;
default:
return CallNextHookEx(NULL, code, wParam, lParam);
}
Ud_Print(L"[Hook] message = %s, wParam = %ld, lParam = %ld\n", event,
(DWORD)wParam, (DWORD)lParam);
return 0;
}
3.3 WH_DEBUG
-
功能:当有挂钩过程创建时调用
-
参数:
wParam lParam 即将调用的挂钩类型 指向 DEBUGHOOKINFO 结构的指针 -
示例:
LRESULT(CALLBACK HOOK_DEBUG)(int code, WPARAM wParam, LPARAM lParam) {
if (code == HC_ACTION) {
Ud_Print(L"[Hook] message = %s, hook type = %ld\n", str(HC_ACTION),
(DWORD)wParam);
}
return CallNextHookEx(NULL, code, wParam, lParam);
}
3.4 WH_FOREGROUNDIDLE
-
功能:当有前台线程空闲时调用
-
参数:
-
示例:
LRESULT(CALLBACK HOOK_FOREGROUNDIDLE)(int code, WPARAM wParam, LPARAM lParam) {
if (code == HC_ACTION) {
Ud_Print(L"[Hook] message = %s\n", str(HC_ACTION));
}
return CallNextHookEx(NULL, code, wParam, lParam);
}
3.5 WH_GETMESSAGE
-
功能:当消息发布到消息队列时调用
-
参数:
wParam lParam 指定是否已从队列中删除消息 指向包含消息详细信息的 MSG 结构的指针 -
示例:
LRESULT(CALLBACK HOOK_GETMESSAGE)(int code, WPARAM wParam, LPARAM lParam) {
if (code == HC_ACTION) {
Ud_Print(L"[Hook] message = %s, event = %ld, msg = %d\n", str(HC_ACTION),
(DWORD)wParam, ((PMSG)lParam)->message);
}
return CallNextHookEx(NULL, code, wParam, lParam);
}
3.6 WH_JOURNALPLAYBACK、WH_JOURNALRECORD
-
功能:记录和回放消息调用
-
参数:
nCode 含义 lParam HC_ACTION 指向 EVENTMSG 结构的指针 HC_SYSMODALOFF 系统模式对话框已被销毁 HC_SYSMODALON 正在显示系统模式对话框 HC_GETNEXT 挂钩过程必须将当前消息复制到 lParam 指向 EVENTMSG 结构的指针 HC_NOREMOVE 指示在 PeekMessage 处理后不会从消息队列中删除该消息 HC_SKIP 挂钩过程必须准备将下一个鼠标或键盘消息复制到 lParam -
示例:
void DO_JOURNALRECORD(bool);
void DO_JOURNALPLAYBACK(bool);
bool end_ = false;
int cur_ = 0;
bool flag = false;
LRESULT(CALLBACK HOOK_JOURNALPLAYBACK)
(int code, WPARAM wParam, LPARAM lParam) {
wchar_t* event = UnKnown;
if (code == HC_GETNEXT) {
event = str(HC_GETNEXT);
} else if (code == HC_SKIP) {
event = str(HC_SKIP);
} else if (code == HC_NOREMOVE) {
event = str(HC_NOREMOVE);
} else if (code == HC_SYSMODALOFF) {
event = str(HC_SYSMODALOFF);
} else if (code == HC_SYSMODALON) {
event = str(HC_SYSMODALON);
}
Ud_Print(L"[Hook] message = %s, wParam = %ld, lParam = %ld\n", event,
(DWORD)wParam, (DWORD)lParam);
if (code == HC_SKIP) {
flag = true;
if (++cur_ >= ev_record_.size()) {
std::cout << "DO_JOURNALPLAYBACK Empty!" << std::endl;
DO_JOURNALPLAYBACK(false);
PostQuitMessage(0);
end_ = true;
}
return 0;
} else if (code == HC_GETNEXT) {
DWORD time = 0;
PEVENTMSG pEv = (PEVENTMSG)lParam;
if (cur_ < ev_record_.size() && pEv) {
*pEv = ev_record_[cur_];
if (flag) {
time =
ev_record_[cur_ + 1 >= ev_record_.size() ? cur_ : cur_ + 1].time -
ev_record_[cur_].time;
flag = false;
}
}
if (time < 0) time = 1;
return time;
} else if (code == HC_NOREMOVE) {
return 0;
}
return CallNextHookEx(NULL, code, wParam, lParam);
}
bool stop_ = false;
LRESULT(CALLBACK HOOK_JOURNALRECORD)
(int code, WPARAM wParam, LPARAM lParam) {
wchar_t* event = UnKnown;
if (code == HC_ACTION) {
event = str(HC_ACTION);
} else if (code == HC_SYSMODALOFF) {
event = str(HC_SYSMODALOFF);
stop_ = false;
} else if (code == HC_SYSMODALON) {
event = str(HC_SYSMODALON);
stop_ = true;
}
PEVENTMSG pEv = (PEVENTMSG)lParam;
Ud_Print(L"[Hook] message = %s, wParam = %ld, lParam = %ld\n", event,
(DWORD)wParam, (DWORD)(code == HC_ACTION ? pEv->message : lParam));
if (!stop_ && !end_) {
// https://learn.microsoft.com/zh-cn/windows/win32/inputdev/keyboard-input-notifications
// https://learn.microsoft.com/zh-cn/windows/win32/inputdev/mouse-input-notifications
if (pEv->message == WM_KEYDOWN) {
if (LOBYTE(pEv->paramL) == VK_CANCEL) {
std::cout << "DO_JOURNALRECORD VK_CANCEL!" << std::endl;
DO_JOURNALRECORD(false);
PostQuitMessage(0);
return 0;
}
}
ev_record_.push_back(*pEv);
}
return CallNextHookEx(NULL, code, wParam, lParam);
}
void DO_MONITOR_JOURNAL() {
MSG msg;
BOOL bRet;
while (!end_) {
// The call is made by sending a message to the thread that installed the
// hook. Therefore, the thread that installed the hook must have a message
// loop.
if ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0) {
if (bRet == -1) {
std::cout << "GetMessage Error = " << GetLastError() << std::endl;
break;
} else {
TranslateMessage(&msg);
if (msg.message == WM_CANCELJOURNAL) {
std::cout << "DO_JOURNALRECORD WM_CANCELJOURNAL!" << std::endl;
hook_playback_ = NULL;
hook_record_ = NULL;
end_ = true;
}
DispatchMessage(&msg);
}
}
}
}
- 注意:安装日志挂钩的线程必须实现消息循环,否则日志挂钩无法从系统中获取消息。
3.7 WH_KEYBOARD
-
功能:当窗口接收到键盘消息时调用
-
参数:
nCode wParam lParam HC_ACTION | HC_NOREMOVE 生成 击键 消息的密钥的虚拟密钥代码 重复计数、扫描代码、扩展键标志、上下文代码、以前的键状态标志和转换状态标志 -
示例:
LRESULT(CALLBACK HOOK_KEYBOARD)(int code, WPARAM wParam, LPARAM lParam) {
if (code == HC_ACTION) {
Ud_Print(L"[Hook] message = %s, key event = (%ld, %ld)\n", str(HC_ACTION),
(DWORD)wParam, (DWORD)lParam);
} else if (code == HC_NOREMOVE) {
Ud_Print(L"[Hook] message = %s, key event = (%ld, %ld)\n", str(HC_NOREMOVE),
(DWORD)wParam, (DWORD)lParam);
}
return CallNextHookEx(NULL, code, wParam, lParam);
}
3.8 WH_MOUSE
-
功能:当窗口接收到鼠标消息时调用
-
参数:
nCode wParam lParam HC_ACTION | HC_NOREMOVE 鼠标消息的标识符 指向 MOUSEHOOKSTRUCT 结构的指针 -
示例:
LRESULT(CALLBACK HOOK_MOUSE)(int code, WPARAM wParam, LPARAM lParam) {
if (code == HC_ACTION) {
Ud_Print(L"[Hook] message = %s, event = %ld, point = (%d, %d)\n",
str(HC_ACTION), (DWORD)wParam, ((PMOUSEHOOKSTRUCT)lParam)->pt.x,
((PMOUSEHOOKSTRUCT)lParam)->pt.y);
} else if (code == HC_NOREMOVE) {
Ud_Print(L"[Hook] message = %s, event = %ld, point = (%d, %d)\n",
str(HC_NOREMOVE), (DWORD)wParam, ((PMOUSEHOOKSTRUCT)lParam)->pt.x,
((PMOUSEHOOKSTRUCT)lParam)->pt.y);
}
return CallNextHookEx(NULL, code, wParam, lParam);
}
3.9 WH_KEYBOARD_LL
-
功能:当系统接收到键盘消息时调用
-
参数:
nCode wParam lParam HC_ACTION 键盘消息的标识符 指向 KBDLLHOOKSTRUCT 结构的指针 -
示例:
static bool end_ = false;
LRESULT(CALLBACK HOOK_KEYBOARD_LL)(int code, WPARAM wParam, LPARAM lParam) {
if (code == HC_ACTION) {
Ud_Print(L"[Hook] message = %s, event = %ld, key event = (%d, %d)\n",
str(HC_ACTION), (DWORD)wParam, ((PKBDLLHOOKSTRUCT)lParam)->vkCode,
((PKBDLLHOOKSTRUCT)lParam)->flags);
if (wParam == WM_KEYDOWN) {
if (((PKBDLLHOOKSTRUCT)lParam)->vkCode == VK_CANCEL) {
end_ = true;
return 1;
}
}
}
return CallNextHookEx(NULL, code, wParam, lParam);
}
- 注意:安装低级挂钩的线程必须实现消息循环,否则挂钩无法从系统中获取消息。
3.10 WH_MOUSE_LL
-
功能:当系统接收到鼠标消息时调用
-
参数:
nCode wParam lParam HC_ACTION 鼠标消息的标识符 指向 MSLLHOOKSTRUCT 结构的指针 -
示例:
static bool end_ = false;
LRESULT(CALLBACK HOOK_MOUSE_LL)(int code, WPARAM wParam, LPARAM lParam) {
if (code == HC_ACTION) {
Ud_Print(L"[Hook] message = %s, event = %ld, point = (%d, %d)\n",
str(HC_ACTION), (DWORD)wParam, ((PMSLLHOOKSTRUCT)lParam)->pt.x,
((PMSLLHOOKSTRUCT)lParam)->pt.y);
}
return CallNextHookEx(NULL, code, wParam, lParam);
}
- 注意:安装低级挂钩的线程必须实现消息循环,否则挂钩无法从系统中获取消息。
3.11 WH_MSGFILTER
-
功能:当系统接收到键盘消息时调用
-
参数:
nCode 含义 lParam MSGF_DDEMGR MSGF_DIALOGBOX 事件发生在消息框或对话框中 指向 MSG 结构的指针 MSGF_MENU 事件发生在菜单中 指向 MSG 结构的指针 MSGF_SCROLLBAR 事件发生在滚动条中 指向 MSG 结构的指针 -
示例:
LRESULT(CALLBACK HOOK_MSGFILTER)(int code, WPARAM wParam, LPARAM lParam) {
wchar_t* event = UnKnown;
switch (code) {
case MSGF_DDEMGR: // DDEML event
event = str(MSGF_DDEMGR);
break;
case MSGF_DIALOGBOX: // dialog event
event = str(MSGF_DIALOGBOX);
break;
case MSGF_MENU: // menu event
event = str(MSGF_MENU);
case MSGF_SCROLLBAR: // scrollbar event
event = str(MSGF_SCROLLBAR);
break;
default:
return CallNextHookEx(NULL, code, wParam, lParam);
}
Ud_Print(L"[Hook] message = %s, event = %d\n", event,
((PMSG)lParam)->message);
return CallNextHookEx(NULL, code, wParam, lParam);
}
3.12 WH_SYSMSGFILTER
-
功能:当系统接收到键盘消息时调用
-
参数:
nCode 含义 lParam MSGF_DIALOGBOX 事件发生在消息框或对话框中 指向 MSG 结构的指针 MSGF_MENU 事件发生在菜单中 指向 MSG 结构的指针 MSGF_SCROLLBAR 事件发生在滚动条中 指向 MSG 结构的指针 -
示例:
LRESULT(CALLBACK HOOK_SYSMSGFILTER)(int code, WPARAM wParam, LPARAM lParam) {
wchar_t* event = UnKnown;
switch (code) {
case MSGF_DIALOGBOX: // dialog event
event = str(MSGF_DIALOGBOX);
break;
case MSGF_MENU: // menu event
event = str(MSGF_MENU);
break;
case MSGF_SCROLLBAR: // scrollbar event
event = str(MSGF_SCROLLBAR);
break;
default:
return CallNextHookEx(NULL, code, wParam, lParam);
}
Ud_Print(L"[Hook] message = %s, event = %d\n", event,
((PMSG)lParam)->message);
return CallNextHookEx(NULL, code, wParam, lParam);
}
3.12 WH_SHELL
-
功能:当系统接收到Shell事件时调用
-
参数:
nCode 含义 wParam lParam HSHELL_WINDOWCREATED 已创建顶级的无所有者窗口 所创建窗口的句柄 HSHELL_WINDOWDESTROYED 一个顶级的、无所有者的窗口即将被销毁 已销毁窗口的句柄 HSHELL_ACTIVATESHELLWINDOW shell 应激活其main窗口 HSHELL_WINDOWACTIVATED 激活已更改为其他顶级的无所有者窗口 已激活窗口的句柄 如果窗口处于全屏模式,则值为 TRUE,否则值为 FALSE HSHELL_GETMINRECT 窗口正在最小化或最大化 最小化或最大化窗口的句柄 指向 RECT 结构的指针 HSHELL_REDRAW 任务栏中窗口的标题已重绘 重绘窗口的句柄 如果窗口闪烁,则值为 TRUE,否则值为 FALSE HSHELL_TASKMAN 用户已选择任务列表 HSHELL_LANGUAGE 键盘语言已更改或加载了新的键盘布局 窗口的句柄 键盘布局的句柄 HSHELL_ACCESSIBILITYSTATE 辅助功能状态已更改 指示哪个辅助功能已更改状态 HSHELL_APPCOMMAND 用户完成了输入事件 指示最初发送WM_APPCOMMAND消息的位置 包含与WM_APPCOMMAN消息的 lParam 值相同的数据 HSHELL_WINDOWREPLACED 正在替换顶级窗口 要替换的窗口的句柄 新窗口的句柄 -
示例:
LRESULT(CALLBACK HOOK_SHELL)(int code, WPARAM wParam, LPARAM lParam) {
wchar_t* event = UnKnown;
switch (code) {
case HSHELL_ACCESSIBILITYSTATE: // The accessibility state has changed.
event = str(HSHELL_ACCESSIBILITYSTATE);
break;
case HSHELL_ACTIVATESHELLWINDOW: // The shell should activate its main
// window
event = str(HSHELL_ACTIVATESHELLWINDOW);
break;
case HSHELL_TASKMAN: // The user has selected the task list
event = str(HSHELL_TASKMAN);
break;
case HSHELL_LANGUAGE: // Keyboard language was changed
event = str(HSHELL_LANGUAGE);
break;
case HSHELL_REDRAW: // The title of a window in the task bar has been
// redrawn.
event = str(HSHELL_REDRAW);
break;
case HSHELL_WINDOWACTIVATED: // The activation has changed to a
// different top-level, unowned window.
event = str(HSHELL_WINDOWACTIVATED);
break;
case HSHELL_WINDOWCREATED: // A top-level, unowned window has been
// created.
event = str(HSHELL_WINDOWCREATED);
break;
case HSHELL_WINDOWDESTROYED: // A top-level, unowned window is about to be
// destroyed.
event = str(HSHELL_WINDOWDESTROYED);
break;
case HSHELL_WINDOWREPLACED: // A top-level window is being replaced.
event = str(HSHELL_WINDOWREPLACED);
break;
case HSHELL_GETMINRECT: // A window is being minimized or maximized.
{
PRECT pRect = (PRECT)lParam;
Ud_Print(
L"[Hook] message = %s, Wnd = %ld, Point = (%d,%d), "
L"(%d,%d)\n",
str(HSHELL_GETMINRECT), (DWORD)wParam, pRect->left, pRect->top,
pRect->right, pRect->bottom);
return 0;
}
case HSHELL_APPCOMMAND: {
Ud_Print(L"[Hook] message = %s, Cmd = %d, Device = %d, KeyState = %d\n",
str(HSHELL_APPCOMMAND), GET_APPCOMMAND_LPARAM(lParam),
GET_DEVICE_LPARAM(lParam), GET_KEYSTATE_LPARAM(lParam));
return 0;
};
default:
return CallNextHookEx(NULL, code, wParam, lParam);
}
Ud_Print(L"[Hook] message = %s, wParam = %ld, lParam = %ld\n", event,
(DWORD)wParam, (DWORD)lParam);
return 0;
}