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






4.71/5 (9投票s)
我创建了两个 C++ 模板类,
引言
这些类的目标是尽可能简单和无痛地实现跨线程的接口编组。
背景
这里提供的代码基于 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 分离,导致内存泄漏。