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

C++ 模板类, 使 COM 封送操作变得简单

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.71/5 (9投票s)

2016年11月14日

CPOL

3分钟阅读

viewsIcon

19611

我创建了两个 C++ 模板类, 它们可以轻松、 无痛地在线程之间封送 COM 接口

引言

这些类的目标是尽可能简单和无痛地实现跨线程的接口编组。

背景

这里提供的代码基于 CodeProject 上的文章,该文章解释了如何在线程之间编组 COM 接口。

然而,虽然那篇文章非常好,但它让开发者每次需要编组线程时都必须手动管理资源生命周期。这当然给错误留下了解空间。

我决定创建一些模板化的包装类,使用 RAII(资源分配即初始化)自动管理资源生命周期。结果是一个非常简单的接口,学习起来更快,因为你不需要理解任何幕后代码,除非你愿意。通过封装资源管理,可以降低错误的可能性。

Using the Code

假设你有一个名为 IEngine 的接口。为了跨线程编组该接口,首先在主线程中创建 ComInterface<IEngine> 类的实例,并确保它在需要编组接口的时间内一直存在。

        IEnginePtr enginePtr = LoadEngine();

        ComInterface<IEngine> _engineInterface(enginePtr);

要在另一个线程中使用该接口

        ComInterfaceMarshaller<IEngine> marshaller(_engineInterface);

        marshaller->StartLogging(GetLogFilePath(), VARIANT_TRUE);

就是这样!

ComInterfaceMarshaller 构造函数会为你处理编组。当你需要使用 COM 接口时,只需使用 -> 运算符,如所示。当调用 ComInterfaceMarshaller 析构函数时,相关的资源将被释放。

如果 ComInterfaceMarshaller 对象是在主线程中创建的,则无需编组该接口。代码中已经考虑到了这一点。

讨论

底层类是来自 Active Template Library 的 CComGITPtr 类,它实际上执行接口编组。在 ComInterfaceMarshaller 构造函数中,表示原始接口的 cookie 会被转换为供当前线程使用的编组接口。

_gitPtr = ATL::CComGITPtr<T>(comInterface.GitCookie());

_gitPtr.CopyTo(&_interfacePtr);

然后在 ComInterfaceMarshaller 析构函数中,原始接口将从 CComGITPtr 实例中分离,编组接口将被释放。

_gitPtr.Detach();

_interfacePtr->Release();

一个 COM 智能指针封装了 COM 接口,以便管理其生命周期,并在不再需要时释放该接口。

ComInterface 类有一个 Clear 方法,以便你可以释放接口,而无需等待调用析构函数,如果需要的话。

类 ComInterface

#pragma once

#include "atlbase.h"

template <typename T>
class ComInterface
{
public:

    // The smart pointer class
    typedef _com_ptr_t <_com_IIID<T, &__uuidof(T)>> TPtr;

    ComInterface()
        : _interfacePtr(nullptr)
        , _ThreadId(GetCurrentThreadId())
    {

    }

    ComInterface(TPtr & interfacePtr)
        : _interfacePtr(interfacePtr)
    {
        _gitPtr = ATL::CComGITPtr<T>(interfacePtr.GetInterfacePtr());
    }

    ~ComInterface()
    {
    }

    void Clear()
    {
        _gitPtr = nullptr;
        _interfacePtr = nullptr;
    }

    unsigned long ThreadId() const
    {
        return _ThreadId;
    }

    unsigned long GitCookie() const
    {
        return _gitPtr.GetCookie();
    }

    ATL::CComGITPtr<T>& GitPtr()
    {
        return _gitPtr;
    }

    T* InterfacePtr()
    {
        return _interfacePtr.GetInterfacePtr();
    }

    TPtr& ComPtr()
    {
        return _interfacePtr;
    }

private:

    TPtr _interfacePtr;

    // This must be the ID of the thread which created the COM interface
    unsigned long _ThreadId;

    ATL::CComGITPtr<T> _gitPtr;
};

类 CComMarshaller

#pragma once
#include "ComInterface.h"

template <class T>
class ComInterfaceMarshaller
{
public:
    ComInterfaceMarshaller(ComInterface<T>& comInterface)
        : _interfacePtr(comInterface.InterfacePtr())
    {
        _sameThread = (comInterface.ThreadId() == GetCurrentThreadId());

        if (_sameThread || (_interfacePtr == nullptr))
        {
            return;
        }

        _gitPtr = ATL::CComGITPtr<T>(comInterface.GitCookie());
        _gitPtr.CopyTo(&_interfacePtr);
    }

    ~ComInterfaceMarshaller()
    {
        if (!_sameThread)
        {
            _gitPtr.Detach();
            _interfacePtr->Release();
        }
    }

    T* operator->()
    {
        return _interfacePtr;
    }

    T* GetInterface()
    {
        return _interfacePtr;
    }

private:

    ATL::CComGITPtr<T> _gitPtr;
    T* _interfacePtr;
    bool _sameThread;
};

替代方案

我最初创建这些类是因为我正在使用单线程公寓 COM 服务器,并从一个 WPF/.NET 应用程序中的多个线程访问它,这些线程是由 WPF/.NET 应用程序创建的。编组跨线程接口的另一种方法是创建一个单独访问 COM 服务器的工作线程。因此,你的主线程将请求发送到工作线程,然后等待请求完成。但是,请注意,如果你选择这种方法,你必须确保你的主线程实现 Windows 消息泵,因为 COM 使用 Windows 消息来序列化请求。阻塞消息泵会阻塞你的工作线程调用 COM 服务器,正如我所经历的那样。

历史

  • 2016 年 11 月 14 日:第一个版本
  • 2016 年 11 月 25 日:修复了 ComInterface 类中的一些错误。_threadId 属性未正确初始化。cookie 从 _gitPtr 分离,导致内存泄漏。
© . All rights reserved.