如何将WM_KEYDOWN消息传递给IWebBrowser2实例?

我使用IWebBrowser2接口在父应用程序中加载embedded式浏览器。 我的代码被编译为DLL,即浏览器组件在运行时通过插件接口dynamic加载。

我遇到的问题是加载我的dll的应用程序陷阱某些keydown消息,因此他们没有达到我的IWebBrowser2实例。

因此我使用我的dll中的SetWindowsHookEx() API捕获这些消息。

那么我怎样才能将WM_KEYDOWNWM_CHAR消息转发到我的IWebBrowser2实例,以便它们可以被用来在浏览器中的焦点文本框中input文本?

我相信这个问题在主机应用程序的消息队列实现中,一些消息被处理而不是传递,例如实现热键。 既然你不能改变他们的代码,挂钩消息队列听起来就像是一个合理的方法。

以下代码片段演示了问题和解决方案:

 #define WINDOW_CLASS _T("StackOverflow_41911104") HINSTANCE g_Instance = 0; HHOOK g_Hook = 0; HWND g_TargetWindow = 0; LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; } HWND CreateMainWindow() { WNDCLASSEXW wcex; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = g_Instance; wcex.hIcon = nullptr; wcex.hCursor = LoadCursor(nullptr, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wcex.lpszMenuName = nullptr; wcex.lpszClassName = WINDOW_CLASS; wcex.hIconSm = nullptr; ATOM windowClass = RegisterClassExW(&wcex); HWND mainWindow = CreateWindowW(WINDOW_CLASS, WINDOW_CLASS, WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, 400, 400, nullptr, nullptr, g_Instance, nullptr); g_TargetWindow = CreateWindow(_T("Edit"), nullptr, WS_CHILD | WS_VISIBLE | WS_BORDER | ES_MULTILINE, 0, 0, 300, 300, mainWindow, (HMENU)1000, g_Instance, nullptr); return mainWindow; } HACCEL CreateAccelerators() { ACCEL acceleratorsList[] = { {FVIRTKEY, 'R', 1000}, {FVIRTKEY, 'T', 1001}, }; return CreateAcceleratorTable(acceleratorsList, _countof(acceleratorsList)); } void ProcessHookMessage(MSG* a_Message) { // Only affect our window and its children if ((g_TargetWindow != a_Message->hwnd) && !IsChild(g_TargetWindow, a_Message->hwnd)) return; // Deliver the message directly TranslateMessage(a_Message); DispatchMessage(a_Message); // Do not allow to process this message the second time a_Message->message = WM_NULL; } LRESULT CALLBACK Hook_GetMsgProc(int a_Code, WPARAM a_WParam, LPARAM a_LParam) { if ((HC_ACTION == a_Code) && (PM_REMOVE == a_WParam)) ProcessHookMessage((MSG*)a_LParam); return CallNextHookEx(g_Hook, a_Code, a_WParam, a_LParam); } void InstallHook() { g_Hook = SetWindowsHookEx(WH_GETMESSAGE, Hook_GetMsgProc, g_Instance, GetCurrentThreadId()); } int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR, int) { g_Instance = hInstance; HWND mainWindow = CreateMainWindow(); HACCEL hAccelTable = CreateAccelerators(); InstallHook(); MSG msg; while (GetMessage(&msg, nullptr, 0, 0)) { // The problem lurks here: some messages are handled directly and never reach the target window if (TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) continue; TranslateMessage(&msg); DispatchMessage(&msg); } return 0; } 

在这个代码片段中,如果您注释掉InstallHook()调用,将无法打印RT ,因为这些键用于加速器表。 但是,使用InstallHook() ,挂钩强制正常的消息队列行为,一切正常。

建议的钩子代码有以下几点兴趣:

  1. 它只影响你的窗口,没有别的
  2. 它和通常的消息队列一样工作,而不是搞乱SendMessage / PostMessage
  3. 它可以防止托管应用程序未拦截邮件的双重效果

这听起来像根本问题是,你的窗口是在主机应用程序的窗口不同的线程,这可能会混淆焦点状态。 您可以轻松进入主窗口和托管窗口都相信自己有重点的情况。

解决方案是在与父窗口相同的线程上创建窗口,如果这是不可能的(例如,由于插件模型或因为插件在单独的进程中运行),请使用AttachThreadInput 。

多年以来,我还没有使用网络浏览器控件,但是我回想起很久以前的一个项目,当我们在另一个过程中将网页浏览器控件添加为窗口的子项时,出现了类似的问题。 使用AttachThreadInput解决了很多的错误。 缺点是两个线程中的错误(如挂起)都会挂起两个线程。 在拆解过程中,我们也必须小心拆开线。

看起来,这比平常发送信息有点棘手:

首先,您将需要获取网页浏览器的原位活动对象( https://msdn.microsoft.com/en-us/library/windows/desktop/ms691299(v=vs.85).aspx ),然后调用TranslateAcceleratorhttps://msdn.microsoft.com/en-us/library/windows/desktop/ms693360 ( v=vs.85 ) .aspx )。

一些非常高级的伪代码看起来像:

 HRESULT hr; IOleInPlaceActiveObject* pIOIPAO; hr = webBrowser2->QueryInterface(webBrowser2, &IID_IOleInPlaceActiveObject, (LPVOID*)&pIOIPAO); if (SUCCEEDED(hr)) { result = pIOIPAO->lpVtbl->TranslateAccelerator(pIOIPAO, msg); } 

其中msg是消息( MSG ),您应该相应地填充, webBrowser2是您的IWebBrowser2

PS:没有尝试这个代码,使用在自己的风险:)