程序是这样:
标准C++,没有使用MFC
 
使用IWebBrowser2进行一个HTML文件的显示,由于CSS特效很多,所以加载时间有可能长达3-4秒。为保证不会出现白屏或者之前页面的残存,需要判断是否加载完毕,如果加载完毕,则会用SetWindowPos将窗体显示出来。
 
代码如下:
建立窗体
    m_hWeb = CreateWindowEx(
        WS_EX_TOOLWINDOW,
        "AtlAxWin90",
        "Shell.Explorer.2",
        WS_POPUP | WS_VISIBLE ,
        m_nLeft,
        m_nTop,
        m_nWidth,
        m_nHeight,
        NULL,
        NULL,
        NULL,
        NULL
        );获取IWebBrowser2
    CComQIPtr<IWebBrowser2> m_pIE;
    lAtlRet = AtlAxGetControl(m_hWeb, &pDef);
    if (lAtlRet != S_OK)
    {
        return FALSE;
    }
   
    m_pIE = pDef;
    if (m_pIE == NULL)
    {
        return FALSE;
    }
 
实现IDispatch接口
interface IWebEvent : public IDispatch
{
public:
    HANDLE m_hEventHandle;
    IDispatch* m_IDispatch;
    void setIDispatch(IDispatch* IDispatch)
    {
        m_IDispatch = IDispatch;
    }
    void setHandle(HANDLE handle)
    {
        m_hEventHandle = handle;
    }
 
    //IDispatch
    HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void ** ppvObject)
    {
        if(IsEqualIID(iid, IID_IUnknown)||
            IsEqualIID(iid, IID_IDispatch)||
            IsEqualIID(iid, DIID_DWebBrowserEvents2))
        {
            *ppvObject = this;
        }
        else
        {
            *ppvObject = NULL;
            return E_NOINTERFACE;
        }
        return S_OK;
    }
 
    ULONG STDMETHODCALLTYPE AddRef(void)
    {
        return 1;
    }
    ULONG STDMETHODCALLTYPE Release(void)
    {
        return 0;
    }
    HRESULT STDMETHODCALLTYPE GetTypeInfo(unsigned int iTInfo, LCID lcid, ITypeInfo FAR* FAR* ppTInfo)
    {
        if (ppTInfo == NULL)
        {
            return E_INVALIDARG;
        }
        *ppTInfo = NULL;
        if(iTInfo != 0)
        {
            return DISP_E_BADINDEX;
        }
        return S_OK;
    }
    HRESULT STDMETHODCALLTYPE GetTypeInfoCount(unsigned int FAR* pctinfo)
    {
        return E_NOTIMPL;
    }
    HRESULT STDMETHODCALLTYPE GetIDsOfNames(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgDispId)
    {
        return E_NOTIMPL;
    }
    HRESULT STDMETHODCALLTYPE Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR* pDispParams, VARIANT FAR* pVarResult, EXCEPINFO FAR* pExcepInfo, unsigned int FAR* puArgErr)
    {
        switch(dispIdMember)
        {
        case DISPID_DOCUMENTCOMPLETE://DocumentComplete
            char buff[256];
            memset(buff,0,256);
            wcstombs(buff,pDispParams->rgvarg[0].pvarVal->bstrVal,256);
            if (m_IDispatch == (IDispatch*)pDispParams->rgvarg[1].ppdispVal)
            {
                SetEvent(m_hEventHandle);
            }
            break;
        default:
            break;
        }
        return S_OK;
    }
};
设置链接点
    m_hDocComplete = CreateEvent(NULL, FALSE, FALSE, NULL);
    m_cWebEvent = new IWebEvent();
    m_cWebEvent->QueryInterface(IID_IDispatch, (void **)&m_cWebEvent);
    m_cWebEvent->setHandle(m_hDocComplete);
    IConnectionPointContainer *spCPC;
    hRet = m_pIE->QueryInterface(IID_IConnectionPointContainer,(void**)&spCPC);
    if(FAILED(hRet))
    {
        MessageBox(NULL, "m_pIE->QueryInterface", "m_pIE->QueryInterface", NULL);
    }
 
    IConnectionPoint *spCP;
    hRet = spCPC->FindConnectionPoint(DIID_DWebBrowserEvents2,&spCP);
    if(FAILED(hRet))
    {
        MessageBox(NULL, "spCPC->FindConnectionPoint", "spCPC->FindConnectionPoint", NULL);
    }
    DWORD cookie;
    hRet = spCP->Advise(m_cWebEvent, &cookie);
    if(FAILED(hRet))
    {
        MessageBox(NULL, "spCP->Advise", "spCP->Advise", NULL);
    }
    m_pIE->QueryInterface(IID_IDispatch,(void**)&lpWBDisp);
    m_cWebEvent->setIDispatch(lpWBDisp);
等待DOCUMENTCOMPLETE事件中设置的EVENT
        CComVariant vUrl(chHtmlFileNameBuffer);
        CComVariant vEmpty;
        hRet = m_pIE->put_Visible(VARIANT_TRUE);
        if (hRet != S_OK)
        {
            return FALSE;
        }
 
        hRet = m_pIE->Navigate2(&vUrl, &vEmpty, &vEmpty, &vEmpty, &vEmpty);
        if (hRet != S_OK)
        {
            return FALSE;
        }
 
        if (g_hTopMost == m_hWeb)
        {
            return TRUE;
        }
 
        WaitForSingleObject(m_hDocComplete, INFINITE);
现在问题是整个程序是靠主线程中MessageLoop进行控制,用GetMessage接到Message后,调用相应的处理。
如果m_pIE->Navigate2和 WaitForSingleObject在主线程中调用的话,在WaitForSingleObject就开始等待DOCUMENTCOMPLETE了。但此时MessageLoop是停止的,m_pIE->Navigate2也没有继续进行(此为推测,因为一直没有进入响应 DOCUMENTCOMPLETE的代码)
如果开一个线程,将m_pIE->Navigate2和WaitForSingleObject在新线程中做,程序正常。
但是多开一个线程,就有可能埋藏一些隐含问题,不知道现在使用的这些东西有没有线程安全的隐患。
或者有高手知道在一个线程上即能实现等待DOCUMENTCOMPLETE而且MessageLoop也能正常进行的方法么?

解决方案 »

  1.   

    MSDN上Sheng Jiang的思路是用MsgWaitForMultipleObjects
    改后的程序是这样的,其中
             case WM_DATA_ARRIVE:
             case WM_SHOW_VIEW:
             case WM_SHOW_RESULT:
             case WM_ERACE_NOTICE:
             case WM_TIMER:
                break;
    的目的是保证自定义的MSG不会让程序再次进入这个方法。
    while(!EndLoop)
        {
         switch(MsgWaitForMultipleObjects(1, &m_hDocComplete, FALSE, INFINITE, QS_ALLINPUT))
         {
         case WAIT_OBJECT_0:
            EndLoop = true;
            break;
         case WAIT_OBJECT_0 + 1:
            if (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))
            {
             switch(msg.message)
             {
             case WM_DATA_ARRIVE:
             case WM_SHOW_VIEW:
             case WM_SHOW_RESULT:
             case WM_ERACE_NOTICE:
             case WM_TIMER:
                break;
             case WM_QUIT:
                EndLoop = true;
                break;
             default:
                PeekMessage(&msg, NULL, 0, 0, PM_REMOVE);
                if (msg.hwnd == NULL)
                {
                 WriteTime("Debug.log", "%d Message", msg.message);
                 msg.hwnd = getViewControllerInstance()->getHwnd();
                }
                WriteTime("Debug.log", "%d Message PeekMessage", msg.message);
                TranslateMessage(&msg);
                DispatchMessage(&msg);
                break;
             }
            }
            break;
         }
        }
    这样改之后,小文件没问题,可以正常切换,但是如果显示过一个复杂的HTML,然后再让程序显示A,B两个页面,即B页面使用的是大页面当时使用的窗体。这个时候A是正常的,但B就无法显示了,不过碰碰键盘或者晃晃鼠标有几率能正常显示出来。我在主线程的回调和上面的PeekMessage加了输出,获取的结果如下
    1124 Message windowProc: 14:33:02.012    <---大页面开始加载
    32770 Message PeekMessage: 14:33:02.012
    1792 Message PeekMessage: 14:33:02.012
    32770 Message PeekMessage: 14:33:02.102
    32770 Message PeekMessage: 14:33:06.048
    1125 Message windowProc: 14:33:06.048
    1126 Message windowProc: 14:33:06.048    <---大页面显示完毕
    1124 Message windowProc: 14:33:09.423    <---页面A开始加载
    32770 Message PeekMessage: 14:33:09.433
    1792 Message PeekMessage: 14:33:09.433
    32770 Message PeekMessage: 14:33:09.443
    32770 Message PeekMessage: 14:33:09.533
    1125 Message windowProc: 14:33:09.533
    1126 Message windowProc: 14:33:09.533    <---页面A显示完毕
    136 Message windowProc: 14:33:09.533
    133 Message windowProc: 14:33:09.533
    20 Message windowProc: 14:33:09.533
    15 Message windowProc: 14:33:09.543
    1124 Message windowProc: 14:33:10.685    <---页面B开始加载
    32770 Message PeekMessage: 14:33:10.685
    1792 Message PeekMessage: 14:33:10.695
    32770 Message PeekMessage: 14:33:10.735
    32770 Message PeekMessage: 14:33:11.876
    512 Message PeekMessage: 14:33:11.886
    32770 Message PeekMessage: 14:33:11.886
    256 Message PeekMessage: 14:33:17.214
    257 Message PeekMessage: 14:33:17.314
    512 Message PeekMessage: 14:33:17.314
    32770 Message PeekMessage: 14:33:17.314
    28 Message windowProc: 14:33:17.314
    136 Message windowProc: 14:33:18.015
    133 Message windowProc: 14:33:18.015
    20 Message windowProc: 14:33:18.015
    原来正常情况下,应该是A,B的显示速度会很快,考虑到加载B时会将原来大页面的内容干掉,所以大概有1秒多的延迟,但现在来看,程序卡在了页面B的 MSGWAIT那里了……不明白为什么……如果这个方案解决不了的话,多线程版的会有什么隐患呢?string已经被改掉了。
    而且这个程序改后在虚拟机上比在物理机上还快……很诡异……
      

  2.   

    为什么要跳过默认的WM_DATA_ARRIVE、WM_SHOW_VIEW: WM_SHOW_RESULT、WM_ERACE_NOTICE和 WM_TIMER的处理? 
      

  3.   

    回楼上:
    WM_DATA_ARRIVE
    WM_SHOW_VIEW
    WM_SHOW_RESULT
    WM_ERACE_NOTICE
    是程序自己定义的MSG,不用处理
    WM_TIMER在接收到以后也会调用这块机能,有可能形成递归,所以屏蔽掉了。
    实际上我在调试的时候曾经把跳过WM_TIMER的代码给注释掉了,没有效果……到页面B必死……
      

  4.   

    估计是你屏蔽WM_TIMER的问题。在COM事件处理中重新进入消息循环的确会有递归,用异步进行重写吧。
      

  5.   

    楼上指的异步的意思是用多线程么?
    现在整个程序的结构都不太好变了,如果改成像SOCKET那样的异步处理的话,重写的量太大了……
    IWebBroswer2在多线程中使用有隐患么?