COM 使用多线程发送连接点事件的实现

本文介绍如何在COM服务中实现多线程发送连接点事件,包括使用CoMarshalInterThreadInterfaceInStream和CoGetInterfaceAndReleaseStream解决线程间接口调用问题。
摘要由CSDN通过智能技术生成

这是第一次在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, &params, &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);


以上就是全部实现过程。

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值