获取 Windows 系统托盘图标信息的最新方案(三)

目录

前言

一、实现 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。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/402594.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

“目标检测”任务基础认识

“目标检测”任务基础认识 1.目标检测初识 目标检测任务关注的是图片中特定目标物体的位置。 目标检测最终目的&#xff1a;检测在一个窗口中是否有物体。 eg:以猫脸检测举例&#xff0c;当给出一张图片时&#xff0c;我们需要框出猫脸的位置并给出猫脸的大小&#xff0c;如…

MySQL|MySQL基础(求知讲堂-学习笔记【详】)

MySQL基础 目录 MySQL基础一、 MySQL的结构二、 管理数据库1&#xff09;查询所有的数据库2&#xff09;创建数据库3&#xff09;修改数据库的字符编码4&#xff09;删除数据库5&#xff09;切换操作的数据库 三、表的概念四、字段的数据类型4.1 整型4.2 浮点型(float和double)…

MongoDB的介绍和使用

目录 一、MongoDB介绍 二、MongoDB相关概念 三、MongoDB的下载和安装 四、SpringBoot 整合 MongoDB 一、MongoDB介绍 MongoDB是一种NoSQL数据库管理系统&#xff0c;采用面向文档的数据库模型。它以C语言编写&#xff0c;旨在满足大规模数据存储和高性能读写操作的需求。Mo…

如何将 .NET Aspire 应用程序部署到 Azure Container App

作者&#xff1a;Jiachen Jiang 排版&#xff1a;Alan Wang 最近发布的用于构建云原生应用程序的 .NET Aspire 与 Azure Container App&#xff08;ACA&#xff09;非常匹配。.NET Aspire 旨在轻松管理由一系列相互依赖的微服务组成的应用程序。Azure Container App 专为微服务…

Vue样式绑定

1. 绑定 HTML class ①通过class名称的bool值判断样式是否被启用 <template><!--通过样式名称是否显示控制样式--><div :class"{ haveBorder: p.isBorder, haveBackground-color: p.isBackgroundcolor }">此处是样式展示区域</div><br /…

消息队列MQ 保证消息不丢失(消息可靠性)

文章目录 概述RabbitMQ 怎么避免消息丢失&#xff08;可靠传输&#xff09;RocketMQ 怎么确保消息不丢失Kafka 怎么保证消息不丢失activeMQ 怎么避免消息丢失MQ 宕机了消息是否会丢失线上服务宕机时&#xff0c;如何保证数据100%不丢失吗&#xff1f;消息队列消息持久化 概述 …

猫头虎分享已解决Bug || SyntaxError: Unexpected token < in JSON at position 0

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

Docker本地部署Rss订阅工具并实现公网远程访问

文章目录 1. Docker 安装2. Docker 部署Rsshub3. 本地访问Rsshub4. Linux安装Cpolar5. 配置公网地址6. 远程访问Rsshub7. 固定Cpolar公网地址8. 固定地址访问 Rsshub是一个开源、简单易用、易于扩展的RSS生成器&#xff0c;它可以为各种内容生成RSS订阅源。 Rsshub借助于开源社…

一.CMake的工具安装包的下载

下载 cmake工具 进入cmake官网下载cmake&#xff1a;Download CMake 可以选择最新的Windows x64 installer 的包&#xff08;下载需要科学上网&#xff09;

nginx之web性能location优先级

4.2 event事件 events {worker_connections 65536; #设置单个工作进程的最大并发连接数use epoll;#使用epoll事件驱动&#xff0c;Nginx支持众多的事件驱动&#xff0c;比如:select、poll、epoll&#xff0c;只能设置在events模块中设置。accept_mutex on; #on为同一时刻一个…

【刷题】牛客 JZ64 求1+2+3+...+n

刷题 题目描述思路一 &#xff08;暴力递归版&#xff09;思路二 &#xff08;妙用内存版&#xff09;思路三 &#xff08;快速乘法版&#xff09;思路四 &#xff08;构造巧解版&#xff09;Thanks♪(&#xff65;ω&#xff65;)&#xff89;谢谢阅读&#xff01;&#xff01…

Flutter 3.19.0 版本新特性

其实在每个版本的更新中呢&#xff0c;都会合并很多很多的这个合并请求、还有开发建议&#xff0c;那么本版本的也不例外&#xff0c;社区官方发布的公告是合并了168个社区成员的1429个拉请求。 当然&#xff0c;如果你的时间允许的话&#xff0c;你可以去查看一下这些请求&am…

Linux篇:进程

一. 前置知识 1.1冯诺依曼体系结构 我们常见的计算机&#xff0c;如笔记本。我们不常见的计算机&#xff0c;如服务器&#xff0c;大部分都遵守冯诺依曼体系 为什么计算机要采用冯诺依曼体系呢&#xff1f; 在计算机出现之前有很多人都提出过计算机体系结构&#xff0c;但最…

linux 网络服务小实验

实验图和要求&#xff1a; 1&#xff09;网关服务器&#xff1a;ens36&#xff1a;12.0.0.254/24&#xff0c;ens33&#xff1a;192.168.44.254/24&#xff1b;Server1&#xff1a;192.168.44.20/24&#xff1b;PC1和Server2&#xff1a;自动获取IP&#xff1b;交换机无需配置。…

仿慕课网html+css+js特效

如果阅读有疑问的话&#xff0c;欢迎评论或私信&#xff01;&#xff01; 本人会很热心的阐述自己的想法&#xff01;谢谢&#xff01;&#xff01;&#xff01; 文章目录 文件目录内容图片部分代码下载路径 文件目录 –css ----index –images –js ----index.js ----jquery.…

MySQL 查询遇到Illegal mix of collations的错误

业务同学线上业务执行 SQL 时报错&#xff0c; ### Error querying database. Cause: java.sql.SQLException: Illegal mix of collations (utf8_general_ci,IMPLICIT) and (utf8mb4_0900_ai_ci,COERCIBLE) for operation like含义是对like操作非法混合了排序规则(utf8mb4_ge…

汉诺塔问题—java详解(附源码)

来源及应用 相传在古印度圣庙中&#xff0c;有一种被称为汉诺塔(Hanoi)的游戏。该游戏是在一块铜板装置上&#xff0c;有三根杆(编号A、B、C)&#xff0c;在A杆自下而上、由大到小按顺序放置64个金盘(如图1)。游戏的目标&#xff1a;把A杆上的金盘全部移到C杆上&#xff0c;并仍…

【人工智能学习思维脉络导图】

曾梦想执剑走天涯&#xff0c;我是程序猿【AK】 目录 知识图谱1. 基础知识2.人工智能核心概念3.实践与应用4.持续学习与进展5.挑战与自我提升6.人脉网络 知识图谱 人工智能学习思维脉络导图 1. 基础知识 计算机科学基础数学基础&#xff08;线性代数、微积分、概率论和统计学…

PNG图片压缩-UPNG.js参数说明及示例

UPNG.js是一个非常轻量且高效的库&#xff0c;用于处理PNG图像。它可以编码和解码PNG图片&#xff0c;同时支持压缩和解压缩功能。特别适合在前端项目中处理图像&#xff0c;尤其是在需要优化图像大小而不牺牲质量时。 UPNG.encode()函数是UPNG.js中用于将图像数据编码成PNG格…

第三十九天| 62.不同路径、63. 不同路径 II

Leetcode 62.不同路径 题目链接&#xff1a;62 不同路径 题干&#xff1a;一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “…