接下来,添加将事件路由到新的 OnDocumentComplete 事件处理程序方法的 ATL 宏,该事件处理程序方法采用的是 DocumentComplete 事件所定义的相同参数和顺序。将以下代码放置到该类的公共部分。
| 以下是引用片段: BEGIN_SINK_MAP(CHelloWorldBHO) SINK_ENTRY_EX(1, DIID_DWebBrowserEvents2, DISPID_DOCUMENTCOMPLETE, OnDocumentComplete) END_SINK_MAP() // DWebBrowserEvents2 void STDMETHODCALLTYPE OnDocumentComplete(IDispatch *pDisp, VARIANT *pvarURL); |
提供给 SINK_ENTRY_EX 宏 (1) 的数字指的是 IDispEventImpl 类定义的第一个参数,在必要时用于区分来自不同接口的事件。另请注意,不能从该事件处理程序返回值;这是因为 Internet Explorer 无论怎样都会忽略从 Invoke 返回的值。
最后,添加一个专用成员变量,以跟踪各对象是否已建立了与浏览器的连接。
| 以下是引用片段: private: BOOL m_fAdvised; HelloWorldBHO.cpp |
要通过事件映射将事件处理程序连接到浏览器,可在处理 SetSite 期间调用 DispEventAdvise。同样,使用 DispEventUnadvise 断开连接。
以下是 SetSite 的新实现:
| 以下是引用片段: STDMETHODIMP CHelloWorldBHO::SetSite(IUnknown* pUnkSite) { if (pUnkSite != NULL) { // 缓存指向 IWebBrowser2 的指针。 HRESULT hr = pUnkSite->QueryInterface(IID_IWebBrowser2, (void **)&m_spWebBrowser); if (SUCCEEDED(hr)) { // 注册以从 DWebBrowserEvents2 中汇集事件。 hr = DispEventAdvise(m_spWebBrowser); if (SUCCEEDED(hr)) { m_fAdvised = TRUE; } } } else { // 取消注册事件汇。 if (m_fAdvised) { DispEventUnadvise(m_spWebBrowser); m_fAdvised = FALSE; } // 在此释放缓存的指针和其他资源。 m_spWebBrowser.Release(); } // 调用基类实现。 return IObjectWithSiteImpl::SetSite(pUnkSite); } |
最后,添加一个简单的 OnDocumentComplete 事件处理程序。
| 以下是引用片段: void STDMETHODCALLTYPE CHelloWorldBHO::OnDocumentComplete(IDispatch *pDisp, VARIANT *pvarURL) { // 从站点检索顶级窗口。 HWND hwnd; HRESULT hr = m_spWebBrowser->get_HWND((LONG_PTR*)&hwnd); if (SUCCEEDED(hr)) { // 加载页面时输出消息框。 MessageBox(hwnd, L"大家好!", L"BHO", MB_OK); } } |
请注意,消息框会将站点的顶层窗口用作其父窗口,而不仅仅是通过该参数传递 NULL。在 Internet Explorer 6 中,NULL 父窗口并不阻止应用程序,也就是说,在消息框等待用户输入时用户可以继续与浏览器交互。在某些情况下,这会导致浏览器挂起或崩溃。在 BHO 需要显示 UI 的这种少见情况下,应始终通过指定指向父窗口的句柄来确保该对话框为应用程序模态。

