首页 > 解决方案 > 如何从 Win32 中的挂钩 DLL 记录/输出消息?

问题描述

我有一个 Win32 GUI 应用程序,其中一个线程在按下按钮时启动,如下所示:

...
        switch (wmId)
        {

            case int(BTN::Test) :
                CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)dllTest, &hWnd, 0, NULL);
...

HHOOK hhookSysMsg = nullptr;
HINSTANCE hinstDLL = nullptr;

void dllTest() {
    HOOKPROC hkprcSysMsg;
    
    hinstDLL = LoadLibraryA("F:\\projects\\_dll_hook\\x64\\Release\\hook.dll");

    if (hinstDLL == NULL) {
        prnt("Error loading dll: #%d", PV, GetLastError()); 
        return;
    }

    hkprcSysMsg = (HOOKPROC)GetProcAddress(hinstDLL, "SysMessageProc");
    if (hkprcSysMsg == NULL) {
        prnt("Error getting address of dll.SysMessageProc: #%d", PV, GetLastError());
        return;
    }

    hhookSysMsg = SetWindowsHookExA(
        WH_CALLWNDPROC,
        hkprcSysMsg,
        hinstDLL,
        0);
    if (hhookSysMsg == NULL) {
        prnt("Error hooking dll.SysMessageProc: #%d", PV, GetLastError());
        return;
    }

    Sleep(5000);

    UnhookWindowsHookEx(hhookSysMsg);

    FreeLibrary(hinstDLL);

}

这是DLL代码:

std::unordered_map<int, std::string> map;

void loadMsg() {
    std::fstream stream("F:\\tmp\\_dll\\msg.txt");
    if (stream.is_open()) {
        std::string line;
        while (std::getline(stream, line)) {
            auto index = line.find(' ');
            if (index != std::string::npos)
                map.insert(std::make_pair(std::stoi(line.substr(0, index)), line.substr(index + 1)));
        }
        stream.close();
    }
}

INT APIENTRY DllMain(HMODULE hDLL, DWORD Reason, LPVOID Reserved) {

    switch (Reason) {
    case DLL_PROCESS_ATTACH:
        OutputDebugStringA("DLL attach function called");
        loadMsg();
        break;
    case DLL_PROCESS_DETACH:
        OutputDebugStringA("DLL detach function called");
        break;
    case DLL_THREAD_ATTACH:
        OutputDebugStringA("DLL thread attach function called");
        break;
    case DLL_THREAD_DETACH:
        OutputDebugStringA("DLL thread detach function called");
        break;
    }

    return TRUE;
}

extern "C" __declspec(dllexport) auto SysMessageProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    if (nCode < 0) return CallNextHookEx(NULL, nCode, wParam, lParam);

    if (nCode == HC_ACTION) {

        CWPSTRUCT* pCWP = (CWPSTRUCT*)lParam;

        HWND hWnd = pCWP->hwnd;

        char wclass[256]; wclass[0] = 0;

        if (GetClassNameA(hWnd, wclass, 255) != 0) {

            std::string msg = map[pCWP->message];
            
            if (1 || msg == "WM_NOTIFY") {
                char out[256]; out[0] = 0;
                sprintf_s(out, 255, "class: '%s', msg: %s %08X\n", wclass, msg.c_str(), pCWP->message);
                OutputDebugStringA(out);
            }
        }

    }
    

    return CallNextHookEx(NULL, nCode, wParam, lParam);
}

此代码有效,但它永远不会从其他程序(如记事本)打印出(在 DebugView 或 MSVS 调试输出中)消息。

如果我将 OutputDebugStringA 替换为文件的输出,目标程序和 explorer.exe 经常会崩溃或程序冻结。但是,如果我查看该文件,我可以看到来自不同程序的消息被捕获在那里。

那么在这种情况下显示消息的正确方法是什么,它可以快速并从其他程序捕获消息,就像在 Spy++ 中所做的那样?

标签: c++winapidll

解决方案


您的函数与其线程回调的预期dllTest()签名不匹配。因此,您的代码甚至在触及 DLL 之前就具有未定义的行为。CreateThread()

摆脱(LPTHREAD_START_ROUTINE)类型转换,您将看到编译器抱怨不匹配。正确的声明是:

DWORD WINAPI dllTest(LPVOID)

您的 DLLSysMessageProc()函数声明不正确。也。所以这是未定义行为的另一个来源。正确的声明是:

extern "C" __declspec(dllexport) LRESULT CALLBACK SysMessageProc(int nCode, WPARAM wParam, LPARAM lParam)

此外,没有理由转换pCWP->message为 astd::string只是为了比较它的值。您可以按原样进行比较:

if (1 || pCWP->message == WM_NOTIFY)

推荐阅读