程序是这样:
标准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也能正常进行的方法么?
标准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也能正常进行的方法么?
改后的程序是这样的,其中
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已经被改掉了。
而且这个程序改后在虚拟机上比在物理机上还快……很诡异……
WM_DATA_ARRIVE
WM_SHOW_VIEW
WM_SHOW_RESULT
WM_ERACE_NOTICE
是程序自己定义的MSG,不用处理
WM_TIMER在接收到以后也会调用这块机能,有可能形成递归,所以屏蔽掉了。
实际上我在调试的时候曾经把跳过WM_TIMER的代码给注释掉了,没有效果……到页面B必死……
现在整个程序的结构都不太好变了,如果改成像SOCKET那样的异步处理的话,重写的量太大了……
IWebBroswer2在多线程中使用有隐患么?