COM事件和线程间接口指针传递

在COM编程中,事件的传递和线程间接口指针的传递是两个常见的问题。本文将介绍如何在不同进程间使用连接点,以及如何在本地服务器中实现事件触发机制。

连接点的使用

连接点是COM中一个重要的概念,它允许对象通知其他对象关于其状态的改变。在Microsoft Visual C++ 6.0中,CONNECT示例展示了如何在进程内使用连接点。然而,更常见的情况是在不同进程间使用连接点。

为了实现这一目标,需要将进程内服务器DLL转换为服务器EXE。这可以通过使用ATL COM AppWizard来完成。在本文的例子中,将新的应用程序命名为"Conexe",以区分原始项目。

在转换为EXE后,需要创建一个新的接口。这可以通过在ClassView中右击并选择"Create Interface"来完成。在本文的例子中,创建了一个名为IRandexe的新接口,并从CONNECT示例中的IRandom接口复制了相关的IDL接口行。

接下来,将原始Random.cpp中的所有函数和Random.h中的定义复制到新接口中,以完成新接口的创建。这样,就得到了一个与IRandom功能相同但具有新名称和IID的新接口。

线程间接口指针传递的问题

在创建本地服务器版本时,遇到了线程间接口指针传递的问题。为了解决这个问题,需要在RandomSession线程中添加对CoInitialize的调用。这样,每个通过客户端请求创建的线程都将获得自己的私有单线程公寓。

以下是RandomSession线程的实现:

DWORD WINAPI RandomSessionThreadEntry(void* pv) { CoInitialize(NULL); CRandexe::RandomSessionData* pS = (CRandexe::RandomSessionData*)pv; CRandexe* p = pS->pRandom; while(WaitForSingleObject(pS->m_hEvent, 0) != WAIT_OBJECT_0) p->Fire(pS->m_nID); CoUninitialize(); return 0; }

在这个实现中,首先调用CoInitialize来创建一个单线程公寓。然后,使用WaitForSingleObject函数等待事件对象。如果事件对象没有被触发,就调用Fire函数来触发事件。最后,调用CoUninitialize来清理COM库。

Advise函数的实现

Advise函数是连接点机制中的一个重要部分,它用于初始化事件的连接。在本文的例子中,重写了IConnectionPointImpl::Advise函数。在这个实现中,首先检查传入的接口指针和cookie是否为NULL。然后,获取连接接口的IID,并调用QueryInterface函数来获取接口指针。

如果QueryInterface函数成功,就将接口指针添加到m_vec数组中,并设置cookie的值。然后,调用CoMarshalInterThreadInterfaceInStream函数来将接口指针进行线程间传递。最后,释放接口指针并解锁。

HRESULT CRandexe::Advise(IUnknown *pUnkSink, DWORD *pdwCookie) { ATLTRACE("RANDEXE: CRandexe::Advise entry\n"); if(m_nStreamIndex >= 10) return CONNECT_E_ADVISELIMIT; T* pT = static_cast(this); IUnknown* p; HRESULT hRes = S_OK; if(pUnkSink == NULL || pdwCookie == NULL) return E_POINTER; IID iid; GetConnectionInterface(&iid); hRes = pUnkSink->QueryInterface(iid, (void**)&p); if(SUCCEEDED(hRes)) { Lock(); *pdwCookie = m_vec.Add(p); hRes = (*pdwCookie != NULL) ? S_OK : CONNECT_E_ADVISELIMIT; ATLTRACE("RANDEXE: CRandexe::Advise: cookie = %ld\n", *pdwCookie); HRESULT hr = CoMarshalInterThreadInterfaceInStream(IID_IRandexeEvent, p, &m_StreamArray[m_nStreamIndex]); ErrorUI(hr, "CoMarshalInterThreadInterfaceInStream error."); m_nStreamIndex++; Unlock(); if(hRes != S_OK) p->Release(); } else if(hRes == E_NOINTERFACE) hRes = CONNECT_E_CANNOTCONNECT; if(FAILED(hRes)) *pdwCookie = 0; ATLTRACE("RANDEXE: CRandexe::Advise exit\n"); return hRes; }

Fire函数的实现

Fire函数是连接点机制中的另一个重要部分,它用于触发事件。在本文的例子中,重写了CRandexe::Fire函数。在这个实现中,首先锁定对象,然后遍历m_StreamArray数组,并对每个元素调用CoUnmarshalInterface函数来获取接口指针。

如果CoUnmarshalInterface函数失败,就调用FormatMessage函数来获取错误信息。然后,调用接口指针的Fire函数来触发事件。最后,解锁对象并返回结果。

HRESULT CRandexe::Fire(long nID) { Lock(); HRESULT hr = S_OK; for(int i = 0; i < m_nStreamIndex; i++) { CComPtr pStream; hr = m_StreamArray[i]->Clone(&pStream); IRandexeEvent *pI; hr = CoUnmarshalInterface(pStream, IID_IRandexeEvent, (void**)&pI); if(FAILED(hr)) { void *pMsgBuf; ::FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, hr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&pMsgBuf, 0, NULL); ATLTRACE("RANDEXE: Windows error 0x%lx, %s\n", (DWORD)hr, (LPSTR)pMsgBuf); LocalFree(pMsgBuf); } hr = pI->Fire(nID); } Unlock(); return hr; }

客户端的实现

客户端项目MDrive被简单地复制到一个新的子目录中,并稍作修改以使用新的服务器。可以启动MDrive的多个实例,它们都可以访问Conexe.exe本地服务器。需要注意的是,本地服务器版本的速度较慢,这可以通过MDrive中的像素绘制速率看出。

本文的示例程序只是一个简单的示例,它展示了如何在本地服务器中实现事件触发机制。对于具有更复杂功能的程序,当然需要更多的努力。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485