65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2000 年 8 月 13 日

viewsIcon

83649

downloadIcon

713

关于 ATL COM 事件连接点线程问题的文章

  • 下载演示项目 - 72 Kb
  • Sample Image - ConExe.gif

    引言

    注意:正如已指出的,我的第一版文章在 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_StreamArraym_nStreamIndex 设为静态成员——我没有尝试过,但很想试试。功能更完善的程序自然需要更多的努力。

    来自 MSDN Library 2000 年 7 月的一些参考资料

    • Dr. GUI 和 COM 事件,第一部分 1999 年 8 月 23 日
    • Dr. GUI 和 COM 事件,第二部分,1999 年 10 月 25 日

    最后更新:2000 年 8 月 12 日

    © . All rights reserved.