如何处理插入外部进程窗口中按钮的Windows消息?
我来帮你拆解下这个问题的核心矛盾:你在自己进程里创建了按钮作为第三方窗口的子控件,但按钮的消息默认会流向第三方窗口的线程,再加上CEF自带消息循环的特殊性,导致你收不到点击事件。咱们一步步来解决:
先纠正你代码里的关键错误
你用SetWindowsHookEx的时候犯了一个低级错误——最后一个参数传成了进程ID(dwProcId),但这个参数要求的是线程ID(dwThreadId)。这直接导致钩子根本没挂到正确的线程上,当然没效果。
不过就算你传对了线程ID,跨进程钩子还需要把钩子函数放在DLL里(系统要把DLL注入到第三方进程才能执行钩子逻辑),你直接在自己进程里写Hookproc是行不通的。
更简单的解决方案:用隐藏中转窗口绕开钩子
不需要复杂的钩子,咱们可以创建一个属于你进程的隐藏窗口,把它作为第三方窗口的子窗口,再把按钮放在这个隐藏窗口上。这样按钮的所有消息都会直接发到你的隐藏窗口的消息过程里,完美避开跨进程消息的问题:
// 1. 先注册你的隐藏窗口类(要在初始化时执行) WNDCLASSEX wc = {0}; wc.cbSize = sizeof(WNDCLASSEX); wc.lpfnWndProc = YourHiddenWndProc; wc.hInstance = GetModuleHandle(0); wc.lpszClassName = L"MyHiddenProxyWindow"; RegisterClassEx(&wc); // 2. 找到第三方窗口 HWND hwndTarget = FindWindowEx(0, 0, L"TCustomBaseForm", NULL); if (!hwndTarget) return; // 3. 创建隐藏中转窗口(作为第三方窗口的子窗口) HWND hwndProxy = CreateWindowEx( 0, L"MyHiddenProxyWindow", L"", WS_CHILD | WS_VISIBLE, // 虽然是WS_VISIBLE,但可以把大小设为和按钮一致 435, 45, 35, 45, // 和按钮位置大小一致,避免布局问题 hwndTarget, NULL, GetModuleHandle(0), NULL ); // 4. 在中转窗口上创建按钮 HWND hwndButton = CreateWindowEx( 0L, _T("Button"), L"Btn", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 0, 0, 35, 45, // 相对于中转窗口的坐标,设为0,0即可 hwndProxy, NULL, GetModuleHandle(0), 0 );
然后实现你的隐藏窗口消息过程:
LRESULT CALLBACK YourHiddenWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { case WM_COMMAND: if (HIWORD(wParam) == BN_CLICKED && (HWND)lParam == hwndButton) { // 这里处理按钮点击事件,完全在你的进程里执行 MessageBox(NULL, L"按钮被点击了!", L"提示", MB_OK); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hwnd, msg, wParam, lParam); } return 0; }
适配CEF的消息循环
你之前用while(GetMessage)会阻塞线程,是因为CEF有自己的消息循环机制。如果用的是CefRunMessageLoop,它会接管消息循环;如果是手动调用CefDoMessageLoopWork,你可以把消息处理逻辑集成进去:
// 示例:手动处理消息 + CEF循环 MSG msg; while (true) { // 先处理CEF的消息 CefDoMessageLoopWork(); // 处理自己的消息,用PeekMessage避免阻塞 if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); if (msg.message == WM_QUIT) break; } // 给系统留点时间,避免CPU占用过高 Sleep(10); }
其他可选方案:给按钮设置子类窗口过程
如果你不想用中转窗口,也可以给按钮设置自定义的子类过程,但需要先把你的线程和第三方窗口的线程绑定(让你的线程能收到按钮的消息):
// 获取第三方窗口的线程ID DWORD dwTargetThreadId = GetWindowThreadProcessId(hwndTarget, NULL); // 绑定线程输入队列 AttachThreadInput(GetCurrentThreadId(), dwTargetThreadId, TRUE); // 给按钮设置子类过程 SetWindowSubclass(hwndButton, ButtonSubclassProc, 0, (DWORD_PTR)NULL);
子类过程示例:
LRESULT CALLBACK ButtonSubclassProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData) { switch(msg) { case WM_LBUTTONDOWN: // 处理按钮点击 MessageBox(NULL, L"按钮被点击了!", L"提示", MB_OK); break; case WM_NCDESTROY: // 销毁时移除子类 RemoveWindowSubclass(hwnd, ButtonSubclassProc, 0); break; } return DefSubclassProc(hwnd, msg, wParam, lParam); }
注意:用完后要记得调用AttachThreadInput(GetCurrentThreadId(), dwTargetThreadId, FALSE)解除绑定,避免线程资源泄漏。
最后提醒
- 操作第三方窗口时要小心,尽量不要影响目标应用的正常运行;
- 如果目标应用有反注入或者窗口保护机制,可能需要调整方案;
- 优先选择中转窗口的方案,它最稳定,不需要处理跨进程的复杂逻辑。
内容的提问来源于stack exchange,提问作者iwpz

