将 CONNECT 示例转换为本地服务器





5.00/5 (2投票s)
2000 年 8 月 13 日

83649

713
关于 ATL COM 事件连接点线程问题的文章
引言
注意:正如已指出的,我的第一版文章在 NT 和 W2K 上存在一些严重问题。以下尝试使用两年前被忽略或不可用的信息来纠正这些错误。正如 MSDN Dr. GUI 的一篇较新文章指出的,在线程之间传递接口指针存在大问题。我曾希望 Dr. GUI 能专门针对从新线程触发事件的情况解决这个问题,文章中也提到了这一点,但留给读者作为高级练习。我从文章中借鉴了一些提示,以便我的新示例项目能够正常工作。
Microsoft Visual C++ 6.0 附带的 CONNECT 示例演示了如何使用 ATL 的连接点。进程内服务器实现为 connect.dll,而客户端之一是一个简单的基于对话框的应用程序,名为 MDrive
。它旨在作为在单个进程边界内使用连接点的示例。然而,您首先可能想做的就是使用连接点在不同进程之间进行通信。我找不到如何做到这一点的示例,所以不得不自己想办法。
我的真正目标是创建一个独立的温度监控程序,该程序具有一个用户界面,还可以使用连接点事件接口将温度更新发送到客户端应用程序。在这种情况下,使用轮询还是异步事件消息是有争议的,我选择了事件。不过,我没有将最终项目直接基于此示例,因为使用 VC6 中的 ATL 向导并创建一个新项目要容易得多,而且您创建的代码将更符合当前的 ATL 编码约定。以下只是一个将 CONNECT 示例转换为本地服务器的简单示例。
首先,我将进程内服务器 DLL 转换为了服务器 EXE。最快的方法是使用 ATL COM 应用程序向导创建一个新应用程序。我将新应用程序命名为“Conexe”,以区别于原始项目。新应用程序的 conexe.cpp
中的样板代码无需修改即可使用。在 _tWinMain
中保留 CoInitialize
的使用,而不是 CoInitializeEx
。
然后,我使用 ClassView 的右键菜单创建了一个名为 IRandexe
的新接口。接着,我将 CONNECT 中的 IRandom
接口相关的 IDL 行复制了过来。最后,我将原始 Random.cpp
中的所有函数和 Random.h
中的定义复制了过去,以完成新接口。结果是一个与 IRandom
功能相同的新接口,只是名称和 IID 不同。
现在到了最有趣的部分。在创建此项目时,我尝试了许多线程设计,这是唯一似乎正常工作的一种。在本地服务器版本中,我必须在 RandomSession
线程中添加一个对 CoInitialize
的调用。因此,通过客户端请求创建的每个线程都将获得自己的私有单线程单元。
DWORD WINAPI RandomSessionThreadEntry(void* pv) { // Need to call CoInitialize on this thread to create a single // threaded apartment. If you don't do this you will get the // "CoInitialize has not been called." error. CoInitialize(NULL); // new line 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(); // new line return 0; }
正如 Dr. GUI 文章所建议的,Advise 可以很好地完成初始接口封送处理,因为这是事件接口的 m_vec
数组增长的地方。这是我对 IConnectionPointImpl::Advise
的重写,我只是从 ATL 源代码中复制了代码。在本演示程序中,我使用了固定大小的流指针数组,但您应该使用您选择的集合类型来修改代码。如果已有 10 个 Advise 调用,我将任意返回 CONNECT_E_ADVISELIMIT
,使其失败。
HRESULT CRandexe::Advise( IUnknown *pUnkSink, DWORD *pdwCookie ) { ATLTRACE("RANDEXE: CRandexe::Advise entry\n"); // Limit the number of advises in this test program. if( m_nStreamIndex >= 10 ) return CONNECT_E_ADVISELIMIT; //T* pT = static_cast<T*>(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(); //pT->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(); //pT->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 函数的实现。使用 CRandexe
成员数组 m_StreamArray
,您可以遍历数组并对每个流调用 CoUnMarshallInterface
。解封送处理的一个效果似乎是流指针的位置会改变,为了解决这个问题,我在解封送处理之前克隆了流。您也可以通过将流指针重新定位回开头来使其工作。我能够使用成员数组大大简化 fire 函数。我保留了所有我使用的调试代码,您可以根据需要将其删除。
// broadcast to all the objects HRESULT CRandexe::Fire(long nID) { Lock(); HRESULT hr = S_OK; for( int i = 0; i < m_nStreamIndex; i++ ) { CComPtr<IStream> 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
中的像素绘制速率看出。我没有过多考虑与多个客户端的交互,我在 Win98se 和 Win2K 上的测试时间只有 5 分钟,但到目前为止,它似乎能完成工作。可能会出现一些错误——您也可能考虑将 m_StreamArray
和 m_nStreamIndex
设为静态成员——我没有尝试过,但很想试试。功能更完善的程序自然需要更多的努力。
来自 MSDN Library 2000 年 7 月的一些参考资料
- Dr. GUI 和 COM 事件,第一部分 1999 年 8 月 23 日
- Dr. GUI 和 COM 事件,第二部分,1999 年 10 月 25 日
最后更新:2000 年 8 月 12 日