测试环境: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.youkuaiyun.com/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/