如何判断C++ WinAPI中IWebBrowser2的导航已完成?
Got it, let's work through this problem together. Since you can't rely on MFC or ATL, we'll use raw COM interfaces to check navigation completion safely before calling get_LocationUrl.
To safely retrieve the current page URL via get_LocationUrl, you need to confirm the browser has finished navigating first. There are two reliable ways to do this with raw COM: checking the browser's ReadyState property, or listening for the DocumentComplete event (the more robust option for most scenarios).
If you just need to poll for navigation completion at a specific point, you can check the READYSTATE enum from the IWebBrowser2 interface. When it returns READYSTATE_COMPLETE, navigation is done and you can call get_LocationUrl.
Here's a code example:
HRESULT GetCurrentUrl(IWebBrowser2* pBrowser, BSTR* pbstrUrl) { if (!pBrowser || !pbstrUrl) return E_INVALIDARG; READYSTATE readyState; HRESULT hr = pBrowser->get_ReadyState(&readyState); if (FAILED(hr)) return hr; // Only proceed if navigation is fully complete if (readyState != READYSTATE_COMPLETE) return E_PENDING; // Return a pending error to indicate navigation isn't done // Now safely fetch the URL hr = pBrowser->get_LocationUrl(pbstrUrl); return hr; }
For real-time detection of when navigation finishes (especially useful for dynamic pages or pages with frames), you can hook into the DWebBrowserEvents2 event interface. The DocumentComplete event fires when the browser finishes loading a document, making it the perfect trigger to call get_LocationUrl.
First, implement the IDispatch interface to handle the event:
class CWebBrowserEventSink : public IDispatch { private: LONG m_cRefCount; IWebBrowser2* m_pBrowser; public: CWebBrowserEventSink(IWebBrowser2* pBrowser) : m_cRefCount(1), m_pBrowser(pBrowser) { if (m_pBrowser) m_pBrowser->AddRef(); } ~CWebBrowserEventSink() { if (m_pBrowser) m_pBrowser->Release(); } // IUnknown Implementation STDMETHOD(QueryInterface)(REFIID riid, void** ppvObj) override { if (riid == IID_IUnknown || riid == IID_IDispatch || riid == DIID_DWebBrowserEvents2) { *ppvObj = static_cast<IDispatch*>(this); AddRef(); return S_OK; } return E_NOINTERFACE; } STDMETHOD_(ULONG, AddRef)() override { return InterlockedIncrement(&m_cRefCount); } STDMETHOD_(ULONG, Release)() override { ULONG refCount = InterlockedDecrement(&m_cRefCount); if (refCount == 0) delete this; return refCount; } // IDispatch Implementation STDMETHOD(GetTypeInfoCount)(UINT* pCount) override { *pCount = 0; return E_NOTIMPL; } STDMETHOD(GetTypeInfo)(UINT, LCID, ITypeInfo**) override { return E_NOTIMPL; } STDMETHOD(GetIDsOfNames)(REFIID, LPOLESTR* rgszNames, UINT cNames, LCID, DISPID* rgDispIds) override { // Map the DocumentComplete event name to its DISPID if (cNames == 1 && !wcscmp(rgszNames[0], L"DocumentComplete")) { *rgDispIds = DISPID_DOCUMENTCOMPLETE; return S_OK; } return DISP_E_UNKNOWNNAME; } STDMETHOD(Invoke)(DISPID dispId, REFIID, LCID, WORD, DISPPARAMS* pParams, VARIANT*, EXCEPINFO*, UINT*) override { if (dispId == DISPID_DOCUMENTCOMPLETE) { // Verify this is the main frame's completion (optional but useful for frames) IDispatch* pFrameDisp = pParams->rgvarg[1].pdispVal; IWebBrowser2* pFrameBrowser = nullptr; HRESULT hr = pFrameDisp->QueryInterface(IID_IWebBrowser2, (void**)&pFrameBrowser); if (SUCCEEDED(hr)) { if (pFrameBrowser == m_pBrowser) { // Main frame loaded, get the URL BSTR bstrUrl = nullptr; hr = m_pBrowser->get_LocationUrl(&bstrUrl); if (SUCCEEDED(hr) && bstrUrl) { // Do something with the URL (e.g., log it) wprintf(L"Loaded URL: %s\n", bstrUrl); SysFreeString(bstrUrl); } } pFrameBrowser->Release(); } } return S_OK; } };
Then connect the event sink to your browser instance:
HRESULT ConnectBrowserEventSink(IWebBrowser2* pBrowser, DWORD* pdwCookie) { if (!pBrowser || !pdwCookie) return E_INVALIDARG; IConnectionPointContainer* pCPC = nullptr; HRESULT hr = pBrowser->QueryInterface(IID_IConnectionPointContainer, (void**)&pCPC); if (FAILED(hr)) return hr; IConnectionPoint* pCP = nullptr; hr = pCPC->FindConnectionPoint(DIID_DWebBrowserEvents2, &pCP); pCPC->Release(); if (FAILED(hr)) return hr; CWebBrowserEventSink* pSink = new CWebBrowserEventSink(pBrowser); if (!pSink) { pCP->Release(); return E_OUTOFMEMORY; } hr = pCP->Advise(static_cast<IDispatch*>(pSink), pdwCookie); pCP->Release(); if (FAILED(hr)) { pSink->Release(); return hr; } return S_OK; }
- Always manage COM interface reference counts properly (use
AddRef/Releaseto avoid memory leaks). - If using the event sink, make sure your UI thread has an active message loop—events won't fire without it.
- For pages with multiple frames, checking if the frame's
IWebBrowser2matches your main browser instance ensures you only act when the entire page finishes loading.
内容的提问来源于stack exchange,提问作者adziri

