目录
前言
一、实现 CallWndProcHook
二、安装钩子例程
三、创建消息处理窗口
四、完整代码和注意事项
五、总结&更新
文章出处链接:[https://blog.csdn.net/qq_59075481/article/details/136240462]
前言
在《获取 Windows 系统托盘图标信息的最新方案(一)》中(下文简称 《最新方案(一)》),我们讨论了在 Win11 22H2 上获取系统托盘图标信息的方法,即拦截 Shell_TrayWnd 窗口的 WM_COPYDATA 消息。在《最新方案(一)》中,我们主要使用 Inline hook 重写 CTray::v_WndProc 函数,也就是窗口过程函数来拦截 WM_COPYDATA 消息。具体分析了两种注入 explorer 的实现方法:(1)创建挂起进程的远程线程注入;(2)模拟调试进程的 TLS 函数注入。本文我将分析通过 SetWindowsHookEx 实现消息钩子的角度分析如何拦截 WM_COPYDATA,该方法与拦截未导出的 CTray::v_WndProc 函数相比,将更加易于实现。
系列文章列表:
编号 | 文章标题 | AID |
1 | 获取系统托盘图标信息的尝试 | 128594435 |
2 | 获取 Windows 系统托盘图标信息的最新方案(一) | 136134195 |
3 | 获取 Windows 系统托盘图标信息的最新方案(二) | 136199670 |
4 | 获取 Windows 系统托盘图标信息的最新方案(三) | 136240462 |
一、实现 CallWndProcHook
SetWindowsHookEx 提供了多种钩子类型。尽管 WH_GETMESSAGE 类钩子也可以实现对 WM_COPYDATA 消息的拦截。我依然准备使用 WH_CALLWNDPROC 钩子,在安装此类钩子后,消息在到达处理消息的窗口过程之前就被我们的窗口过程处理,这类似于窗口子类化。
我们使用 SendMessageTimeout 发送消息给我们自定义的消息处理窗口。
使用静态函数 GetMessageProc 配合 WH_GETMESSAGE 类钩子拦截基础消息结构:
static LRESULT CALLBACK GetMessageProc(int code, WPARAM wParam, LPARAM lParam)
{
if (code == HC_ACTION)
{
PCWPSTRUCT pMsg = (PCWPSTRUCT)lParam;
SHELLWND_MAG lpTrayData = { 0 };
COPYDATASTRUCT lpSevCDS = { 0 };
DWORD_PTR lpdwResult = 0;
lpTrayData.hMsgWnd = pMsg->hwnd;
lpTrayData.message = pMsg->message;
lpTrayData.wParam = pMsg->wParam;
lpTrayData.lParam = pMsg->lParam;
// 填充 COPYDATASTRUCT 结构体
lpSevCDS.dwData = WM_NotifyGetMessage;
lpSevCDS.cbData = sizeof(SHELLWND_MAG);
lpSevCDS.lpData = &lpTrayData;
SendMessageTimeoutW(g_hNotifyWnd,
WM_COPYDATA, (WPARAM)wParam,
(LPARAM)&lpSevCDS, SMTO_NOTIMEOUTIFNOTHUNG, 0x1B58, &lpdwResult);
//char szBuf[MAX_PATH] = {0};
//_snprintf_s(szBuf, MAX_PATH, "GetMessage Handle: 0x%08X PostMsg: %s(%04X), wParam: %08X, lParam: %08X\n",
// pMsg->hwnd, GetMsgStringA(pMsg->message), pMsg->message, (int)pMsg->wParam, (int)pMsg->lParam);
//OutputDebugStringA(szBuf);
}
return CallNextHookEx(g_hGetMessageHook, code, wParam, lParam);
}
使用静态函数 CallWndProc 配合 WH_CALLWNDPROC 类钩子拦截 WM_COPYDATA 等特殊消息结构:
static LRESULT CALLBACK CallWndProc(int code, WPARAM wParam, LPARAM lParam)
{
if (code == HC_ACTION)
{
PCWPSTRUCT pMsg = (PCWPSTRUCT)lParam;
if (pMsg->hwnd == g_hCaptureWnd && pMsg->message == WM_COPYDATA)
{
PTRAY_ICON_DATAW lpTrayData = nullptr;
COPYDATASTRUCT* lpShellCDS =
(COPYDATASTRUCT*)pMsg->lParam;
COPYDATASTRUCT lpSevCDS = { 0 };
DWORD_PTR lpdwResult = 0;
if (lpShellCDS->dwData == 1) // 判断是否是 Shell_NotifyIcon 调用
{
lpTrayData = (TRAY_ICON_DATAW*)lpShellCDS->lpData;
if (lpTrayData->Signature == 0x34753423) // 判断是否是 NOTIFYICONDATA 结构体封送过程
{
// 填充 COPYDATASTRUCT 结构体
lpSevCDS.dwData = WM_NotifyCallWndProc;
lpSevCDS.cbData = sizeof(TRAY_ICON_DATAW);
lpSevCDS.lpData = lpTrayData;
// 发送消息到我们自己的窗口
SendMessageTimeoutW(g_hNotifyWnd,
WM_COPYDATA, (WPARAM)pMsg->wParam,
(LPARAM)&lpSevCDS, SMTO_NOTIMEOUTIFNOTHUNG, 0x1B58, &lpdwResult);
}
}
//char szBuf[MAX_PATH] = {0};
//_snprintf_s(szBuf, MAX_PATH, "CallWndProc Handle: 0x%08X SendMsg: %s(%04X), wParam: %08X, lParam: %08X\n",
// pMsg->hwnd, GetMsgStringA(pMsg->message), pMsg->message, (int)pMsg->wParam, (int)pMsg->lParam);
//OutputDebugStringA(szBuf);
}
}
return CallNextHookEx(g_hCallWndProcHook, code, wParam, lParam);
}
使用静态函数 CBTProc 配合 WH_CBT 类钩子即可拦截窗口创建/销毁等状态消息:
static LRESULT CALLBACK CBTProc(int nCode, WPARAM wParam, LPARAM lParam)
{
if (nCode >= 0)
{
if (nCode == HCBT_ACTIVATE) //Called when the application window is activated
{
::PostMessage(g_hNotifyWnd, WM_NotifyActivate, wParam, NULL);
}
else if (nCode == HCBT_SETFOCUS)
{
::PostMessage(g_hNotifyWnd, WM_NotifyFocus, wParam, NULL);
}
else if (nCode == HCBT_DESTROYWND) //Called when the application window is destroyed
{
}
}
return CallNextHookEx(g_hCBTHook, nCode, wParam, lParam);
}
二、安装钩子例程
使用 SetWindowsHookEx 并指定第三个参数为与挂钩例程绑定的消息窗口的线程。
比如 WH_CALLWNDPROC 钩子安装的方式如下:
DLL_EXPORT bool InstallCallWndProcHook(HWND hNotifyWnd, HWND hCaptureWnd)
{
g_hNotifyWnd = hNotifyWnd;
g_hCaptureWnd = hCaptureWnd;
if (!g_hCallWndProcHook)
{
DWORD dwThreadId = ::GetWindowThreadProcessId(g_hCaptureWnd, NULL);
g_hCallWndProcHook = SetWindowsHookEx(WH_CALLWNDPROC, (HOOKPROC)CallWndProc, g_hInstance, dwThreadId);
if (g_hCallWndProcHook)
{
OutputDebugStringA("Hook CallWndProc succeed\n");
return true;
}
else
{
DWORD dwError = GetLastError();
char szError[MAX_PATH];
_snprintf_s(szError, MAX_PATH, "Hook CallWndProc failed, error = %u\n", dwError);
OutputDebugStringA(szError);
}
}
return false;
}
三、创建消息处理窗口
在我们的程序中需要创建消息处理窗口来处理钩子模块转发至我们进程的消息数据。
LRESULT CALLBACK WindowProc(
HWND hwnd, UINT uMsg,
WPARAM wParam, LPARAM lParam);
int main() {
_wsetlocale(LC_ALL, L".UTF8"); // 设置代码页以支持中文
SetConsoleTitleW(L"ConsoleShowNofifyMsg");
// 创建窗口类
WNDCLASS wc = {};
wc.lpfnWndProc = WindowProc;
wc.hInstance = GetModuleHandle(NULL);
wc.lpszClassName = L"ShellMsgGetMessageWindowClass";
RegisterClass(&wc);
// 创建窗口
HWND hwnd = CreateWindowExW(
WS_EX_LAYERED |
WS_EX_TRANSPARENT |
WS_EX_TOOLWINDOW,
wc.lpszClassName,
L"ShellMsgGetMessageWindow",
0, 0, 0,
0, 0,
NULL, NULL,
GetModuleHandle(NULL),
NULL);
if (hwnd == NULL) {
std::cerr << "Failed to create window." << std::endl;
return 1;
}
// 设置窗口透明度
SetLayeredWindowAttributes(hwnd, RGB(0, 0, 0), 1, LWA_ALPHA);
// 显示窗口
ShowWindow(hwnd, SW_SHOWDEFAULT);
// 启用窗口过程钩子
if (!OnInstallHookNotifyWndProc(hwnd))
{
CloseWindow(hwnd);
return -1;
}
// 消息循环
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
由于是要对 Shell_TrayWnd 窗口进行挂钩处理,所以我们在挂钩处理程序中这样写:
// 安装 Win32 钩子
BOOL OnInstallHookNotifyWndProc(HWND hDlgWnd)
{
bool m_bCallWndProcHooked = false;
HWND hShellWnd = ::FindWindow(TEXT("Shell_TrayWnd"), nullptr);
if (!hShellWnd || !::IsWindow(hShellWnd))
{
MessageBox(NULL, TEXT("Not found Shell_TrayWnd."),
TEXT("No!!!"), MB_OK | MB_ICONWARNING);
return FALSE;
}
m_bCallWndProcHooked = InstallCallWndProcHook(hDlgWnd, hShellWnd);
if (m_bCallWndProcHooked)
{
wprintf(L"Hook CallWndProc succeed\r\n");
return TRUE;
}
else
{
wprintf(L"Hook CallWndProc failed\r\n");
return FALSE;
}
}
创建窗口以及挂钩均完成后,窗口需要开始接收消息。接收消息的窗口过程函数如下:
// 消息窗口过程函数
LRESULT CALLBACK WindowProc(
HWND hwnd, UINT uMsg,
WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_DESTROY:
OnUninstallHookNotifyWndProc();
PostQuitMessage(0);
return 0;
case WM_COPYDATA:
{
COPYDATASTRUCT* lpReceiveCDS = (COPYDATASTRUCT*)lParam;
PTRAY_ICON_DATAW lpNotifyData = nullptr;
if (lpReceiveCDS == nullptr ||
IsBadReadPtr(lpReceiveCDS, sizeof(TRAY_ICON_DATAW)) == TRUE)
{
break;
}
// 测试时,只实现了 WM_NotifyCallWndProc 钩子的消息处理
if (lpReceiveCDS->dwData == WM_NotifyCallWndProc
&& lpReceiveCDS->cbData == sizeof(TRAY_ICON_DATAW))
{
lpNotifyData = (TRAY_ICON_DATAW*)lpReceiveCDS->lpData;
// 输出结果
wprintf(L"CTray-NotifyIconMsg:[%ws]:[%ws];\n",
DMSG2TEXT(lpNotifyData->dwMessage), HWND2TEXT((HWND)wParam));
if ((lpNotifyData->uFlags & NIF_INFO) != 0)
{
wprintf(L"Tip[%ws], szInfoParam:\n",
lpNotifyData->szTip);
wprintf(L"InfoTitle[%ws], Info[%ws], InfoFlags[%ws];\n",
lpNotifyData->szInfoTitle, lpNotifyData->szInfo,
NIIF2TEXT(lpNotifyData->dwInfoFlags));
}
else if ((lpNotifyData->uFlags & NIF_TIP) != 0)
{
wprintf(L"Tip[%ws], non-szInfo;\n", lpNotifyData->szTip);
}
else {
wprintf(L"non-szTip, non-szInfo;\n");
}
}
break;
}
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
程序编译完成后运行效果如下图所示:
四、完整代码和注意事项
注意事项:该程序如果要实现获取全部图标的功能,必须在资源管理器创建窗口(推荐使用 WH_CBT 钩子)的时候就执行 WH_CALLWNDPROC 钩子对 WM_COPYDATA 消息进行转发处理。并把工具注册为系统开机自启动服务,这样就可以在 explorer 初始化时无缝衔接消息的处理过程。我给出的代码中 WH_CBT (钩子模块中有 CBT 挂钩的样例,但处理端没有去实现)和服务的部分由于时间匆忙暂未实施,不过理论上一定是可行的。
完整的钩子模块代码:
[文件: hookcore.h]
#pragma once
#ifdef SHELLMSGHOOK_EXPORTS
#define DLL_EXPORT __declspec(dllexport)
#else
#define DLL_EXPORT __declspec(dllimport)
#endif
enum NotifyMsg
{
WM_NotifyActivate = WM_APP + 1,
WM_NotifyFocus,
WM_NotifyCallWndProc,
WM_NotifyGetMessage,
};
// x64 结构体的声明
typedef struct _TRAY_ICON_DATAW {
DWORD Signature;
DWORD dwMessage; // dwMessage <-- Shell_NotifyIconW(DWORD dwMessage, ...)
DWORD cbSize;
DWORD hWnd;
UINT uID;
UINT uFlags;
UINT uCallbackMessage;
DWORD uIconID; // HICON hIcon; why it changes?
#if (NTDDI_VERSION < NTDDI_WIN2K)
WCHAR szTip[64];
#endif
#if (NTDDI_VERSION >= NTDDI_WIN2K)
WCHAR szTip[128];
DWORD dwState;
DWORD dwStateMask;
WCHAR szInfo[256];
#ifndef _SHELL_EXPORTS_INTERNALAPI_H_
union {
UINT uTimeout;
UINT uVersion; // used with NIM_SETVERSION, values 0, 3 and 4
} DUMMYUNIONNAME;
#endif
WCHAR szInfoTitle[64];
DWORD dwInfoFlags;
#endif
#if (NTDDI_VERSION >= NTDDI_WINXP)
GUID guidItem;
#endif
#if (NTDDI_VERSION >= NTDDI_VISTA)
HICON hBalloonIcon;
#endif
} TRAY_ICON_DATAW, * PTRAY_ICON_DATAW;
typedef struct __SHELLWND_MAG
{
LPARAM lParam;
WPARAM wParam;
UINT message;
HWND hMsgWnd;
}SHELLWND_MAG, PSHELLWND_MAG;
#ifdef __cplusplus
extern "C"
{
#endif
DLL_EXPORT bool InstallCBTHook(HWND hNotifyWnd);
DLL_EXPORT bool UninstallCBTHook();
DLL_EXPORT bool InstallCallWndProcHook(HWND hNotifyWnd, HWND hCaptureWnd);
DLL_EXPORT bool UninstallCallWndProcHook();
DLL_EXPORT bool InstallGetMessageHook(HWND hNotifyWnd, HWND hCaptureWnd);
DLL_EXPORT bool UninstallGetMessageHook();
#ifdef __cplusplus
}
#endif
[文件: dllmain.cpp]
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
//#include <windows.h>
#include <CommCtrl.h>
#include <commdlg.h>
#include <Richedit.h>
#include <Ime.h>
#include <shellapi.h>
#include <dde.h>
#include <stdio.h>
#include <map>
#include "hookcore.h"
//Initialized Data to be shared with all instance of the dll
#pragma data_seg("Shared")
HWND g_hNotifyWnd = NULL;
HWND g_hCaptureWnd = NULL;
HINSTANCE g_hInstance = NULL;
HHOOK g_hCBTHook = NULL;
HHOOK g_hCallWndProcHook = NULL;
HHOOK g_hGetMessageHook = NULL;
#pragma data_seg()
// Initialised data End of data share
#pragma comment(linker,"/section:Shared,RWS")
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
g_hInstance = hModule;
break;
}
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
#ifdef __cplusplus
extern "C"
{
#endif
static LRESULT CALLBACK CBTProc(int nCode, WPARAM wParam, LPARAM lParam)
{
if (nCode >= 0)
{
if (nCode == HCBT_ACTIVATE) //Called when the application window is activated
{
::PostMessage(g_hNotifyWnd, WM_NotifyActivate, wParam, NULL);
}
else if (nCode == HCBT_SETFOCUS)
{
::PostMessage(g_hNotifyWnd, WM_NotifyFocus, wParam, NULL);
}
else if (nCode == HCBT_DESTROYWND) //Called when the application window is destroyed
{
}
}
return CallNextHookEx(g_hCBTHook, nCode, wParam, lParam);
}
DLL_EXPORT bool InstallCBTHook(HWND hNotifyWnd)
{
g_hNotifyWnd = hNotifyWnd;
if (!g_hCBTHook)
{
g_hCBTHook = SetWindowsHookEx(WH_CBT, (HOOKPROC)CBTProc, g_hInstance, 0);
if (g_hCBTHook)
{
OutputDebugStringA("Hook CBT succeed\n");
return true;
}
else
{
DWORD dwError = GetLastError();
char szError[MAX_PATH];
_snprintf_s(szError, MAX_PATH, "Hook CBT failed, error = %u\n", dwError);
OutputDebugStringA(szError);
}
}
return false;
}
DLL_EXPORT bool UninstallCBTHook()
{
if (g_hCBTHook)
{
UnhookWindowsHookEx(g_hCBTHook);
g_hCBTHook = NULL;
OutputDebugStringA("Uninstall CBT Hook\n");
}
return true;
}
//note:
//CallWndProc will be executed in the process which myhook.dll injected, not the MySpy process
static LRESULT CALLBACK CallWndProc(int code, WPARAM wParam, LPARAM lParam)
{
if (code == HC_ACTION)
{
PCWPSTRUCT pMsg = (PCWPSTRUCT)lParam;
if (pMsg->hwnd == g_hCaptureWnd && pMsg->message == WM_COPYDATA)
{
PTRAY_ICON_DATAW lpTrayData = nullptr;
COPYDATASTRUCT* lpShellCDS =
(COPYDATASTRUCT*)pMsg->lParam;
COPYDATASTRUCT lpSevCDS = { 0 };
DWORD_PTR lpdwResult = 0;
if (lpShellCDS->dwData == 1) // 判断是否是 Shell_NotifyIcon 调用
{
lpTrayData = (TRAY_ICON_DATAW*)lpShellCDS->lpData;
if (lpTrayData->Signature == 0x34753423) // 判断是否是 NOTIFYICONDATA 结构体封送过程
{
// 填充 COPYDATASTRUCT 结构体
lpSevCDS.dwData = WM_NotifyCallWndProc;
lpSevCDS.cbData = sizeof(TRAY_ICON_DATAW);
lpSevCDS.lpData = lpTrayData;
// 发送消息到我们自己的窗口
SendMessageTimeoutW(g_hNotifyWnd,
WM_COPYDATA, (WPARAM)pMsg->wParam,
(LPARAM)&lpSevCDS, SMTO_NOTIMEOUTIFNOTHUNG, 0x1B58, &lpdwResult);
}
}
//char szBuf[MAX_PATH] = {0};
//_snprintf_s(szBuf, MAX_PATH, "CallWndProc Handle: 0x%08X SendMsg: %s(%04X), wParam: %08X, lParam: %08X\n",
// pMsg->hwnd, GetMsgStringA(pMsg->message), pMsg->message, (int)pMsg->wParam, (int)pMsg->lParam);
//OutputDebugStringA(szBuf);
}
}
return CallNextHookEx(g_hCallWndProcHook, code, wParam, lParam);
}
DLL_EXPORT bool InstallCallWndProcHook(HWND hNotifyWnd, HWND hCaptureWnd)
{
g_hNotifyWnd = hNotifyWnd;
g_hCaptureWnd = hCaptureWnd;
if (!g_hCallWndProcHook)
{
DWORD dwThreadId = ::GetWindowThreadProcessId(g_hCaptureWnd, NULL);
g_hCallWndProcHook = SetWindowsHookEx(WH_CALLWNDPROC, (HOOKPROC)CallWndProc, g_hInstance, dwThreadId);
if (g_hCallWndProcHook)
{
OutputDebugStringA("Hook CallWndProc succeed\n");
return true;
}
else
{
DWORD dwError = GetLastError();
char szError[MAX_PATH];
_snprintf_s(szError, MAX_PATH, "Hook CallWndProc failed, error = %u\n", dwError);
OutputDebugStringA(szError);
}
}
return false;
}
DLL_EXPORT bool UninstallCallWndProcHook()
{
if (g_hCallWndProcHook)
{
UnhookWindowsHookEx(g_hCallWndProcHook);
g_hCallWndProcHook = NULL;
OutputDebugStringA("Uninstall CallWndProc Hook\n");
}
return true;
}
//note:
//CallWndProc will be executed in the process which myhook.dll injected, not the MySpy process
static LRESULT CALLBACK GetMessageProc(int code, WPARAM wParam, LPARAM lParam)
{
if (code == HC_ACTION)
{
PCWPSTRUCT pMsg = (PCWPSTRUCT)lParam;
SHELLWND_MAG lpTrayData = { 0 };
COPYDATASTRUCT lpSevCDS = { 0 };
DWORD_PTR lpdwResult = 0;
lpTrayData.hMsgWnd = pMsg->hwnd;
lpTrayData.message = pMsg->message;
lpTrayData.wParam = pMsg->wParam;
lpTrayData.lParam = pMsg->lParam;
// 填充 COPYDATASTRUCT 结构体
lpSevCDS.dwData = WM_NotifyGetMessage;
lpSevCDS.cbData = sizeof(SHELLWND_MAG);
lpSevCDS.lpData = &lpTrayData;
SendMessageTimeoutW(g_hNotifyWnd,
WM_COPYDATA, (WPARAM)wParam,
(LPARAM)&lpSevCDS, SMTO_NOTIMEOUTIFNOTHUNG, 0x1B58, &lpdwResult);
//char szBuf[MAX_PATH] = {0};
//_snprintf_s(szBuf, MAX_PATH, "GetMessage Handle: 0x%08X PostMsg: %s(%04X), wParam: %08X, lParam: %08X\n",
// pMsg->hwnd, GetMsgStringA(pMsg->message), pMsg->message, (int)pMsg->wParam, (int)pMsg->lParam);
//OutputDebugStringA(szBuf);
}
return CallNextHookEx(g_hGetMessageHook, code, wParam, lParam);
}
DLL_EXPORT bool InstallGetMessageHook(HWND hNotifyWnd, HWND hCaptureWnd)
{
g_hNotifyWnd = hNotifyWnd;
g_hCaptureWnd = hCaptureWnd;
if (!g_hGetMessageHook)
{
DWORD dwThreadId = ::GetWindowThreadProcessId(g_hCaptureWnd, NULL);
g_hGetMessageHook = SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)GetMessageProc, g_hInstance, dwThreadId);
if (g_hGetMessageHook)
{
OutputDebugStringA("Hook GetMessage succeed\n");
return true;
}
else
{
DWORD dwError = GetLastError();
char szError[MAX_PATH];
_snprintf_s(szError, MAX_PATH, "Hook GetMessage failed, error = %u\n", dwError);
OutputDebugStringA(szError);
}
}
return false;
}
DLL_EXPORT bool UninstallGetMessageHook()
{
if (g_hGetMessageHook)
{
UnhookWindowsHookEx(g_hGetMessageHook);
g_hGetMessageHook = NULL;
OutputDebugStringA("Uninstall GetMessage Hook\n");
}
return true;
}
}
完整的模块注入和消息接收端代码:
// ConsoleShowMsg.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <windows.h>
#include <iostream>
#include "../ShellMsgHook/hookcore.h" // 钩子模块的头文件
// 钩子模块的 lib 文件
#if (NDEBUG) && (_WIN64)
#pragma comment(lib, "../x64/Release/ShellMsgHook.lib")
#else
#pragma comment(lib, "../x64/Debug/ShellMsgHook.lib")
#endif // NDEBUG
// 一些宏定义
#define NIM_ADD 0x00000000
#define NIM_MODIFY 0x00000001
#define NIM_DELETE 0x00000002
#define NIM_SETFOCUS 0x00000003
#define NIM_SETVERSION 0x00000004
#define NIF_TIP 0x00000004
#define NIF_INFO 0x00000010
// Notify Icon Infotip flags
#define NIIF_NONE 0x00000000
#define NIIF_INFO 0x00000001
#define NIIF_WARNING 0x00000002
#define NIIF_ERROR 0x00000003
#define NIIF_USER 0x00000004
#define NIIF_ICON_MASK 0x0000000F
#define NIIF_NOSOUND 0x00000010
#define NIIF_LARGE_ICON 0x00000020
#define NIIF_RESPECT_QUIET_TIME 0x00000080
// 格式化输出相关函数的声明
std::wstring make_hwnd_text(HWND hwnd);
std::wstring make_snmsg_text(DWORD dwMessage);
std::wstring make_infoflag_text(DWORD dwInfoFlags);
// 方便于调用格式化输出函数
#define HWND2TEXT(hwnd) make_hwnd_text(hwnd).c_str()
#define DMSG2TEXT(dwMessage) make_snmsg_text(dwMessage).c_str()
#define NIIF2TEXT(dwInfoFlags) make_infoflag_text(dwInfoFlags).c_str()
// 其他函数的声明
BOOL OnInstallHookNotifyWndProc(HWND hDlgWnd);
void OnUninstallHookNotifyWndProc();
LRESULT CALLBACK WindowProc(
HWND hwnd, UINT uMsg,
WPARAM wParam, LPARAM lParam);
int main() {
_wsetlocale(LC_ALL, L".UTF8"); // 设置代码页以支持中文
SetConsoleTitleW(L"ConsoleShowNofifyMsg");
// 创建窗口类
WNDCLASS wc = {};
wc.lpfnWndProc = WindowProc;
wc.hInstance = GetModuleHandle(NULL);
wc.lpszClassName = L"ShellMsgGetMessageWindowClass";
RegisterClass(&wc);
// 创建窗口
HWND hwnd = CreateWindowExW(
WS_EX_LAYERED |
WS_EX_TRANSPARENT |
WS_EX_TOOLWINDOW,
wc.lpszClassName,
L"ShellMsgGetMessageWindow",
0, 0, 0,
0, 0,
NULL, NULL,
GetModuleHandle(NULL),
NULL);
if (hwnd == NULL) {
std::cerr << "Failed to create window." << std::endl;
return 1;
}
// 设置窗口透明度
SetLayeredWindowAttributes(hwnd, RGB(0, 0, 0), 1, LWA_ALPHA);
// 显示窗口
ShowWindow(hwnd, SW_SHOWDEFAULT);
// 启用窗口过程钩子
if (!OnInstallHookNotifyWndProc(hwnd))
{
CloseWindow(hwnd);
return -1;
}
// 消息循环
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
// 消息窗口过程函数
LRESULT CALLBACK WindowProc(
HWND hwnd, UINT uMsg,
WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_DESTROY:
OnUninstallHookNotifyWndProc();
PostQuitMessage(0);
return 0;
case WM_COPYDATA:
{
COPYDATASTRUCT* lpReceiveCDS = (COPYDATASTRUCT*)lParam;
PTRAY_ICON_DATAW lpNotifyData = nullptr;
if (lpReceiveCDS == nullptr ||
IsBadReadPtr(lpReceiveCDS, sizeof(TRAY_ICON_DATAW)) == TRUE)
{
break;
}
// 测试时,只实现了 WM_NotifyCallWndProc 钩子的消息处理
if (lpReceiveCDS->dwData == WM_NotifyCallWndProc
&& lpReceiveCDS->cbData == sizeof(TRAY_ICON_DATAW))
{
lpNotifyData = (TRAY_ICON_DATAW*)lpReceiveCDS->lpData;
// 输出结果
wprintf(L"CTray-NotifyIconMsg:[%ws]:[%ws];\n",
DMSG2TEXT(lpNotifyData->dwMessage), HWND2TEXT((HWND)wParam));
if ((lpNotifyData->uFlags & NIF_INFO) != 0)
{
wprintf(L"Tip[%ws], szInfoParam:\n",
lpNotifyData->szTip);
wprintf(L"InfoTitle[%ws], Info[%ws], InfoFlags[%ws];\n",
lpNotifyData->szInfoTitle, lpNotifyData->szInfo,
NIIF2TEXT(lpNotifyData->dwInfoFlags));
}
else if ((lpNotifyData->uFlags & NIF_TIP) != 0)
{
wprintf(L"Tip[%ws], non-szInfo;\n", lpNotifyData->szTip);
}
else {
wprintf(L"non-szTip, non-szInfo;\n");
}
}
break;
}
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
// 安装 Win32 钩子
BOOL OnInstallHookNotifyWndProc(HWND hDlgWnd)
{
bool m_bCallWndProcHooked = false;
HWND hShellWnd = ::FindWindow(TEXT("Shell_TrayWnd"), nullptr);
if (!hShellWnd || !::IsWindow(hShellWnd))
{
MessageBox(NULL, TEXT("Not found Shell_TrayWnd."),
TEXT("No!!!"), MB_OK | MB_ICONWARNING);
return FALSE;
}
m_bCallWndProcHooked = InstallCallWndProcHook(hDlgWnd, hShellWnd);
if (m_bCallWndProcHooked)
{
wprintf(L"Hook CallWndProc succeed\r\n");
return TRUE;
}
else
{
wprintf(L"Hook CallWndProc failed\r\n");
return FALSE;
}
}
// 卸载 Win32 钩子
void OnUninstallHookNotifyWndProc()
{
UninstallCallWndProcHook();
wprintf(L"Unhook CallWndProc\r\n");
}
// 窗口句柄转换字符串的函数
std::wstring make_hwnd_text(HWND hwnd)
{
wchar_t buf[25];
wsprintfW(buf, L"HWND:0x%I64X", (UINT64)hwnd);
return buf;
}
// dwMessage 参数转换为已知参数字符串
std::wstring make_snmsg_text(DWORD dwMessage)
{
#define CHECK_DMSG(dwMessage, var) if (dwMessage == var) return L#var;
CHECK_DMSG(dwMessage, NIM_ADD);
CHECK_DMSG(dwMessage, NIM_MODIFY);
CHECK_DMSG(dwMessage, NIM_DELETE);
CHECK_DMSG(dwMessage, NIM_SETFOCUS);
CHECK_DMSG(dwMessage, NIM_SETVERSION);
wchar_t buf[25];
wsprintfW(buf, L"Message:%u", dwMessage);
return buf;
#undef CHECK_HWND
}
// dwInfoFlags 参数转换为已知参数字符串
std::wstring make_infoflag_text(DWORD dwInfoFlags)
{
#define CHECK_DMSG(dwInfoFlags, var) if (dwInfoFlags == var) return L#var;
CHECK_DMSG(dwInfoFlags, NIIF_NONE);
CHECK_DMSG(dwInfoFlags, NIIF_INFO);
CHECK_DMSG(dwInfoFlags, NIIF_WARNING);
CHECK_DMSG(dwInfoFlags, NIIF_ERROR);
CHECK_DMSG(dwInfoFlags, NIIF_USER);
CHECK_DMSG(dwInfoFlags, NIIF_ICON_MASK);
CHECK_DMSG(dwInfoFlags, NIIF_NOSOUND);
CHECK_DMSG(dwInfoFlags, NIIF_LARGE_ICON);
CHECK_DMSG(dwInfoFlags, NIIF_RESPECT_QUIET_TIME);
wchar_t buf[25];
wsprintfW(buf, L"Message:%u", dwInfoFlags);
return buf;
#undef CHECK_HWND
}
格式化文本的方式其实和《最新方案(一)》里面的类似。
五、总结&更新
本文通过微软提供的 SetWindowsHookEx 钩子注入方法,使用 WH_CALLWNDPROC 等转发 Shell_TrayWnd 窗口的 WM_COPYDATA 消息到我们自定义的窗口,并在我们的窗口中对消息进行进一步的处理。这是对《最新方案(一)》中的方法做出的补充,如果你有更好的方案,欢迎在系列文章的评论区中交流。
本文属于原创文章,转载请注明出处:
https://blog.csdn.net/qq_59075481/article/details/136240462。
文章发布于:2024.02.22;更新于:2024.02.22。