这是第一次在CSDN上写文章,经过了几天的研究,终于把COM 多线程发送连接点事件实现。由于实现的比较急。设计方案可能考虑不周,望各位如果有更好有方法可以一起进行研究。
开发平台:Microsoft Visual Studio 2005
程序框架:Atl Windows 服务 (EXE)
这里只说重点实现部份,创建工程的相关不在演示,请自行解决。
第一步:
在创建好的服务工程中。先插入一个ATL类,选择ATL 简单对象。键入名称,这里示例为:ThreadEvent
下一步,设置这个类线程模型为模型默认值(单元) 接口(自定义)支持(选择上连接点)如下图
这里关于接口自定义和双重接口的区别
自定义的接口是从 IUnknown 派生的接口。双击左侧的 IThreadEvent 后。在源码中可以看到
...
interface IThreadEvent : IUnknown
这里我不打算我的接口让脚本程序调用。所以选择从IUnknown派生的接口就可以了,即自定义
如果你的接口打算让脚本程序可以访问。哪么就要用双重接口。
我们知道要想获取COM服务器的连接点事件,哪么客户端进行和服务器进行连接。绑定事件,在客户端调用 AfxConnectionAdvise 或自己实现查找接口 时。服务器会通过代理
在服务器中调用 Advise 方法。客户端与服务器断开 客户端调用 AfxConnectionUnadvise 时,服务器会通过代理调用服务器的 Unadvise 方法。
查看我们程序源代码。打开我们新建的这个类的定义
class ATL_NO_VTABLE CThreadEvent :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CThreadEvent, &CLSID_ThreadEvent>,
public IConnectionPointContainerImpl<CThreadEvent>,
public CProxy_IThreadEventEvents<CThreadEvent>,
public IThreadEvent
我们这个类有一个基类是 IConnectionPointContainerImpl , 查看这个类的定义中,看到这个类的基类有 IConnectionPoint 这个类。而我们上边刚说的Advise 方法和 Unadvise 就在这个类中。哪么在我们新建的类中也就继承这些方法。现在我们要做的是重写这两个方法。不让系统让调用原来的这两个方法
在新建的类中手动添加代码 如下图:
你看到这里。如果正在做多线程发送事件这些。哪么你肯定遇到。在线程中调用 Fire_XXX 肯定会失败。
这里的原因是COM的规范所致。说的简单的也不说哪些专业的语言:COM的接口不支持线程调用。即谁创建的接口,只可以谁调用。如果这个接口让其它线程调用就会失败。
哪么问题来了。既然不支持多线程调用。哪么COM就不可能使用多线程了吗?答案是:可以的。还好MS给我们提供了几个API来解决这一问题。
CoMarshalInterThreadInterfaceInStream
CoGetInterfaceAndReleaseStream
CoMarshalInterThreadInterfaceInStream 这个函数的意思把某个接口封装在一个IStream 中。
而CoGetInterfaceAndReleaseStream 是把IStream 再解出来封装之前的接口。
在多线程中。如果A线程创建的接口。并且用CoMarshalInterThreadInterfaceInStream封装后。B线程把A封装的IStream解封出来的接口,就可以正常调用。
由于本人线程基础知识很差。具体的原理就不说了。有兴趣的可以网上找找,相关方面的知识还是很多的。
现在就把这两个API 用在我们的实例中
在我们重写的Advise方法中。我们把这里取到的代理接口进行封装。在Fire_XXX中再把这个接口解出来。进行发送事件。
STDMETHODIMP Advise(IUnknown* pUnkSink,DWORD* pdwCookie)
{
IUnknown* p;
HRESULT hRes = S_OK;
if (pdwCookie != NULL)
*pdwCookie = 0;
if (pUnkSink == NULL || pdwCookie == NULL)
return E_POINTER;
IID iid;
GetConnectionInterface(&iid);
hRes = pUnkSink->QueryInterface(iid, (void**)&p);
if (SUCCEEDED(hRes))
{
Lock();
IStream *pStream = NULL;
CoMarshalInterThreadInterfaceInStream(iid,p,&pStream);
if(pStream)
{
*pdwCookie = (DWORD)m_ThreadStrampArray.Add(pStream);
}
hRes = (*pdwCookie != NULL) ? S_OK : CONNECT_E_ADVISELIMIT;
Unlock();
if (hRes != S_OK)
p->Release();
}
else if (hRes == E_NOINTERFACE)
hRes = CONNECT_E_CANNOTCONNECT;
if (FAILED(hRes))
*pdwCookie = 0;
return hRes;
}
ThreadStrampArray 这个是一个组类,这个类的重要的一点就是Add方法的返回值,在插入第一个数据返回1,第二个数据返回2。
这个返回值正好是客户端是的Cookies.
在重写 Unadvise 方法中释放相关的资源
STDMETHODIMP Unadvise(DWORD dwCookie)
{
HRESULT hRes = S_OK;
IID iid;
GetConnectionInterface(&iid);
Lock();
IUnknown* p = NULL;
LPSTREAM pStream = m_ThreadStrampArray.GetAt(dwCookie-1);
if(pStream)
{
CoGetInterfaceAndReleaseStream(pStream,iid,(void**)&p);
m_ThreadStrampArray.SetAt(dwCookie-1,NULL);
HRESULT hRes = p == NULL ? CONNECT_E_NOCONNECTION : S_OK;
}
Unlock();
if (hRes == S_OK && p != NULL)
p->Release();
return hRes;
}
按下来就是代理函数的实现。以下是一个传送BSTR的例子。如果传送其它类型的参照修改代。
在线程中调用Fire_OnStatus的话必须调用 CoInitialize 和 CoUninitialize 。
HRESULT Fire_OnStatus( BSTR bstrTest)
{
HRESULT hr = S_OK;
T * pThis = static_cast<T *>(this);
CoInitialize(0);
ULONG nCount = pThis->m_ThreadStrampArray.GetCount();
for (ULONG i=0;i<nCount;i++)
{
IID iid;
GetConnectionInterface(&iid);
CComPtr<IUnknown> punkConnection;
//把封存的指针拿到当前线程
pThis->Lock();
LPSTREAM pStream = pThis->m_ThreadStrampArray.GetAt(i);
pThis->Unlock();
if(pStream)
{
CoGetInterfaceAndReleaseStream(pStream,iid,(void**)&punkConnection);
IDispatch * pConnection = static_cast<IDispatch *>(punkConnection.p);
if (pConnection)
{
CComVariant avarParams[1];
avarParams[0] = bstrTest;
avarParams[0].vt = VT_BSTR;
CComVariant varResult;
DISPPARAMS params = { avarParams, NULL, 1, 0 };
hr = pConnection->Invoke(1, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, ¶ms, &varResult, NULL, NULL);
}
//再把指针重新封存起来
pStream = NULL;
CoMarshalInterThreadInterfaceInStream(iid,punkConnection,&pStream);
pThis->m_ThreadStrampArray.SetAt(i,pStream);
}
}
CoUninitialize();
return hr;
}
现在服务可以正常在多线程中发送事件了。
在发送事件函数中值的注意的是这几句。为什么要重新封装。之前不是已经封装好了吗?
因为CoMarshalInterThreadInterfaceInStream在调用这个API时。在解封接口的同时。也把IStream给释放了。所以我们要重新封装起来,要不然下一个事件到达的时候在解封接口的时候就会出错,因为Stream已经释放掉了。。
pStream = NULL;
CoMarshalInterThreadInterfaceInStream(iid,punkConnection,&pStream);
pThis->m_ThreadStrampArray.SetAt(i,pStream);
以上就是全部实现过程。