目录
8.1 截获 WM_COPYDATA 消息(HOOK 途径)
8.3 通过 XAML Diagnostics 和 IsLands 访问界面元素(Hook Xaml 途径)
文章出处链接:[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 函数相比,将更加易于实现。
【注明】
(1)请注意,本系列所提的部分方法仅适用于 Win11 22H2 22621.1344 (KB5022913) 以及更高版本(除 WM_COPYDATA 兼容早期系统以外)。对于早期系统版本,请参考网络上公开的方法。
(2)目前该系列分析的对 WM_COPYDATA 机制进行处理的方法依然适用于 Win11 22H2、 23H2 以及测试渠道的 24H2。(检测时间:2024.03.04, 2024.05.10, 2024.08.23)
(3)通知图标的隐藏可以通过 XAML 方式解决,涉及到 XAML 诊断相关技术(不详细解释了),就是把元素换个区域;
(4)
未来将更新窗口子类化代码,使得可以通过系统接口管理托盘图标状态或者拦截通知等。(2024.07.10)此文自 2025.01.01 最后一次更新以来,考虑到自己没有时间更新这部分的内容。所以,文章将暂时进入封锁阶段,且不再及时更新。
系列文章列表:
编号 | 文章标题 | AID |
1 | 128594435 | |
2 | 获取 Windows 系统托盘图标信息的最新方案(一) | 136134195 |
3 | 获取 Windows 系统托盘图标信息的最新方案(二) | 136199670 |
4 | 获取 Windows 系统托盘图标信息的最新方案(三) | 136240462 |
5 | 对通知区域(Win 系统托盘)窗口层次的分析 | 128538050 |
一、原理解释
应用程序通过 Shell_NotifyIcon 函数向系统通知区域注册“通知图标”。 Shell_NotifyIcon 函数仅仅是将 NOTIFYICONDATA 结构封装到 COPYDATASTRUCT (CDS) 中,通过 WM_COPYDATA 消息向 explorer.exe 共享缓冲区的信息。此缓冲区传递的结构包括了 NOTIFYICONDATA 结构包含的全部信息、要处理的图标状态更改(Shell_NotifyIcon 函数的 dwMessage 参数)以及调用方的一部分信息(调用方窗口句柄、进程完整路径等)。 NOTIFYICONDATA 结构是通知图标的相关信息。
当了解到这些情况后,我们还可以通过 Spy++ 工具知道通知区域处理此类 WM_COPYDATA 消息的窗口是“Shell_TrayWnd”。所有这些,在第一篇中已经通过逆向工程和相关文献被分析出来。
为了便于获取所传递结构的成员,我解析了 UNICODE 版本的结构:
// 结构体的声明(UNICODE 字符集)
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;
虽然此内部结构存在 UNICODE 和 ASCII 两种版本,但是当使用 ASCII 版本时,内部会进行隐式转换,发送时的消息结构还是 UNICODE 编码的。

结构的第一个成员是一个哨兵值(签名),因为 WM_COPYDATA 可能传递多种不同结构,而为了便于接收消息的 explorer 程序能够正确处理不同的情况,内部采用了签名来验证消息的格式。
而传递通知图标结构体信息时的签名信息为 0x34753423,也就是说,接收方会检查该值是否是 0x34753423 来决定是否调用相应的处理。
而结构的第二个成员则是和 Shell_NotifyIcon 函数绑定的参数 dwMessage,该参数指定了需要对通知图标进行的操作。这里有几个不同的操作,包含创建图标、修改图标、删除图标等,只需要指定 Shell_NotifyIcon 的文档所规定的常量。
结构接下来的成员则基本和 NOTIFYICONDATA 结构相对应,只不过对 hWnd 和 hIcon 成员有所变化,他们转为了由 DWORD 值存储。
微软提供的 Win32 WindowsHook 可以实现在消息到达目标窗口前/后进行捕获,其中CallWndProcHook 就可以在消息被窗口处理前捕获(但是无法修改或者阻止消息向目标窗口过程传递),而 GetMessageHook 只可以捕获通过 GetMessage 或者 PeekMessage 接收到窗口的消息,无法获取直接派送到窗口过程的消息,比如这里的 WM_COPYDAYA 就没法捕获。同时, GetMessageHook 不能在消息发送至窗口过程前获取消息和修改消息传递。
通过创建一个 CallWndProcHook 钩子例程,并利用 WM_COPYDATA 将发送至 Shell_TrayWnd 窗口的 WM_COPYDATA 转发至我们创建窗口。这将允许我们获取通知区域图标的注册或者状态更改信息。
在这里,我们需要注意一点,使用 SendMessage(严禁使用,除非你知道你在做什么) 和 SendMessageTimeout 转发消息均可能因阻滞导致一系列问题,尤其是它们配合 CallWndProcHook 使用时,必须考虑清楚需要为 SMTO 设置的超时时间间隔(本文代码默认设置的是 10 秒)。如果为了安全起见,比如你只需要获取消息,而不需要改变消息的处理模式,则推荐使用 GetMessageHook 获取消息并配合 PostMessage 转发消息。
完整工具程序(TrayNotifyCapturer)流程:获取 Shell_TrayWnd 窗口句柄和线程ID,利用窗口子类化,在任意应用程序调用 Shell_NotifyIcon 或者 Shell_NotifyIconGetRect 时,会向 Shell_TrayWnd 窗口发送 COPYDATASTRUCT(CDS) 消息,该消息在到达 Shell_TrayWnd 窗口前被我们的 HOOK 获取,当然除了这两个函数会发送 CDS 消息外,其他函数也可能发送 CDS 消息,而这些 CDS 消息的签名和结构体大小不同,由此可以进行过滤获以便于取我们需要的消息包。此外,程序窗口还可能发送其他非 CDS 消息,对于非 CDS 消息我们不做处理,要让他们通过并继续发送至 Shell_TrayWnd 窗口。从需要的 CDS 消息中可以获取通知图标信息和控制权,通过修改信息就可以实现对通知图标的修改。
通过 CallWndProc Hook 实现的消息转发程序的流程图如下图所示:
通过窗口子类化实现的完全拦截消息程序的流程图如下图所示:
提示:如果需要拦截或者修改消息,则需要将 CallWndProc Hook 替换为窗口子类化(未来实现)或者窗口过程挂钩(第一篇讲过),因为 CallWndProc Hook 仅能够有限地操作托盘图标的消息(如获取信息和模拟消息等,但不能拦截活动消息)!!!
07/10 更新——窗口子类化示例:
https://blog.csdn.net/qq_59075481/article/details/140334106.
二、实现 CallWndProcHook
SetWindowsHookEx 提供了多种钩子类型。 WH_GETMESSAGE 或者 WH_CALLWNDPROC 类钩子都可以实现对窗口消息的获取。但它们有所不同,使用 WH_CALLWNDPROC 钩子后,消息在到达处理消息的窗口过程之前就被修改后的窗口过程处理,这类似于窗口子类化。
为了防止消息阻滞,使用 SendMessageTimeout 发送消息给我们自定义的消息处理窗口时,必须合理设置超时时间。
使用静态函数 GetMessageProc 配合 WH_GETMESSAGE 类钩子,可以拦截通过 GetMessage 和 PeekMessage 消息循环获取的窗口消息,但是无法获取直接发送到窗口过程的系统消息和用户消息。需要注意的是,这些捕获的消息不能够在消息被原始窗口处理前进行处理。
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, 0x0A, &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 等特殊消息结构。
在 HOOK 窗口过程或者消息时,拦截到的 wParam 是调用方窗口句柄,可以转为 DWORD 值或 HWND。lParam 则可能指向 CDS 结构体,这里需要检查 uMsg (message) 是否是 WM_COPYDATA。除此之外,还可以检查签名信息来进一步确认是否是传递通知图标信息的结构体。
通过转发消息到 g_hNotifyWnd 窗口,我们就可以作为中间人处理消息。
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;
// 发送消息到我们自己的窗口(10 秒)
SendMessageTimeoutW(g_hNotifyWnd,
WM_COPYDATA, (WPARAM)pMsg->wParam,
(LPARAM)&lpSevCDS, SMTO_NOTIMEOUTIFNOTHUNG, 0x0A, &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 类钩子即可拦截窗口创建/销毁等状态消息(可以用于监视 explorer 相应的窗口是否创建)。
这个函数能比较多地获取窗口初始化前期的信息,可以在窗口创建的过程中拦截并获取创建窗口的结构信息。其中就包含调用 CreateWindowEx 创建窗口时的参数。
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);
}
三、安装钩子例程
提示: WH_CALLWNDPROC 只能获取消息但不能拦截或者修改消息,WH_GETMESSAGE 不能够获取到部分窗口的消息。如果要修改消息传递,正确的方法应该使用窗口子类化和 IATHOOK。
使用 SetWindowsHookEx 并指定第三个参数是与挂钩例程绑定的消息窗口的线程,必须指定要挂勾的窗口的线程,否则将尝试在所有接收消息的窗口线程安装消息钩子(第三个参数为 NULL 时)。
WH_CALLWNDPROC 钩子安装的方式如下所示。首先通过 GetWindowThreadProcessId 并根据 Shell_TrayWnd 窗口句柄获取对应窗口线程的 Id。然后使用 SetWindowsHookEx 安装钩子。
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);
}
程序编译完成后运行效果如下图所示:

在早期系统,如 Win 7 上运行效果:

五、完整代码和注意事项
注意事项:
(1) 该程序如果要实现获取全部图标的功能,必须在资源管理器创建窗口(推荐使用 WH_CBT 钩子)的时候就执行 WH_CALLWNDPROC 钩子对 WM_COPYDATA 消息进行转发处理。并把工具注册为系统开机自启动服务,这样就可以在 explorer 初始化时无缝衔接消息的处理过程。我给出的代码中 WH_CBT (钩子模块中有 CBT 挂钩的样例,但处理端没有去实现)和服务的部分由于时间匆忙暂未实施,不过理论上一定是可行的。
(2) 中文处理问题:程序面临着含中文字符的缓冲区处理问题,这个比较难解决。因为我一开始用的是 “_wsetlocale(LC_ALL, L".UTF8"); // 设置代码页以支持中文” 但是发现,在回溯到 Win8.1 虚拟机时,就出现了该语句无效的情况,会导致程序出错。我的解决方法就是改为 “_wsetlocale(LC_ALL, L"chs"); // 设置代码页以支持中文” 并且不使用 std::wcout 改为使用 std::cout 输出文本,类似地 sprintf,而不用 wsprinf。至于最终怎么解决,就仁者见仁智者见智了。
完整的钩子模块代码:
[文件: 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;
// 发送消息到我们自己的窗口(10 秒)
SendMessageTimeoutW(g_hNotifyWnd,
WM_COPYDATA, (WPARAM)pMsg->wParam,
(LPARAM)&lpSevCDS, SMTO_NOTIMEOUTIFNOTHUNG, 0x0A, &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, 0x0A, &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() {
setlocale(LC_ALL, "zh-CN"); // 设置代码页以支持中文,原本用的是 _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
}
格式化文本的方式其实和《最新方案(一)》里面的类似。
六、判断指定图标是否正在 “闪烁”
(本节于 2024/05/04 补充)
图标状态的更改包括图标创建、修改和删除,这和 dwMessage 有关。
dwMessage 支持的数值 | 表示的功能 |
NIM_ADD (0x00000000) | 将图标添加到状态区域。 |
NIM_MODIFY (0x00000001) | 修改状态区域中的图标。 |
NIM_DELETE (0x00000002) | 从状态区域中删除图标。 |
NIM_SETFOCUS (0x00000003) | 将焦点返回到任务栏通知区域。 |
NIM_SETVERSION (0x00000004) | 指示通知区域按照 lpdata 所指向结构的 uVersion 成员中指定的版本号的行为。 版本号指定可识别的成员。 |
大部分应用对他们的通知图标的管理都只能遵守这个规范。下面以 “图标闪烁” 为例解释一种中间状态。
经常使用一些通讯软件的人会知道,通知区域图标的 “闪烁” 意味着有新的消息或者来电。“闪烁”并不在上面列出的状态列表上面,那么,这个 “闪烁” 到底是如何实现的呢?
“闪烁” 是通过设置计时器,不断地调用 Shell_NotifyIcon 发送 NIM_MODIFY,在其中有一个巧妙的循环,实现 “闪烁” 效果。
下图展示了当 “微信” 有新消息时,该应用调用 Shell_NotifyIcon 函数并进行的动作。(NIM_ADD 和 NIM_SETVERSION 是初始化微信时候它注册通知图标的,后面的 NIM_MODIFY 才是发生即将闪烁时的动作)

消息结构中的 HICON hIcon(或者 DWORD uIconID)成员代表着通知图标的图像句柄。通过不断循环地将该成员置为 0 和有效值。利用人的视觉暂留机理,可以让观众感觉到图标在闪烁。

那么,检测这种状态就很容易了,通过分析 dwMessage 是否是 NIM_MODIFY 并且 uIconID 是否在 0 和非零值之间循环变化即可判断通知图标是否正在闪烁。

一般地,该方法对大多数应用都适用。
通讯软件以及音乐播放器:

截图软件:

由于要适配多种不同的应用环境或者操作系统版本问题,相关代码将在后期补充。
注:获取图标矩形、对应进程的文件路径等信息也将在后期补充。
七、模拟发送消息至托盘图标
(此部分于 2024/08/22 补充)
有一段时间没有在 CSDN 更新了,写这一部分是为了解答读者私信提问的问题。由于窗口子类化的部分我暂时还没有写好,API Hook 也懒得去重新跑挂钩的特征码。所以,就只从技术实现原理的角度去解释吧(只有简单的示例程序)。
首先,我们得了解系统是如何实现通知区域(托盘)中图标的鼠标或键盘响应的。每一个托盘图标在注册时,都需要绑定一个应用程序的窗口。explorer 通过应用程序定义消息标识符 uCallbackMessage 来发送消息到托盘图标绑定的应用程序窗口。根据微软说明,从 Windows 2000 (Shell32.dll 版本 5.0) 开始,如果将 lpdata 指向的 NOTIFYICONDATA 结构的 uVersion 成员设置为 NOTIFYICON_VERSION_4 或更高版本,Shell_NotifyIcon鼠标和键盘事件的处理方式与早期版本的 Windows 不同。
当 uVersion 成员为 0 或 NOTIFYICON_VERSION 时,消息的 wParam 参数包含发生事件的任务栏图标的标识符。 此标识符的长度可以是 32 位。 lParam 参数保存与事件关联的鼠标或键盘消息。 例如,当指针移动到任务栏图标上时, lParam 设置为 WM_MOUSEMOVE。
当 uVersion 成员 NOTIFYICON_VERSION_4 时,应用程序继续通过 uCallbackMessage 成员以应用程序定义消息的形式接收通知事件,但该消息的 lParam 和 wParam 参数的解释将更改如下:
- LOWORD (lParam) 包含通知事件,例如 NIN_BALLOONSHOW、NIN_POPUPOPEN 或 WM_CONTEXTMENU。
- HIWORD (lParam) 包含图标 ID。 图标 ID 的长度限制为 16 位。
- GET_X_LPARAM (wParam) 返回通知事件 NIN_POPUPOPEN、NIN_SELECT、NIN_KEYSELECT 以及 WM_MOUSEFIRST 和 WM_MOUSELAST 之间的所有鼠标消息的 X 定位点坐标。 如果其中任何消息是由键盘生成的, 则 wParam 将设置为目标图标的左上角。 对于所有其他消息, wParam 未定义。
- GET_Y_LPARAM (wParam) 返回为 X 定位点定义的通知事件和消息的 Y 定位点坐标。
- 如果用户使用键盘选择通知图标的快捷菜单,Shell 现在会向关联的应用程序发送 WM_CONTEXTMENU 消息。 早期版本发送 WM_RBUTTONDOWN 和 WM_RBUTTONUP 消息。
- 如果用户使用键盘选择通知图标并使用空格键或 ENTER 键激活它,则 5.0 版 Shell 会向关联的应用程序发送 NIN_KEYSELECT 通知。 早期版本发送 WM_RBUTTONDOWN 和 WM_RBUTTONUP 消息。
- 如果用户使用鼠标选择通知图标并使用 ENTER 键激活它,Shell 现在会向关联的应用程序发送 NIN_SELECT 通知。 早期版本发送 WM_RBUTTONDOWN 和 WM_RBUTTONUP 消息。
假设现在使用 NOTIFYICON_VERSION_4 或更高版本(低版本的处理比较麻烦,暂时不进行分析,可自行去研究),那么当我们知道托盘图标绑定的窗口时,就可以发送模拟消息给消息处理的窗口了,并且程序也应该会正常响应(具体处理是否成功取决于程序是否有模拟消息的检测)。
有人会问 “那你怎么知道托盘图标绑定的消息处理窗口呢?”,其实看懂我文章的读者应该就会知道程序绑定的窗口是 NOTIFYICONDATA 结构的 hWnd (窗口句柄)成员指向的窗口。多个图标可以关联到同一个窗口。通过解析所有的 ICONDATA 信息即可获取窗口句柄,解析的方法可以用过挂钩处理动态获取,也可以通过注册表的转存信息来获取,后者应该相对容易些。
示例程序是微软在 Github 上面公开的托盘消息处理程序的示例,我们对其进行稍微修改(修改窗口尺寸和启用高 DPI 支持)后进行测试。

它通过处理 uCallbackMessage 消息来响应一些操作:

在了解示例程序是如何响应托盘消息后,我们就可以尝试发送模拟消息了,比如下面的打开菜单(一般是右键菜单,如果有的话):
POINT pt = { 0 };
GetCursorPos(&pt);
PostMessageW(hTargetWnd, uCallbackMessage, MAKEWPARAM(pt.x, pt.y), WM_CONTEXTMENU);

对于左键消息,也是通过 LPARAM 传递原始消息的标识符。但我发现这里有一个有趣的现象,就是即使设置了 NOTIFYICON_VERSION_4,也会发送旧版本的消息。比如当左键单击发出时,先发送了 WM_LBUTTONDOWN 再发出 WM_LBUTTONUP ,最后发出一次 NIN_SELECT;左键双击时,先发出 WM_LBUTTONDBLCLK 再发出 WM_LBUTTONUP,最后发出一次 NIN_SELECT。

这样做也是很明显的,因为单靠一个 NIN_SELECT 不足以区分左键的具体操作是单击还是双击。
对于一些细节,我就不再赘述了,可以依照文档的说明自行研究,等我有时间了也会补充相关实现的。
此部分用到的示例程序源代码如下:
链接:https://pan.baidu.com/s/16Wgs8Hm5SqVpCpsJVTqWpA?pwd=iu7j
提取码:iu7j注解:微软原项目代码见:
目前只上传了本小节的项目代码,后期会看情况补充其他小节的项目文件的,也可能会开源到 github。
补充:如果想通过集中发送消息至系统托盘窗口,然后再由系统托盘窗口转发,实现将会比较复杂并且全网暂无更新的研究。不过我可以简单描述这里不同版本系统的窗口的差异:
在 22H2 的 1413 更新至 23H2 的最近更新(推断为 6 月中旬的更新)为止,Win11 系统使用窗口类名为 NotifyIconOverflowWindow 的全新窗口(子窗口为 ToolBar 窗口)来管理溢出区域的托盘图标信息(其他信息仍然由 Shell_TrayWnd 下面的几个窗口管理)。但是新的 23H2 和目前所有的 24H2 均更改为了使用 XAML 窗口(类名为 TopLevelWindowForOverflowXamlIsland 的窗口)的子窗口(标题 DesktopWindowXamlSource,类名 Windows.UI.Composition.DesktopWindowContentBridge)来收发溢出通知区域的消息。

目前能够在窗口激活的状态下模拟鼠标悬停在通知图标上。
测试代码:
#include <iostream>
#include <Windows.h>
// 模拟鼠标移动消息,不改变鼠标实际光标位置
void SimulateMouseMove(int x, int y) {
// 获取当前鼠标位置
POINT currentPos;
GetCursorPos(¤tPos);
// 计算需要发送的鼠标事件
INPUT input = { 0 };
input.type = INPUT_MOUSE;
input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE;
input.mi.dx = (x * 65535) / GetSystemMetrics(SM_CXSCREEN);
input.mi.dy = (y * 65535) / GetSystemMetrics(SM_CYSCREEN);
// 发送鼠标事件
SendInput(1, &input, sizeof(INPUT));
// 将鼠标恢复到原始位置
SetCursorPos(currentPos.x, currentPos.y);
}
// 模拟后台鼠标点击
void SimulateMouseClick(int x, int y) {
// 获取当前鼠标位置
POINT currentPos;
GetCursorPos(¤tPos);
// 将屏幕坐标转换为绝对值 (0-65535) 范围内的值
int absoluteX = (x * 65535) / GetSystemMetrics(SM_CXSCREEN);
int absoluteY = (y * 65535) / GetSystemMetrics(SM_CYSCREEN);
// 准备输入事件
INPUT inputs[3] = { 0 };
// 设置鼠标移动到目标位置(但不移动光标)
inputs[0].type = INPUT_MOUSE;
inputs[0].mi.dx = absoluteX;
inputs[0].mi.dy = absoluteY;
inputs[0].mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE;
// 设置鼠标左键按下事件
inputs[1].type = INPUT_MOUSE;
inputs[1].mi.dwFlags = MOUSEEVENTF_LEFTDOWN;
// 设置鼠标左键释放事件
inputs[2].type = INPUT_MOUSE;
inputs[2].mi.dwFlags = MOUSEEVENTF_LEFTUP;
// 发送输入事件
SendInput(3, inputs, sizeof(INPUT));
// 恢复鼠标位置
SetCursorPos(currentPos.x, currentPos.y);
}
// 打开任务栏溢出通知区域
void OpenOverflowNotificationArea() {
HWND hTrayWnd = FindWindowW(L"Shell_TrayWnd", NULL);
HWND hNotifyWnd = FindWindowExW(hTrayWnd, 0, L"TrayNotifyWnd", L"");
if (hTrayWnd && hNotifyWnd) {
// 找到溢出区域后,模拟鼠标点击事件,打开该区域
RECT rect;
GetWindowRect(hNotifyWnd, &rect);
int x = rect.left + 15; // 当任务栏停靠在屏幕下边缘时,根据通知窗口估计按钮位置
int y = rect.top + 25;
// 模拟鼠标点击,打开溢出区域
SimulateMouseClick(x, y);
std::cout << "Overflow notification area opened.\n";
}
else {
std::cout << "Failed to find overflow notification area.\n";
}
}
int main()
{
// 延时
Sleep(1000);
// 打开并激活溢出通知区域
OpenOverflowNotificationArea();
// 设置目标坐标 (1934, 1251) 或其他托盘图标位置
int targetX = 1900;
int targetY = 1251;
std::cout << "Simulating mouse move to (" << targetX << ", " << targetY << ") without changing actual cursor position...\n";
// 模拟鼠标移动到指定坐标并恢复光标位置
SimulateMouseMove(targetX, targetY);
std::cout << "Mouse move simulated.\n";
std::cout << "Done.\n";
system("pause");
return 0;
}
但是,目前此代码还存在缺陷,主要在激活窗口和发送消息上,目前测试发送 PostMessage 消息没有效果(有人说可能跟通知图标的版本有关系,内部有鼠标位置的检测,但我还没有确认)。

因为速度比较快,所以几乎看不到鼠标光标的位置移动,可以正常显示指定位置的悬停提示,当鼠标光标再次移动时提示才消失。
八、隐藏托盘图标方案(实验)
(此部分于 2024/05/13 补充)
在以前我们可以通过 TB_BUTTON 的 TBDATA 结构设置托盘图标的可见性状态,相关的工具有ShellTrayInfo(Shell Tray Info - CodeProject)。自从微软删除该接口后,我们没有找到替代的直接记录用于设置可见性。但是,我们有一些技巧可以实现 “准隐身” 效果,下面我将简单介绍我的思路。

8.1 截获 WM_COPYDATA 消息(HOOK 途径)
在 explorer 初始化时托管 Shell_TrayWnd 对 WM_COPYDATA 消息的处理,我们截获所有已经注册的通知图标的信息。在需要隐藏图标时候,我们调用 Shell_NotifyIcon 并构造 NOTIFYICONDATA 结构来删除图标。而在要显示某个托盘图标的时候,就重新注册图标。我观察到一款使用 Delphi 语言编写的工具(TrayControl: 点此下载)似乎正在通过接管 WM_COPYDATA 并使用未记录的消息结构来控制图标可见性,虽然实测在最新的 Win11 上无法工作,但是它的内部实现也许是对该任务有帮助的。具体内容有待逆向工程分析。

当然,也有相关讨论提到了该方法的可行性:

8.2 通过注册表隐藏(注册表途径)
Win 11 注册表支持对图标的隐藏和显示,图标信息在注册表有转储(具体运用代码见《最新方案(二)》)。
下面的注册表路径下,有各个图标的显示级别信息,IsPromoted 为 1 表示始终显示通知图标,而为 0 则表示图标隐藏在人字形托盘图标菜单 (System Tray Chevron) 中,猜测该名称由折叠按钮的形状得名。
[HKEY_CURRENT_USER\Control Panel\NotifyIconSettings]
任务栏角溢出通知区域会显示所有放在图标菜单列表中的通知图标。角溢出通知的注册表设置位于路径:
HKEY_CURRENT_USER\Software\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\TrayNotify
在该子键下的值项 SystemTrayChevronVisibility 控制图标菜单是否显示。当 SystemTrayChevronVisibility 为 1 时,显示角溢出按钮,当它为 0 时,立即隐藏角溢出通知区域。

该设置和系统的任务栏设置项对应:

其中 “隐藏的图标菜单” 设置对应 SystemTrayChevronVisibility 值项设置,下面的其它系统托盘图标设置开关和 IsPromoted 值项对应。
上面的两个注册表设置会立即生效而不需要重启资源管理器,原因是资源管理器注册了对该路径的监视,当注册表更改时会立即通知资源管理器。
但是微软提供的该方法对用户不友好,甚至我觉得很鸡肋,“隐藏的图标菜单” 会使得在角溢出通知栏里面的图标都被隐藏而无法查看。
此外,我在第一篇中说过,Shell_NotifyIconGetRect 函数能够获取托盘图标的坐标位置和尺寸,它内部使用一个未被记录的 CLSID:GUID_ShellNotifyChevron。

这个名字和注册表的 SystemTrayChevronVisibility 是不是很类似?我相信他们之间一定是有关联的。是否可以编写一个 Shell_NotifyIconSetRect 函数呢?
8.3 通过 XAML Diagnostics 和 IsLands 访问界面元素(Hook Xaml 途径)
在新版的系统上,包括任务栏在内的许多系统程序已经逐渐转移到 WinUI/UWP 框架。通过针对 XAML 诊断和 XAML 岛相关功能的利用技术进行实验研究,我们可以寻求一个在 Win10/11 系统大部分新版本上轻松操作界面元素的方法。
可扩展应用程序标记语言 (XAML) 是一种基于 XML 的声明性语言。XAML 广泛用于 WPF、UWP、Xamarin.Forms 等类型的应用程序以构建用户界面。
我在此需要感谢一些研究者的研究[m417z,ExplorerPatcher,TranslucentTB]。通过他们的文章、项目,我们了解到如何操作UI。 InitializeXamlDiagnosticsEx 函数允许应用诊断调试 WinUI 和 UWP,这也是 VS 调试器和其他一些 Spy 工具所采取的策略。根据下面的代码动态调用此函数。
typedef HRESULT(*InitializeXamlDiagnosticsExProto)(
_In_ LPCWSTR endPointName, _In_ DWORD pid,
_In_opt_ LPCWSTR wszDllXamlDiagnostics,
_In_ LPCWSTR wszTAPDllName,
_In_opt_ CLSID tapClsid,
_In_ LPCWSTR wszInitializationData);
InitializeXamlDiagnosticsExProto InitializeXamlDiagnosticsExFn =
(InitializeXamlDiagnosticsExProto) GetProcAddress(LoadLibraryEx(L"Windows.UI.Xaml.dll",
NULL, LOAD_LIBRARY_SEARCH_SYSTEM32),
"InitializeXamlDiagnosticsEx");
下面是 m417z 提供的 xaml 诊断 Demo 代码,示例代码监视 VisualTree 的变化,并生成日志。
// ==WindhawkMod==
// @id xaml-visual-tree-monitoring-demo
// @name XAML Visual Tree Monitoring Demo
// @description A demo mod which uses XAML diagnostics to monitor for visual tree changes
// @version 0.1
// @author m417z
// @github https://github.com/m417z
// @twitter https://twitter.com/m417z
// @homepage https://m417z.com/
// @include explorer.exe
// @architecture x86-64
// @compilerOptions -lole32 -loleaut32 -Wl,--export-all-symbols
// ==/WindhawkMod==
// ==WindhawkModReadme==
/*
# XAML Visual Tree Monitoring Demo
The code is mostly based on the
[ExplorerTAP](https://github.com/TranslucentTB/TranslucentTB/tree/develop/ExplorerTAP)
code from the **TranslucentTB** project. Parts of the following commit were
reverted: [Implement COM remoting in
ExplorerTAP](https://github.com/TranslucentTB/TranslucentTB/commit/7b44ef431a19c4157854da69b36a6acb35369edd)
Some code is borrowed from MSVC generated headers. To reduce the noise, hide the
relevant `#pragma region` regions in your editor.
*/
// ==/WindhawkModReadme==
// Source code is published under The GNU General Public License v3.0.
bool g_disabled = false;
#undef GetCurrentTime
#pragma region winrt_hpp
#include <guiddef.h>
#include <Unknwn.h>
#include <winrt/base.h>
// forward declare namespaces we alias
namespace winrt {
namespace Microsoft::UI::Xaml::Controls {}
namespace TranslucentTB::Xaml::Models::Primitives {}
namespace Windows {
namespace Foundation::Collections {}
namespace UI::Xaml {
namespace Controls {}
namespace Hosting {}
}
}
}
// alias some long namespaces for convenience
// namespace mux = winrt::Microsoft::UI::Xaml;
// namespace muxc = winrt::Microsoft::UI::Xaml::Controls;
// namespace txmp = winrt::TranslucentTB::Xaml::Models::Primitives;
namespace wf = winrt::Windows::Foundation;
// namespace wfc = wf::Collections;
namespace wux = winrt::Windows::UI::Xaml;
// namespace wuxc = wux::Controls;
namespace wuxh = wux::Hosting;
#pragma endregion // winrt_hpp
#pragma region xamlOM_h
/* this ALWAYS GENERATED file contains the definitions for the interfaces */
/* File created by MIDL compiler version 8.01.0622 */
/* @@MIDL_FILE_HEADING( ) */
/* verify that the <rpcndr.h> version is high enough to compile this file*/
#ifndef __REQUIRED_RPCNDR_H_VERSION__
#define __REQUIRED_RPCNDR_H_VERSION__ 500
#endif
/* verify that the <rpcsal.h> version is high enough to compile this file*/
#ifndef __REQUIRED_RPCSAL_H_VERSION__
#define __REQUIRED_RPCSAL_H_VERSION__ 100
#endif
#include <rpc.h>
#include <rpcndr.h>
#ifndef __RPCNDR_H_VERSION__
#error this stub requires an updated version of <rpcndr.h>
#endif /* __RPCNDR_H_VERSION__ */
#ifndef COM_NO_WINDOWS_H
#include <windows.h>
#include <ole2.h>
#endif /*COM_NO_WINDOWS_H*/
#ifndef __xamlom_h__
#define __xamlom_h__
#if defined(_MSC_VER) && (_MSC_VER >= 1020)
// #pragma once
#endif
/* Forward Declarations */
#ifndef __IVisualTreeServiceCallback_FWD_DEFINED__
#define __IVisualTreeServiceCallback_FWD_DEFINED__
typedef interface IVisualTreeServiceCallback IVisualTreeServiceCallback;
#endif /* __IVisualTreeServiceCallback_FWD_DEFINED__ */
#ifndef __IVisualTreeServiceCallback2_FWD_DEFINED__
#define __IVisualTreeServiceCallback2_FWD_DEFINED__
typedef interface IVisualTreeServiceCallback2 IVisualTreeServiceCallback2;
#endif /* __IVisualTreeServiceCallback2_FWD_DEFINED__ */
#ifndef __IVisualTreeService_FWD_DEFINED__
#define __IVisualTreeService_FWD_DEFINED__
typedef interface IVisualTreeService IVisualTreeService;
#endif /* __IVisualTreeService_FWD_DEFINED__ */
#ifndef __IXamlDiagnostics_FWD_DEFINED__
#define __IXamlDiagnostics_FWD_DEFINED__
typedef interface IXamlDiagnostics IXamlDiagnostics;
#endif /* __IXamlDiagnostics_FWD_DEFINED__ */
#ifndef __IBitmapData_FWD_DEFINED__
#define __IBitmapData_FWD_DEFINED__
typedef interface IBitmapData IBitmapData;
#endif /* __IBitmapData_FWD_DEFINED__ */
#ifndef __IVisualTreeService2_FWD_DEFINED__
#define __IVisualTreeService2_FWD_DEFINED__
typedef interface IVisualTreeService2 IVisualTreeService2;
#endif /* __IVisualTreeService2_FWD_DEFINED__ */
#ifndef __IVisualTreeService3_FWD_DEFINED__
#define __IVisualTreeService3_FWD_DEFINED__
typedef interface IVisualTreeService3 IVisualTreeService3;
#endif /* __IVisualTreeService3_FWD_DEFINED__ */
/* header files for imported files */
#include <oaidl.h>
#include <ocidl.h>
#include <inspectable.h>
#include <dxgi1_2.h>
#ifdef __cplusplus
extern "C"{
#endif
/* interface __MIDL_itf_xamlom_0000_0000 */
/* [local] */
#pragma region Application Family
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
#pragma warning(push)
#pragma warning(disable:4668)
#pragma warning(disable:4001)
// #pragma once
#pragma warning(pop)
// Win32 API definitions
#define E_NOTFOUND HRESULT_FROM_WIN32(ERROR_NOT_FOUND)
#define E_UNKNOWNTYPE MAKE_HRESULT(SEVERITY_ERROR, FACILITY_XAML, 40L)
_Check_return_ HRESULT InitializeXamlDiagnostic(_In_ LPCWSTR endPointName, _In_ DWORD pid, _In_ LPCWSTR wszDllXamlDiagnostics, _In_ LPCWSTR wszTAPDllName, _In_ CLSID tapClsid);
_Check_return_ HRESULT InitializeXamlDiagnosticsEx(_In_ LPCWSTR endPointName, _In_ DWORD pid, _In_ LPCWSTR wszDllXamlDiagnostics, _In_ LPCWSTR wszTAPDllName, _In_ CLSID tapClsid, _In_ LPCWSTR wszInitializationData);
typedef MIDL_uhyper InstanceHandle;
typedef
enum VisualMutationType
{
Add = 0,
Remove = ( Add + 1 )
} VisualMutationType;
typedef
enum BaseValueSource
{
BaseValueSourceUnknown = 0,
BaseValueSourceDefault = ( BaseValueSourceUnknown + 1 ) ,
BaseValueSourceBuiltInStyle = ( BaseValueSourceDefault + 1 ) ,
BaseValueSourceStyle = ( BaseValueSourceBuiltInStyle + 1 ) ,
BaseValueSourceLocal = ( BaseValueSourceStyle + 1 ) ,
Inherited = ( BaseValueSourceLocal + 1 ) ,
DefaultStyleTrigger = ( Inherited + 1 ) ,
TemplateTrigger = ( DefaultStyleTrigger + 1 ) ,
StyleTrigger = ( TemplateTrigger + 1 ) ,
ImplicitStyleReference = ( StyleTrigger + 1 ) ,
ParentTemplate = ( ImplicitStyleReference + 1 ) ,
ParentTemplateTrigger = ( ParentTemplate + 1 ) ,
Animation = ( ParentTemplateTrigger + 1 ) ,
Coercion = ( Animation + 1 ) ,
BaseValueSourceVisualState = ( Coercion + 1 )
} BaseValueSource;
typedef struct SourceInfo
{
BSTR FileName;
unsigned int LineNumber;
unsigned int ColumnNumber;
unsigned int CharPosition;
BSTR Hash;
} SourceInfo;
typedef struct ParentChildRelation
{
InstanceHandle Parent;
InstanceHandle Child;
unsigned int ChildIndex;
} ParentChildRelation;
typedef struct VisualElement
{
InstanceHandle Handle;
SourceInfo SrcInfo;
BSTR Type;
BSTR Name;
unsigned int NumChildren;
} VisualElement;
typedef struct PropertyChainSource
{
InstanceHandle Handle;
BSTR TargetType;
BSTR Name;
BaseValueSource Source;
SourceInfo SrcInfo;
} PropertyChainSource;
typedef
enum MetadataBit
{
None = 0,
IsValueHandle = 0x1,
IsPropertyReadOnly = 0x2,
IsValueCollection = 0x4,
IsValueCollectionReadOnly = 0x8,
IsValueBindingExpression = 0x10,
IsValueNull = 0x20,
IsValueHandleAndEvaluatedValue = 0x40
} MetadataBit;
typedef struct PropertyChainValue
{
unsigned int Index;
BSTR Type;
BSTR DeclaringType;
BSTR ValueType;
BSTR ItemType;
BSTR Value;
BOOL Overridden;
hyper MetadataBits;
BSTR PropertyName;
unsigned int PropertyChainIndex;
} PropertyChainValue;
typedef struct EnumType
{
BSTR Name;
SAFEARRAY * ValueInts;
SAFEARRAY * ValueStrings;
} EnumType;
typedef struct CollectionElementValue
{
unsigned int Index;
BSTR ValueType;
BSTR Value;
hyper MetadataBits;
} CollectionElementValue;
typedef
enum RenderTargetBitmapOptions
{
RenderTarget = 0,
RenderTargetAndChildren = ( RenderTarget + 1 )
} RenderTargetBitmapOptions;
typedef struct BitmapDescription
{
unsigned int Width;
unsigned int Height;
DXGI_FORMAT Format;
DXGI_ALPHA_MODE AlphaMode;
} BitmapDescription;
typedef
enum ResourceType
{
ResourceTypeStatic = 0,
ResourceTypeTheme = ( ResourceTypeStatic + 1 )
} ResourceType;
typedef
enum VisualElementState
{
ErrorResolved = 0,
ErrorResourceNotFound = ( ErrorResolved + 1 ) ,
ErrorInvalidResource = ( ErrorResourceNotFound + 1 )
} VisualElementState;
extern RPC_IF_HANDLE __MIDL_itf_xamlom_0000_0000_v0_0_c_ifspec;
extern RPC_IF_HANDLE __MIDL_itf_xamlom_0000_0000_v0_0_s_ifspec;
#ifndef __IVisualTreeServiceCallback_INTERFACE_DEFINED__
#define __IVisualTreeServiceCallback_INTERFACE_DEFINED__
/* interface IVisualTreeServiceCallback */
/* [unique][uuid][object] */
EXTERN_C const IID IID_IVisualTreeServiceCallback;
#if defined(__cplusplus) && !defined(CINTERFACE)
#ifdef __CRT_UUID_DECL
__CRT_UUID_DECL(IVisualTreeServiceCallback, 0xAA7A8931, 0x80E4, 0x4FEC, 0x8F, 0x3B, 0x55, 0x3F, 0x87, 0xB4, 0x96, 0x6E);
#endif
MIDL_INTERFACE("AA7A8931-80E4-4FEC-8F3B-553F87B4966E")
IVisualTreeServiceCallback : public IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE OnVisualTreeChange(
/* [in] */ ParentChildRelation relation,
/* [in] */ VisualElement element,
/* [in] */ VisualMutationType mutationType) = 0;
};
#else /* C style interface */
#error Only C++ style interface is supported
#endif /* C style interface */
#endif /* __IVisualTreeServiceCallback_INTERFACE_DEFINED__ */
#ifndef __IVisualTreeServiceCallback2_INTERFACE_DEFINED__
#define __IVisualTreeServiceCallback2_INTERFACE_DEFINED__
/* interface IVisualTreeServiceCallback2 */
/* [unique][uuid][object] */
EXTERN_C const IID IID_IVisualTreeServiceCallback2;
#if defined(__cplusplus) && !defined(CINTERFACE)
#ifdef __CRT_UUID_DECL
__CRT_UUID_DECL(IVisualTreeServiceCallback2, 0xBAD9EB88, 0xAE77, 0x4397, 0xB9, 0x48, 0x5F, 0xA2, 0xDB, 0x0A, 0x19, 0xEA);
#endif
MIDL_INTERFACE("BAD9EB88-AE77-4397-B948-5FA2DB0A19EA")
IVisualTreeServiceCallback2 : public IVisualTreeServiceCallback
{
public:
virtual HRESULT STDMETHODCALLTYPE OnElementStateChanged(
/* [in] */ InstanceHandle element,
/* [in] */ VisualElementState elementState,
/* [in] */ __RPC__in LPCWSTR context) = 0;
};
#else /* C style interface */
#error Only C++ style interface is supported
#endif /* C style interface */
#endif /* __IVisualTreeServiceCallback2_INTERFACE_DEFINED__ */
#ifndef __IVisualTreeService_INTERFACE_DEFINED__
#define __IVisualTreeService_INTERFACE_DEFINED__
/* interface IVisualTreeService */
/* [unique][uuid][object] */
EXTERN_C const IID IID_IVisualTreeService;
#if defined(__cplusplus) && !defined(CINTERFACE)
#ifdef __CRT_UUID_DECL
__CRT_UUID_DECL(IVisualTreeService, 0xA593B11A, 0xD17F, 0x48BB, 0x8F, 0x66, 0x83, 0x91, 0x07, 0x31, 0xC8, 0xA5);
#endif
MIDL_INTERFACE("A593B11A-D17F-48BB-8F66-83910731C8A5")
IVisualTreeService : public IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE AdviseVisualTreeChange(
/* [in] */ __RPC__in_opt IVisualTreeServiceCallback *pCallback) = 0;
virtual HRESULT STDMETHODCALLTYPE UnadviseVisualTreeChange(
/* [in] */ __RPC__in_opt IVisualTreeServiceCallback *pCallback) = 0;
virtual HRESULT STDMETHODCALLTYPE GetEnums(
/* [out] */ __RPC__out unsigned int *pCount,
/* [size_is][size_is][out] */ __RPC__deref_out_ecount_full_opt(*pCount) EnumType **ppEnums) = 0;
virtual HRESULT STDMETHODCALLTYPE CreateInstance(
/* [in] */ __RPC__in BSTR typeName,
/* [in] */ __RPC__in BSTR value,
/* [retval][out] */ __RPC__out InstanceHandle *pInstanceHandle) = 0;
virtual HRESULT STDMETHODCALLTYPE GetPropertyValuesChain(
/* [in] */ InstanceHandle instanceHandle,
/* [out] */ __RPC__out unsigned int *pSourceCount,
/* [size_is][size_is][out] */ __RPC__deref_out_ecount_full_opt(*pSourceCount) PropertyChainSource **ppPropertySources,
/* [out] */ __RPC__out unsigned int *pPropertyCount,
/* [size_is][size_is][out] */ __RPC__deref_out_ecount_full_opt(*pPropertyCount) PropertyChainValue **ppPropertyValues) = 0;
virtual HRESULT STDMETHODCALLTYPE SetProperty(
/* [in] */ InstanceHandle instanceHandle,
/* [in] */ InstanceHandle value,
/* [in] */ unsigned int propertyIndex) = 0;
virtual HRESULT STDMETHODCALLTYPE ClearProperty(
/* [in] */ InstanceHandle instanceHandle,
/* [in] */ unsigned int propertyIndex) = 0;
virtual HRESULT STDMETHODCALLTYPE GetCollectionCount(
/* [in] */ InstanceHandle instanceHandle,
/* [out] */ __RPC__out unsigned int *pCollectionSize) = 0;
virtual HRESULT STDMETHODCALLTYPE GetCollectionElements(
/* [in] */ InstanceHandle instanceHandle,
/* [in] */ unsigned int startIndex,
/* [out][in] */ __RPC__inout unsigned int *pElementCount,
/* [size_is][size_is][out] */ __RPC__deref_out_ecount_full_opt(*pElementCount) CollectionElementValue **ppElementValues) = 0;
virtual HRESULT STDMETHODCALLTYPE AddChild(
/* [in] */ InstanceHandle parent,
/* [in] */ InstanceHandle child,
/* [in] */ unsigned int index) = 0;
virtual HRESULT STDMETHODCALLTYPE RemoveChild(
/* [in] */ InstanceHandle parent,
/* [in] */ unsigned int index) = 0;
virtual HRESULT STDMETHODCALLTYPE ClearChildren(
/* [in] */ InstanceHandle parent) = 0;
};
#else /* C style interface */
#error Only C++ style interface is supported
#endif /* C style interface */
#endif /* __IVisualTreeService_INTERFACE_DEFINED__ */
#ifndef __IXamlDiagnostics_INTERFACE_DEFINED__
#define __IXamlDiagnostics_INTERFACE_DEFINED__
/* interface IXamlDiagnostics */
/* [unique][uuid][object] */
EXTERN_C const IID IID_IXamlDiagnostics;
#if defined(__cplusplus) && !defined(CINTERFACE)
#ifdef __CRT_UUID_DECL
__CRT_UUID_DECL(IXamlDiagnostics, 0x18C9E2B6, 0x3F43, 0x4116, 0x9F, 0x2B, 0xFF, 0x93, 0x5D, 0x77, 0x70, 0xD2);
#endif
MIDL_INTERFACE("18C9E2B6-3F43-4116-9F2B-FF935D7770D2")
IXamlDiagnostics : public IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE GetDispatcher(
/* [retval][out] */ __RPC__deref_out_opt IInspectable **ppDispatcher) = 0;
virtual HRESULT STDMETHODCALLTYPE GetUiLayer(
/* [retval][out] */ __RPC__deref_out_opt IInspectable **ppLayer) = 0;
virtual HRESULT STDMETHODCALLTYPE GetApplication(
/* [retval][out] */ __RPC__deref_out_opt IInspectable **ppApplication) = 0;
virtual HRESULT STDMETHODCALLTYPE GetIInspectableFromHandle(
/* [in] */ InstanceHandle instanceHandle,
/* [retval][out] */ __RPC__deref_out_opt IInspectable **ppInstance) = 0;
virtual HRESULT STDMETHODCALLTYPE GetHandleFromIInspectable(
/* [in] */ __RPC__in_opt IInspectable *pInstance,
/* [retval][out] */ __RPC__out InstanceHandle *pHandle) = 0;
virtual HRESULT STDMETHODCALLTYPE HitTest(
/* [in] */ RECT rect,
/* [out] */ __RPC__out unsigned int *pCount,
/* [size_is][size_is][out] */ __RPC__deref_out_ecount_full_opt(*pCount) InstanceHandle **ppInstanceHandles) = 0;
virtual HRESULT STDMETHODCALLTYPE RegisterInstance(
/* [in] */ __RPC__in_opt IInspectable *pInstance,
/* [retval][out] */ __RPC__out InstanceHandle *pInstanceHandle) = 0;
virtual HRESULT STDMETHODCALLTYPE GetInitializationData(
/* [retval][out] */ __RPC__deref_out_opt BSTR *pInitializationData) = 0;
};
#else /* C style interface */
#error Only C++ style interface is supported
#endif /* C style interface */
#endif /* __IXamlDiagnostics_INTERFACE_DEFINED__ */
#ifndef __IBitmapData_INTERFACE_DEFINED__
#define __IBitmapData_INTERFACE_DEFINED__
/* interface IBitmapData */
/* [unique][uuid][object] */
EXTERN_C const IID IID_IBitmapData;
#if defined(__cplusplus) && !defined(CINTERFACE)
#ifdef __CRT_UUID_DECL
__CRT_UUID_DECL(IBitmapData, 0xd1a34ef2, 0xcad8, 0x4635, 0xa3, 0xd2, 0xfc, 0xda, 0x8d, 0x3f, 0x3c, 0xaf);
#endif
MIDL_INTERFACE("d1a34ef2-cad8-4635-a3d2-fcda8d3f3caf")
IBitmapData : public IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE CopyBytesTo(
/* [in] */ unsigned int sourceOffsetInBytes,
/* [in] */ unsigned int maxBytesToCopy,
/* [size_is][out] */ __RPC__out_ecount_full(maxBytesToCopy) byte *pvBytes,
/* [out] */ __RPC__out unsigned int *numberOfBytesCopied) = 0;
virtual HRESULT STDMETHODCALLTYPE GetStride(
/* [out] */ __RPC__out unsigned int *pStride) = 0;
virtual HRESULT STDMETHODCALLTYPE GetBitmapDescription(
/* [out] */ __RPC__out BitmapDescription *pBitmapDescription) = 0;
virtual HRESULT STDMETHODCALLTYPE GetSourceBitmapDescription(
/* [out] */ __RPC__out BitmapDescription *pBitmapDescription) = 0;
};
#else /* C style interface */
#error Only C++ style interface is supported
#endif /* C style interface */
#endif /* __IBitmapData_INTERFACE_DEFINED__ */
#ifndef __IVisualTreeService2_INTERFACE_DEFINED__
#define __IVisualTreeService2_INTERFACE_DEFINED__
/* interface IVisualTreeService2 */
/* [unique][uuid][object] */
EXTERN_C const IID IID_IVisualTreeService2;
#if defined(__cplusplus) && !defined(CINTERFACE)
#ifdef __CRT_UUID_DECL
__CRT_UUID_DECL(IVisualTreeService2, 0x130F5136, 0xEC43, 0x4F61, 0x89, 0xC7, 0x98, 0x01, 0xA3, 0x6D, 0x2E, 0x95);
#endif
MIDL_INTERFACE("130F5136-EC43-4F61-89C7-9801A36D2E95")
IVisualTreeService2 : public IVisualTreeService
{
public:
virtual HRESULT STDMETHODCALLTYPE GetPropertyIndex(
/* [in] */ InstanceHandle object,
/* [in] */ __RPC__in LPCWSTR propertyName,
/* [out] */ __RPC__out unsigned int *pPropertyIndex) = 0;
virtual HRESULT STDMETHODCALLTYPE GetProperty(
/* [in] */ InstanceHandle object,
/* [in] */ unsigned int propertyIndex,
/* [out] */ __RPC__out InstanceHandle *pValue) = 0;
virtual HRESULT STDMETHODCALLTYPE ReplaceResource(
/* [in] */ InstanceHandle resourceDictionary,
/* [in] */ InstanceHandle key,
/* [in] */ InstanceHandle newValue) = 0;
virtual HRESULT STDMETHODCALLTYPE RenderTargetBitmap(
/* [in] */ InstanceHandle handle,
/* [in] */ RenderTargetBitmapOptions options,
/* [in] */ unsigned int maxPixelWidth,
/* [in] */ unsigned int maxPixelHeight,
/* [out] */ __RPC__deref_out_opt IBitmapData **ppBitmapData) = 0;
};
#else /* C style interface */
#error Only C++ style interface is supported
#endif /* C style interface */
#endif /* __IVisualTreeService2_INTERFACE_DEFINED__ */
#ifndef __IVisualTreeService3_INTERFACE_DEFINED__
#define __IVisualTreeService3_INTERFACE_DEFINED__
/* interface IVisualTreeService3 */
/* [unique][uuid][object] */
EXTERN_C const IID IID_IVisualTreeService3;
#if defined(__cplusplus) && !defined(CINTERFACE)
#ifdef __CRT_UUID_DECL
__CRT_UUID_DECL(IVisualTreeService3, 0x0E79C6E0, 0x85A0, 0x4BE8, 0xB4, 0x1A, 0x65, 0x5C, 0xF1, 0xFD, 0x19, 0xBD);
#endif
MIDL_INTERFACE("0E79C6E0-85A0-4BE8-B41A-655CF1FD19BD")
IVisualTreeService3 : public IVisualTreeService2
{
public:
virtual HRESULT STDMETHODCALLTYPE ResolveResource(
/* [in] */ InstanceHandle resourceContext,
/* [in] */ __RPC__in LPCWSTR resourceName,
/* [in] */ ResourceType resourceType,
/* [in] */ unsigned int propertyIndex) = 0;
virtual HRESULT STDMETHODCALLTYPE GetDictionaryItem(
/* [in] */ InstanceHandle dictionaryHandle,
/* [in] */ __RPC__in LPCWSTR resourceName,
/* [in] */ BOOL resourceIsImplicitStyle,
/* [out] */ __RPC__out InstanceHandle *resourceHandle) = 0;
virtual HRESULT STDMETHODCALLTYPE AddDictionaryItem(
/* [in] */ InstanceHandle dictionaryHandle,
/* [in] */ InstanceHandle resourceKey,
/* [in] */ InstanceHandle resourceHandle) = 0;
virtual HRESULT STDMETHODCALLTYPE RemoveDictionaryItem(
/* [in] */ InstanceHandle dictionaryHandle,
/* [in] */ InstanceHandle resourceKey) = 0;
};
#else /* C style interface */
#error Only C++ style interface is supported
#endif /* C style interface */
#endif /* __IVisualTreeService3_INTERFACE_DEFINED__ */
/* interface __MIDL_itf_xamlom_0000_0007 */
/* [local] */
#endif /* WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) */
#pragma endregion
extern RPC_IF_HANDLE __MIDL_itf_xamlom_0000_0007_v0_0_c_ifspec;
extern RPC_IF_HANDLE __MIDL_itf_xamlom_0000_0007_v0_0_s_ifspec;
/* Additional Prototypes for ALL interfaces */
unsigned long __RPC_USER BSTR_UserSize( __RPC__in unsigned long *, unsigned long , __RPC__in BSTR * );
unsigned char * __RPC_USER BSTR_UserMarshal( __RPC__in unsigned long *, __RPC__inout_xcount(0) unsigned char *, __RPC__in BSTR * );
unsigned char * __RPC_USER BSTR_UserUnmarshal(__RPC__in unsigned long *, __RPC__in_xcount(0) unsigned char *, __RPC__out BSTR * );
void __RPC_USER BSTR_UserFree( __RPC__in unsigned long *, __RPC__in BSTR * );
unsigned long __RPC_USER LPSAFEARRAY_UserSize( __RPC__in unsigned long *, unsigned long , __RPC__in LPSAFEARRAY * );
unsigned char * __RPC_USER LPSAFEARRAY_UserMarshal( __RPC__in unsigned long *, __RPC__inout_xcount(0) unsigned char *, __RPC__in LPSAFEARRAY * );
unsigned char * __RPC_USER LPSAFEARRAY_UserUnmarshal(__RPC__in unsigned long *, __RPC__in_xcount(0) unsigned char *, __RPC__out LPSAFEARRAY * );
void __RPC_USER LPSAFEARRAY_UserFree( __RPC__in unsigned long *, __RPC__in LPSAFEARRAY * );
unsigned long __RPC_USER BSTR_UserSize64( __RPC__in unsigned long *, unsigned long , __RPC__in BSTR * );
unsigned char * __RPC_USER BSTR_UserMarshal64( __RPC__in unsigned long *, __RPC__inout_xcount(0) unsigned char *, __RPC__in BSTR * );
unsigned char * __RPC_USER BSTR_UserUnmarshal64(__RPC__in unsigned long *, __RPC__in_xcount(0) unsigned char *, __RPC__out BSTR * );
void __RPC_USER BSTR_UserFree64( __RPC__in unsigned long *, __RPC__in BSTR * );
unsigned long __RPC_USER LPSAFEARRAY_UserSize64( __RPC__in unsigned long *, unsigned long , __RPC__in LPSAFEARRAY * );
unsigned char * __RPC_USER LPSAFEARRAY_UserMarshal64( __RPC__in unsigned long *, __RPC__inout_xcount(0) unsigned char *, __RPC__in LPSAFEARRAY * );
unsigned char * __RPC_USER LPSAFEARRAY_UserUnmarshal64(__RPC__in unsigned long *, __RPC__in_xcount(0) unsigned char *, __RPC__out LPSAFEARRAY * );
void __RPC_USER LPSAFEARRAY_UserFree64( __RPC__in unsigned long *, __RPC__in LPSAFEARRAY * );
/* end of Additional Prototypes */
#ifdef __cplusplus
}
#endif
#endif
#pragma endregion // xamlOM_h
#pragma region windows_ui_xaml_hosting_desktopwindowxamlsource_h
/* this ALWAYS GENERATED file contains the definitions for the interfaces */
/* File created by MIDL compiler version 8.01.0622 */
/* @@MIDL_FILE_HEADING( ) */
/* verify that the <rpcndr.h> version is high enough to compile this file*/
#ifndef __REQUIRED_RPCNDR_H_VERSION__
#define __REQUIRED_RPCNDR_H_VERSION__ 500
#endif
/* verify that the <rpcsal.h> version is high enough to compile this file*/
#ifndef __REQUIRED_RPCSAL_H_VERSION__
#define __REQUIRED_RPCSAL_H_VERSION__ 100
#endif
#include <rpc.h>
#include <rpcndr.h>
#ifndef __RPCNDR_H_VERSION__
#error this stub requires an updated version of <rpcndr.h>
#endif /* __RPCNDR_H_VERSION__ */
#ifndef COM_NO_WINDOWS_H
#include <windows.h>
#include <ole2.h>
#endif /*COM_NO_WINDOWS_H*/
#ifndef __windows2Eui2Examl2Ehosting2Edesktopwindowxamlsource_h__
#define __windows2Eui2Examl2Ehosting2Edesktopwindowxamlsource_h__
#if defined(_MSC_VER) && (_MSC_VER >= 1020)
// #pragma once
#endif
/* Forward Declarations */
#ifndef __IDesktopWindowXamlSourceNative_FWD_DEFINED__
#define __IDesktopWindowXamlSourceNative_FWD_DEFINED__
typedef interface IDesktopWindowXamlSourceNative IDesktopWindowXamlSourceNative;
#endif /* __IDesktopWindowXamlSourceNative_FWD_DEFINED__ */
#ifndef __IDesktopWindowXamlSourceNative2_FWD_DEFINED__
#define __IDesktopWindowXamlSourceNative2_FWD_DEFINED__
typedef interface IDesktopWindowXamlSourceNative2 IDesktopWindowXamlSourceNative2;
#endif /* __IDesktopWindowXamlSourceNative2_FWD_DEFINED__ */
/* header files for imported files */
#include <oaidl.h>
#ifdef __cplusplus
extern "C"{
#endif
/* interface __MIDL_itf_windows2Eui2Examl2Ehosting2Edesktopwindowxamlsource_0000_0000 */
/* [local] */
#if (NTDDI_VERSION >= NTDDI_WIN10_RS5)
extern RPC_IF_HANDLE __MIDL_itf_windows2Eui2Examl2Ehosting2Edesktopwindowxamlsource_0000_0000_v0_0_c_ifspec;
extern RPC_IF_HANDLE __MIDL_itf_windows2Eui2Examl2Ehosting2Edesktopwindowxamlsource_0000_0000_v0_0_s_ifspec;
#ifndef __IDesktopWindowXamlSourceNative_INTERFACE_DEFINED__
#define __IDesktopWindowXamlSourceNative_INTERFACE_DEFINED__
/* interface IDesktopWindowXamlSourceNative */
/* [unique][local][uuid][object] */
EXTERN_C const IID IID_IDesktopWindowXamlSourceNative;
#if defined(__cplusplus) && !defined(CINTERFACE)
#ifdef __CRT_UUID_DECL
__CRT_UUID_DECL(IDesktopWindowXamlSourceNative, 0x3cbcf1bf, 0x2f76, 0x4e9c, 0x96, 0xab, 0xe8, 0x4b, 0x37, 0x97, 0x25, 0x54);
#endif
MIDL_INTERFACE("3cbcf1bf-2f76-4e9c-96ab-e84b37972554")
IDesktopWindowXamlSourceNative : public IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE AttachToWindow(
/* [annotation][in] */
_In_ HWND parentWnd) = 0;
virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_WindowHandle(
/* [retval][out] */ HWND *hWnd) = 0;
};
#else /* C style interface */
#error Only C++ style interface is supported
#endif /* C style interface */
#endif /* __IDesktopWindowXamlSourceNative_INTERFACE_DEFINED__ */
/* interface __MIDL_itf_windows2Eui2Examl2Ehosting2Edesktopwindowxamlsource_0000_0001 */
/* [local] */
#endif // NTDDI_VERSION >= NTDDI_WIN10_RS5
#if (NTDDI_VERSION >= NTDDI_WIN10_19H1)
extern RPC_IF_HANDLE __MIDL_itf_windows2Eui2Examl2Ehosting2Edesktopwindowxamlsource_0000_0001_v0_0_c_ifspec;
extern RPC_IF_HANDLE __MIDL_itf_windows2Eui2Examl2Ehosting2Edesktopwindowxamlsource_0000_0001_v0_0_s_ifspec;
#ifndef __IDesktopWindowXamlSourceNative2_INTERFACE_DEFINED__
#define __IDesktopWindowXamlSourceNative2_INTERFACE_DEFINED__
/* interface IDesktopWindowXamlSourceNative2 */
/* [unique][local][uuid][object] */
EXTERN_C const IID IID_IDesktopWindowXamlSourceNative2;
#if defined(__cplusplus) && !defined(CINTERFACE)
#ifdef __CRT_UUID_DECL
__CRT_UUID_DECL(IDesktopWindowXamlSourceNative2, 0xe3dcd8c7, 0x3057, 0x4692, 0x99, 0xc3, 0x7b, 0x77, 0x20, 0xaf, 0xda, 0x31);
#endif
MIDL_INTERFACE("e3dcd8c7-3057-4692-99c3-7b7720afda31")
IDesktopWindowXamlSourceNative2 : public IDesktopWindowXamlSourceNative
{
public:
virtual HRESULT STDMETHODCALLTYPE PreTranslateMessage(
/* [annotation][in] */
_In_ const MSG *message,
/* [retval][out] */ BOOL *result) = 0;
};
#else /* C style interface */
#error Only C++ style interface is supported
#endif /* C style interface */
#endif /* __IDesktopWindowXamlSourceNative2_INTERFACE_DEFINED__ */
/* interface __MIDL_itf_windows2Eui2Examl2Ehosting2Edesktopwindowxamlsource_0000_0002 */
/* [local] */
#endif // NTDDI_VERSION >= NTDDI_WIN10_19H1
extern RPC_IF_HANDLE __MIDL_itf_windows2Eui2Examl2Ehosting2Edesktopwindowxamlsource_0000_0002_v0_0_c_ifspec;
extern RPC_IF_HANDLE __MIDL_itf_windows2Eui2Examl2Ehosting2Edesktopwindowxamlsource_0000_0002_v0_0_s_ifspec;
/* Additional Prototypes for ALL interfaces */
/* end of Additional Prototypes */
#ifdef __cplusplus
}
#endif
#endif
#pragma endregion // windows_ui_xaml_hosting_desktopwindowxamlsource_h
#pragma region visualtreewatcher_hpp
#include <winrt/Windows.UI.Xaml.h>
struct VisualTreeWatcher : winrt::implements<VisualTreeWatcher, IVisualTreeServiceCallback2, winrt::non_agile>
{
VisualTreeWatcher() = default;
VisualTreeWatcher(const VisualTreeWatcher&) = delete;
VisualTreeWatcher& operator=(const VisualTreeWatcher&) = delete;
VisualTreeWatcher(VisualTreeWatcher&&) = delete;
VisualTreeWatcher& operator=(VisualTreeWatcher&&) = delete;
void SetXamlDiagnostics(winrt::com_ptr<IXamlDiagnostics> diagnostics);
~VisualTreeWatcher();
private:
HRESULT STDMETHODCALLTYPE OnVisualTreeChange(ParentChildRelation relation, VisualElement element, VisualMutationType mutationType) override;
HRESULT STDMETHODCALLTYPE OnElementStateChanged(InstanceHandle element, VisualElementState elementState, LPCWSTR context) noexcept override;
template<typename T>
T FromHandle(InstanceHandle handle)
{
wf::IInspectable obj;
winrt::check_hresult(m_XamlDiagnostics->GetIInspectableFromHandle(handle, reinterpret_cast<::IInspectable**>(winrt::put_abi(obj))));
return obj.as<T>();
}
winrt::com_ptr<IXamlDiagnostics> m_XamlDiagnostics = nullptr;
};
#pragma endregion // visualtreewatcher_hpp
#pragma region visualtreewatcher_cpp
#include <winrt/Windows.UI.Xaml.Hosting.h>
void VisualTreeWatcher::SetXamlDiagnostics(winrt::com_ptr<IXamlDiagnostics> diagnostics)
{
m_XamlDiagnostics = std::move(diagnostics);
}
VisualTreeWatcher::~VisualTreeWatcher()
{
}
HRESULT VisualTreeWatcher::OnVisualTreeChange(ParentChildRelation, VisualElement element, VisualMutationType mutationType) try
{
if (g_disabled)
{
return S_OK;
}
Wh_Log(L"========================================");
switch (mutationType)
{
case Add:
Wh_Log(L"Mutation type: Add");
break;
case Remove:
Wh_Log(L"Mutation type: Remove");
break;
default:
Wh_Log(L"Mutation type: %d", static_cast<int>(mutationType));
break;
}
Wh_Log(L"Element type: %s", element.Type);
const auto inspectable = FromHandle<wf::IInspectable>(element.Handle);
auto frameworkElement = inspectable.try_as<wux::FrameworkElement>();
if (!frameworkElement)
{
const auto desktopXamlSource = FromHandle<wuxh::DesktopWindowXamlSource>(element.Handle);
frameworkElement = desktopXamlSource.Content().try_as<wux::FrameworkElement>();
}
if (frameworkElement)
{
Wh_Log(L"FrameworkElement name: %s", frameworkElement.Name().c_str());
}
return S_OK;
}
catch (...)
{
HRESULT hr = winrt::to_hresult();
Wh_Log(L"Error %08X", hr);
return hr;
}
HRESULT VisualTreeWatcher::OnElementStateChanged(InstanceHandle, VisualElementState, LPCWSTR) noexcept
{
return S_OK;
}
#pragma endregion // visualtreewatcher_cpp
#pragma region tap_hpp
#include <ocidl.h>
// {C85D8CC7-5463-40E8-A432-F5916B6427E5}
static constexpr CLSID CLSID_WindhawkTAP = { 0xc85d8cc7, 0x5463, 0x40e8, { 0xa4, 0x32, 0xf5, 0x91, 0x6b, 0x64, 0x27, 0xe5 } };
struct WindhawkTAP : winrt::implements<WindhawkTAP, IObjectWithSite, winrt::non_agile>
{
HRESULT STDMETHODCALLTYPE SetSite(IUnknown *pUnkSite) override;
HRESULT STDMETHODCALLTYPE GetSite(REFIID riid, void **ppvSite) noexcept override;
private:
template<typename T>
static winrt::com_ptr<T> FromIUnknown(IUnknown *pSite)
{
winrt::com_ptr<IUnknown> site;
site.copy_from(pSite);
return site.as<T>();
}
winrt::com_ptr<IVisualTreeService3> visualTreeService;
winrt::com_ptr<VisualTreeWatcher> visualTreeWatcher;
};
#pragma endregion // tap_hpp
#pragma region tap_cpp
HRESULT WindhawkTAP::SetSite(IUnknown *pUnkSite) try
{
if (visualTreeService && visualTreeWatcher)
{
winrt::check_hresult(visualTreeService->UnadviseVisualTreeChange(visualTreeWatcher.get()));
visualTreeWatcher->SetXamlDiagnostics(nullptr);
}
visualTreeService = FromIUnknown<IVisualTreeService3>(pUnkSite);
if (visualTreeService)
{
if (!visualTreeWatcher)
{
visualTreeWatcher = winrt::make_self<VisualTreeWatcher>();
}
visualTreeWatcher->SetXamlDiagnostics(visualTreeService.as<IXamlDiagnostics>());
winrt::check_hresult(visualTreeService->AdviseVisualTreeChange(visualTreeWatcher.get()));
}
return S_OK;
}
catch (...)
{
return winrt::to_hresult();
}
HRESULT WindhawkTAP::GetSite(REFIID riid, void **ppvSite) noexcept
{
return visualTreeService.as(riid, ppvSite);
}
#pragma endregion // tap_cpp
#pragma region simplefactory_hpp
#include <Unknwn.h>
template<class T>
struct SimpleFactory : winrt::implements<SimpleFactory<T>, IClassFactory, winrt::non_agile>
{
HRESULT STDMETHODCALLTYPE CreateInstance(IUnknown* pUnkOuter, REFIID riid, void** ppvObject) override try
{
if (!pUnkOuter)
{
*ppvObject = nullptr;
return winrt::make<T>().as(riid, ppvObject);
}
else
{
return CLASS_E_NOAGGREGATION;
}
}
catch (...)
{
return winrt::to_hresult();
}
HRESULT STDMETHODCALLTYPE LockServer(BOOL) noexcept override
{
return S_OK;
}
};
#pragma endregion // simplefactory_hpp
#pragma region module_cpp
#include <combaseapi.h>
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdll-attribute-on-redeclaration"
__declspec(dllexport)
_Use_decl_annotations_ STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv) try
{
if (rclsid == CLSID_WindhawkTAP)
{
*ppv = nullptr;
return winrt::make<SimpleFactory<WindhawkTAP>>().as(riid, ppv);
}
else
{
return CLASS_E_CLASSNOTAVAILABLE;
}
}
catch (...)
{
return winrt::to_hresult();
}
__declspec(dllexport)
_Use_decl_annotations_ STDAPI DllCanUnloadNow(void)
{
if (winrt::get_module_lock())
{
return S_FALSE;
}
else
{
return S_OK;
}
}
#pragma clang diagnostic pop
#pragma endregion // module_cpp
#pragma region api_cpp
using PFN_INITIALIZE_XAML_DIAGNOSTICS_EX = decltype(&InitializeXamlDiagnosticsEx);
HMODULE GetCurrentModuleHandle()
{
HMODULE module;
if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
L"", &module)) {
return nullptr;
}
return module;
}
HRESULT InjectWindhawkTAP() noexcept
{
HMODULE module = GetCurrentModuleHandle();
if (!module)
{
return HRESULT_FROM_WIN32(GetLastError());
}
WCHAR location[MAX_PATH];
switch (GetModuleFileName(module, location, ARRAYSIZE(location)))
{
case 0:
case ARRAYSIZE(location):
return HRESULT_FROM_WIN32(GetLastError());
}
const HMODULE wux(LoadLibraryEx(L"Windows.UI.Xaml.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32));
if (!wux) [[unlikely]]
{
return HRESULT_FROM_WIN32(GetLastError());
}
const auto ixde = reinterpret_cast<PFN_INITIALIZE_XAML_DIAGNOSTICS_EX>(GetProcAddress(wux, "InitializeXamlDiagnosticsEx"));
if (!ixde) [[unlikely]]
{
return HRESULT_FROM_WIN32(GetLastError());
}
const HRESULT hr2 = ixde(L"VisualDiagConnection1", GetCurrentProcessId(), nullptr, location, CLSID_WindhawkTAP, nullptr);
if (FAILED(hr2)) [[unlikely]]
{
return hr2;
}
return S_OK;
}
#pragma endregion // api_cpp
BOOL Wh_ModInit()
{
Wh_Log(L">");
if (g_disabled)
{
Wh_Log(L"Already loaded, resuming");
g_disabled = false;
return TRUE;
}
HRESULT hr = InjectWindhawkTAP();
if (FAILED(hr))
{
Wh_Log(L"Error %08X", hr);
return FALSE;
}
return TRUE;
}
void Wh_ModUninit()
{
Wh_Log(L">");
// TODO: Explore a way to stop the monitoring and unload the dll.
g_disabled = true;
}
需要注意的是,必须在 UI 线程操作视图树,否则会导致崩溃。此外,应用必须激活才能成功注入模块,UWP 的应用执行态可以通过调试 API 修改。部分应用的视图树是动态生成的,所以需要订阅元素添加事件和窗口显示事件。
文档条目“使用 C++/WinRT 的并发和异步操作:考虑线程亲和性进行编程”解释了如何控制哪个线程运行特定代码。这在异步函数上下文中特别有用。
C++/WinRT 提供帮助程序 winrt::resume_background()
和 winrt::resume_foreground()
的 co_await(协程)
切换到相应的线程(后台线程或与控件调度程序关联的线程)。
以下代码说明了用法:
IAsyncOperation<bool> ScrollIt(h, v, zoom){
co_await winrt::resume_background();
// 在这里做计算工作。
// 切换到与 m_scroll_viewer 关联的前台线程。
co_await winrt::resume_foreground(m_scroll_viewer.Dispatcher());
// 执行GUI相关代码
m_scroll_viewer.ChangeView(h, v, zoom);
// 可选择切换回后台线程。
// 返回一个适当的值。
co_return {};
}
下面的使用一定正确吗?
CoreWindow const window{ CoreWindow::GetForCurrentThread() };
Getting CoreWindow from CoreWindow::GetForCurrentThread() will only work when called from a UI thread which has an associated window. Instead, when you are on a background thread, you must access the UI thread differently:
Windows::ApplicationModel::Core::CoreApplication::MainView->CoreWindow->Dispatcher->RunAsync(
CoreDispatcherPriority::Normal,
ref new Windows::UI::Core::DispatchedHandler([this]()
{
// do stuff
}));
This instead finds the CoreWindow of main view of your app and gets the its dispatcher. The advantage is that this approach doesn't use GetForCurrentThread so you can use it even from a background thread.
This becomes a problem when you have multiple views of your app open - then each view has its own UI thread, hence you must know which view is this action modifying so that you know which dispatcher you want to use. In this scenario, you can use the CoreApplication::Views collection to enumerate and access all application views.
8.4 通过官方支持的方法(设置 NIS_HIDDEN)
最近仔细看了一下 NOTIFYICONDATA 的结构,发现了 dwState 成员。
如果 dwStateMask 为 NIS_HIDDEN,则当 dwState 为 NIS_HIDDEN 时图标隐藏;当 dwState 为NIS_SHAREDICON 时图标显示。
具体注解可以见:NOTIFYICONDATAW (shellapi.h) - Win32 apps | Microsoft Learn。
这里先给一个 TB 消息版本的(仅旧系统支持),新的版本我先谈一下思路:还是利用我们的 hook WM_COPYDATA 的思路,这样可以获取结构体信息,然后通过 Shell_NotifyIcon 函数更改信息即可。
TB 消息版本(仅旧系统支持):
#define _WIN32_IE 0x0500
#include <windows.h>
#include <CommCtrl.h>
struct TRAYDATA
{
HWND hwnd;
UINT uID;
UINT uCallbackMessage;
DWORD Reserved[2];
HICON hIcon;
};
void ShowTrayIcon(LPCWSTR lpwszIcon, BOOL bShow)
{
HWND hWnd,hWndPaper;
DWORD dwProcessId;
int nButtonCount;
HANDLE hProcess;
LPVOID lpAddress;
TBBUTTON tb;
hWnd = FindWindow(TEXT("Shell_TrayWnd"), NULL);
hWnd = FindWindowEx(hWnd, 0, TEXT("TrayNotifyWnd"), NULL);
hWndPaper = FindWindowEx(hWnd, 0, TEXT("SysPager"), NULL);
if(!hWndPaper)
hWnd = FindWindowEx(hWnd, 0, TEXT("ToolbarWindow32"), NULL);
else
hWnd = FindWindowEx(hWndPaper, 0, TEXT("ToolbarWindow32"), NULL);
GetWindowThreadProcessId(hWnd, &dwProcessId);
hProcess = OpenProcess(PROCESS_ALL_ACCESS
|PROCESS_VM_OPERATION
|PROCESS_VM_READ
|PROCESS_VM_WRITE,
0,
dwProcessId);
lpAddress = VirtualAllocEx(hProcess,0, 0x4096, MEM_COMMIT, PAGE_READWRITE);
nButtonCount = SendMessage(hWnd, TB_BUTTONCOUNT, 0, 0);
for(int i = 0 ; i < nButtonCount - 1; i++)
{
SendMessage(hWnd, TB_GETBUTTON, i, (LPARAM)lpAddress);
ReadProcessMemory(hProcess, lpAddress, &tb, sizeof(TBBUTTON), 0);
if(tb.iString != -1)
{
TRAYDATA trayData;
WCHAR wszBuff[MAX_PATH] = {0};
WCHAR *pwsz = NULL;
ReadProcessMemory(hProcess, (LPVOID)tb.iString, wszBuff, MAX_PATH, 0);
ReadProcessMemory(hProcess, (LPVOID)tb.dwData, &trayData, sizeof(TRAYDATA), 0);
pwsz = wcsstr(wszBuff, lpwszIcon);
if (pwsz)
{
NOTIFYICONDATA nid;
nid.cbSize = sizeof(NOTIFYICONDATA);
nid.hWnd = trayData.hwnd;
nid.uID = trayData.uID;
nid.uFlags = NIF_STATE;
nid.dwState = bShow?NIS_SHAREDICON:NIS_HIDDEN;
nid.dwStateMask = NIS_HIDDEN;
Shell_NotifyIcon(NIM_MODIFY, &nid);
}
}
}
VirtualFreeEx( hProcess, lpAddress, 0, MEM_RELEASE);
CloseHandle(hProcess);
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
ShowTrayIcon(L"音量", FALSE); //隐藏音量图标
//ShowTrayIcon(L"音量", TRUE); //显示音量图标
return 0;
}
九、补充内容
9.1 从窗口额外字节访问 WndProc
“Shell_TrayWnd” 窗口是 Windows 任务栏窗口,由 explorer.exe 创建,主要是用于管理状态栏以及任务栏的。
在 “Shell_TrayWnd” 窗口的额外字节区(Extra Bytes)的索引 0 的地方存储着 “CTray” 对象的指针:

“Shell_TrayWnd” 中用于处理消息的对象是 “CTray”,此对象包含用于处理 NotifyMessage 的消息的窗口过程函数。其中,“CTray” 对象是未公开的对象,该对象的定义如下:
// Shell_TrayWnd 窗口的额外内存字节
typedef struct _ctray_vtable {
ULONG_PTR vTable; // 虚表首地址
ULONG_PTR AddRef; // 添加引用
ULONG_PTR Release; // 用于释放过程
ULONG_PTR WndProc; // 窗口过程函数的指针
} CTray;
CTray 结构中的消息处理函数是 WndProc 函数,在这里包含对 WM_COPYDATA 消息的处理。可以首先获取 CTray 来间接获取 WndProc。
“CTray” 对象是 “Shell_TrayWnd” 窗口的一个属性。对于窗口的属性,我们可以调用GetWindowLong/GetWindowLongPtr 获取或修改属性的地址,也可以通过 GetProp/SetProp 做到(后者效率更快)。
// 1. 获取托盘窗口的句柄
hw = FindWindow(L"Shell_TrayWnd", NULL);
// 2. 获取 explorer.exe 的 pid
GetWindowThreadProcessId(hw, &pid);
// 3. 访问 explorer.exe
hp = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
// 4. 获取指向当前 CTray 对象的指针地址
ctp = GetWindowLongPtr(hw, 0);
于是,我们便能够获取 WndProc 的地址,接下来就可以想办法修改 WndProc 的值了。
“CTray” 对象的 WndProc 函数指针是只读的,我们无法直接修改 WndProc 函数。但是,我们可以构造一个新的 “CTray” 对象,然后调用 SetWindowLong 改变 “Shell_TrayWnd” 的属性,使得Shell_TrayWnd 使用新的 “CTray” 对象,从而执行我们自定义的窗口过程。但是我更推荐此时根据窗口过程的地址实施 hook 而不是替换 CTray,因为替换必须通过发送一条有效的消息到窗口使得 explorer 读取新的结构。SetWindowLong 重写 CTray 结构的行为也可能被杀软当作可疑操作予以拦截。因为此途径曾经被利用于注入 Explorer 或者提权。

通过对此途径的利用,我们成功从 explorer 弹出了一个对话框。

根据上面的思路,我们可以轻松获取 “Shell_TrayWnd” 窗口的窗口过程函数,而不需要通过《最新方案(一)》中的特征码定位,以及本篇所说的 CallWndProc Hook 、窗口子类化等技术。
当你已经获取到处理消息的过程函数时,你就可以拦截所有 WM_COPYDATA 消息并做你想做的事情了。
备注:技术实现代码将在未来更新中提供。
测试代码:使用下面代码获取 “Shell_TrayWnd” 窗口的窗口过程函数,杀软一般不会拦截的,但如果你直接覆盖 CTray 结构,那么就会被拦截。所以,可以直接用这里的代码获取窗口过程的地址,然后用 《最新方案(一)》的 Hook 挂钩窗口过程,这样就不会被拦截了(最多提示进程注入)。
#include <Windows.h>
#include <iostream>
#pragma comment(lib, "user32.lib")
typedef struct _ctray_vtable {
ULONG_PTR vTable;
ULONG_PTR AddRef;
ULONG_PTR Release;
ULONG_PTR WndProc;
} CTray;
int main()
{
CTray ct{};
ULONG_PTR ctp;
HWND hw;
HANDLE hp;
DWORD pid;
SIZE_T wr;
hw = FindWindowA("Shell_TrayWnd", NULL);
GetWindowThreadProcessId(hw, &pid);
hp = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
ctp = GetWindowLongPtrW(hw, NULL);
ReadProcessMemory(hp, (LPVOID)ctp, (LPVOID)&ct.vTable,
sizeof(ULONG_PTR), &wr);
ReadProcessMemory(hp, (LPVOID)ct.vTable, (LPVOID)&ct.AddRef,
sizeof(ULONG_PTR) * 3, &wr);
std::cout << "ct.WndProc: 0x" << std::hex << ct.WndProc << std::endl;
CloseHandle(hp);
return 0;
}

10.0.22631.4169 地址:0x7ff7bd8d0380。
与特征码识别结果比对:

十、总结&更新
本文通过微软提供的 SetWindowsHookEx 钩子注入方法,使用 WH_CALLWNDPROC 等转发 Shell_TrayWnd 窗口的 WM_COPYDATA 消息到我们自定义的窗口,并在我们的窗口中对消息进行进一步的处理。此外,还介绍了检测通知图标是否在 “闪烁” 以及如何完全隐藏图标的实验性方案。这是对《最新方案(一)》中的方法做出的补充,如果你有更好的方案,欢迎在系列文章的评论区中交流。
更新清单:
- 实现通过窗口子类化真正拦截或者修改 WM_COPYDATA 消息(重要)
- 实现系统托盘图标信息的可视化,以列表形式呈现结果(重要)
- 实现判断当前通知图标状态的功能(已初步实现)
- 实现对通知图标的隐藏,尝试已知的多种途径(重要)
- 实现一个能够获知图标的位置并支持对图标顺序修改和分组管理的功能(次要)
- 改进挂钩处理的方式,实现进一步获取部分 APP 自定义气泡提示窗口的消息(次要)
以上内容将在以后更新。
【更新日志】
2024.06.01:修正代码中使用 std::cout 导致在早于 Win 10 的系统上运行存在中文乱码问题,改用 C 语言风格的输出,暂未找到 std::cout 乱码原因。
2024.06.06:提出一个新的技术角度(XAML)。
2024.08.22:更新本文内容,增加了对 “模拟消息发送至托盘图标” 使得托盘图标响应鼠标操作的方法,后期将整合代码给出完整方案。
2024.09.22:更新了通过 Window Extra Bytes 索引获取 CTray 结构,并解析 WndProc 函数的思路,可以轻松实现我们的最终目的。
2024.11.30:补充 XAML 诊断部分的初步实现。
2025.01.01:最终补充 XAML 诊断调试相关的内容。(请注意,从这里开始,我将不再在文章中更新最新的代码)
本文属于原创文章,转载请注明出处:
https://blog.csdn.net/qq_59075481/article/details/136240462。
文章发布于:2024.02.22;
更新于:2024.02.22,2024.03.04,2024.05.08/13,2024.06.02 / 06, 2024.07.10,
2024.08.22 / 23 / 27,2024.09.22,2024.10.26,2024.11.30,2025.01.01。