测试环境:WinXPSP3 VS2008SP1 IE6 IE8
未验证,[1]这种方法拿出来的Script可不可以被不同的支线线程使用,还是第一次被哪根线程拿的,就只能这根线程用了。
[2]要不要调用CoUninitialize [3]要不要最后释放Script对象。这三个问题,等以后有时间了再折腾,因为,目前的,已经能满足
我的需要了,这三段代码贴出来,主要是给自己看的,免的以后又碰到同样的困难。
分配
void CSnapshot::InitCallBKFuncForHotKey() { boost::lock_guard<boost::mutex> l(g_CallBKFuncInMainThread);//防止两个线程同时初始化m_pStream if(m_pStream!=NULL) { g_log.Add("[%s][%d]已经初始化marshallData!",__FILE__,__LINE__); return; } std::wstring wsTT = OLE2W(m_bstrCallbackFuncName); g_log.Add("[%s][%d][回调函数名称=%s] void CSnapshot::InitCallBKFuncForHotKey() 開始初始化!" ,__FILE__,__LINE__,ws2s(wsTT).c_str()); IDispatch* pScript;//CComPtr<IDispatch> pScript; CComBSTR bstrMember(m_bstrCallbackFuncName); CComPtr<IServiceProvider> ifsp; IWebBrowser2* browser; CComPtr<IHTMLDocument2> spDoc; CComVariant vaResult; UINT nArgErr = (UINT)-1; // initialize to invalid arg HRESULT hr; //取ifsp hr = m_spUnkSite->QueryInterface( &ifsp ); if(hr!=S_OK) { g_log.Add("CSnapshot::CallBKFuncInMainThread 取ifsp失败"); return ; } //取browser hr = ifsp->QueryService(SID_SWebBrowserApp,IID_IWebBrowser2, reinterpret_cast<void **>(&browser)); if(FAILED(hr)) { g_log.Add("CSnapshot::CallBKFuncInMainThread 取browser失败"); return; } //取IHTMLDocument2 hr = browser->get_Document((IDispatch**)&spDoc); if(FAILED(hr)) { g_log.Add("CSnapshot::CallBKFuncInMainThread 取spDoc失败"); return; } browser->Release(); //取Script hr = spDoc->get_Script(&pScript); if(FAILED(hr)) { g_log.Add("CSnapshot::CallBKFuncInMainThread 取Script失败"); } //把Script对象放入容器中,传给支线线程,是因为只有主线程才能拿到script对象。 //注意:刷新IE后,不需要重新去取Script对象,刷新IE不会导致析构。 //只有没客户端继续引用我们的ATL控件,控件内的对象才会被析构 if (pScript) { //Get Function 's dispid hr = pScript->GetIDsOfNames(IID_NULL,&bstrMember,1,LOCALE_SYSTEM_DEFAULT,&m_dispidCallbackFunc); if(hr==S_OK) { //创建IStream对象(容器)用来存放Script对象 hr = CreateStreamOnHGlobal(NULL, TRUE, &m_pStream); if(SUCCEEDED(hr)) { //把Script对象放入IStream对象(容器)中,MSHLFLAGS_TABLESTRONG标志意味着可以多次去取 hr = ::CoMarshalInterface(m_pStream, IID_IDispatch, pScript,MSHCTX_INPROC,NULL,MSHLFLAGS_TABLESTRONG); if(FAILED(hr)) { m_pStream->Release(); m_pStream = NULL; g_log.Add("[%s][%d]CoMarshalInterface=[%x]",__FILE__,__LINE__,hr); } else { g_log.Add("script放入m_pStream(容器)成功!"); } } } else { g_log.Add("[%s][%d] void CSnapshot::InitCallBKFuncForHotKey() 取回調函數的ID失敗!" ,__FILE__,__LINE__); } } }
使用
void CSnapshot::CallBKFuncForHotKey(int status,std::wstring fileName)//调用回调函数 { boost::lock_guard<boost::mutex> l(g_CallBKFuncInMainThread); DISPPARAMS dispparams; EXCEPINFO excepInfo; CComVariant vaResult; HRESULT hr; UINT nArgErr = (UINT)-1; // initialize to invalid arg if(m_pStream!=NULL) { IDispatch* ipScript = NULL;//在支线,线程中每次调用都要取一次,否则ipScript对象会报E_ACCESSDENIED错误 g_log.Add("CSnapshot::CallBKFuncForHotKey,从m_pStream取ipScript对象"); ::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);//如果没有这段代码,在IE6中调用CoUnmarshalInterface会出错 LARGE_INTEGER li = {0}; hr = m_pStream->Seek(li, STREAM_SEEK_SET, NULL);//不seek调用CoUnmarshalInterface会失败 if(FAILED(hr)) { g_log.Add("pStream->Seek(li, STREAM_SEEK_SET, NULL); => %x",hr); goto _EXIT; } hr = CoUnmarshalInterface(m_pStream,IID_IDispatch,(void**)&ipScript); if(FAILED(hr)) { g_log.Add("[%s][%d]CoUnmarshalInterface=[%x]",__FILE__,__LINE__,hr); goto _EXIT; } m_ipScriptForHotKey = ipScript;//保留m_ipScriptForHotKey是为了在析构(或进程要退出)的时候释放它。 } else { g_log.Add("CSnapshot::CallBKFuncForHotKey,还没有m_pStream对象"); return; } if(!m_ipScriptForHotKey) { g_log.Add("CSnapshot::CallBKFuncForHotKey拿到的ipScript对象非法"); goto _EXIT; } memset(&dispparams,0,sizeof(dispparams)); dispparams.cArgs = 2;//表示有2个参数 dispparams.rgvarg = new VARIANT[dispparams.cArgs];//表示对参数数组的引用。 //注意,参数顺序是反的。 //CComBSTR bstr = "111"; // back reading //bstr.CopyTo(&dispparams.rgvarg[i].bstrVal); dispparams.rgvarg[0].bstrVal = ::W2BSTR(fileName.c_str()); dispparams.rgvarg[0].vt = VT_BSTR; dispparams.rgvarg[1].intVal = status; dispparams.rgvarg[1].vt = VT_I4; dispparams.cNamedArgs =0;//表示命名参数的计数。 memset(&excepInfo,0,sizeof(excepInfo)); //进行调用 DWORD id = GetCurrentThreadId(); g_log.Add("[%s][%d][%d]Before Invoke",__FILE__,__LINE__,id); hr = m_ipScriptForHotKey->Invoke(m_dispidCallbackFunc, IID_NULL,0,DISPATCH_METHOD,&dispparams,&vaResult,&excepInfo,&nArgErr); g_log.Add("[%s][%d][%d]After Invoke",__FILE__,__LINE__,id); if(FAILED(hr)) { if(hr==E_ACCESSDENIED) g_log.Add("CSnapshot::CallBKFuncForHotKey 回调失败,E_ACCESSDENIED"); else if(hr==0x80020101) g_log.Add("CSnapshot::CallBKFuncForHotKey 回调失败,JS函数中有语法错误"); else if(hr==0x80020003) g_log.Add("CSnapshot::CallBKFuncForHotKey 回调失败,JS函数没有找到"); else g_log.Add("CSnapshot::CallBKFuncForHotKey 回调失败,hr=[%x]",hr); } _EXIT: ::CoUninitialize();//对应CoInitializeEx函数 }
释放
CSnapshot::~CSnapshot() { if(m_pStream!=NULL) { LARGE_INTEGER li = {0}; HRESULT hr = m_pStream->Seek(li, STREAM_SEEK_SET, NULL);//不Seek释放会失败! if(FAILED(hr)) { g_log.Add("[%s][%d]seek失败[%x]!", __FILE__,__LINE__,hr); } hr = CoReleaseMarshalData(m_pStream); if(FAILED(hr)) { g_log.Add("[%s][%d]释放MarshalData失败[%x]!", __FILE__,__LINE__,hr); } m_pStream = NULL; } if(m_ipScriptForHotKey!=NULL) { m_ipScriptForHotKey->Release(); m_ipScriptForHotKey = NULL; } g_log.Add("[%s][%d]CSnapshot::~CSnapshot()析构完成!",__FILE__,__LINE__); }
参考资料
[1]《CoGetInterfaceAndReleaseStream 退出窗体报错 解决方法》
http://blog.csdn.net/god00/article/details/6648078
[2]《超酷代码:来自 COM 经验的八个教训》
http://www.microsoft.com/china/MSDN/library/windev/COMponentdev/CDwickedtoc.mspx?mfr=true
[3]《如何跨单元中 Visual C++ 封送接口》
support.microsoft.com/kb/q206076/