【问题标题】:How to record/output messages from a hooked DLL in Win32?如何从 Win32 中的挂钩 DLL 记录/输出消息?
【发布时间】:2021-04-13 14:07:43
【问题描述】:

我有一个 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++ 中所做的那样?

【问题讨论】:

  • 你可以试试 Mark Russinovich 的DebugView utility
  • 正如我所提到的,我确实使用该程序来查看调试输出以及 MSVS 内部
  • 哦,原来如此 - 抱歉。

标签: c++ winapi dll


【解决方案1】:

您的dllTest() 函数与CreateThread() 期望的its thread callback 的签名不匹配。因此,您的代码在接触 DLL 之前就已未定义的行为

去掉(LPTHREAD_START_ROUTINE) 类型转换,你会看到编译器抱怨不匹配。正确的声明是:

DWORD WINAPI dllTest(LPVOID)

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

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

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

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

【讨论】:

  • 我做了这些更改,我仍然得到完全相同的行为 - 调试字符串输出不会捕获其他程序消息,文件输出会捕获程序但会导致程序崩溃。为方便起见,我将消息转换为字符串,以便阅读。
  • @use 您可以让 Visual Studio 的调试器使用 Watch 窗口将消息 ID 转换为符号常量。只需添加有问题的值(例如变量或常量值),然后添加 ,wm 格式选项,例如 pCWP-&gt;message,wm。这让您不必拥有一个全球性的std::unordered_map。该对象的构造函数完全有可能导致崩溃。您当然不想在加载程序锁下运行任意代码(顺便说一句,确实如此)。
  • @iinspectable 谢谢,这种方法似乎使它稳定,我现在可以看到文件中的所有消息,如果你写一个,我会接受它作为答案。
  • @use 我不相信删除全局(使用非平凡的 c'tor)就足够了。仍然在加载程序锁下执行对loadMsg 的调用,这很容易使进程死锁。你也删除了吗?
  • 是的,我完全删除了与该地图相关的所有内容,包括函数及其调用。
猜你喜欢
  • 2011-02-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多