跳到主要内容

cpp 的 RAII 技术

什么是 ComPtr?

通过使用 ComPtr,可以避免忘记调用 Release 方法而导致的资源泄露问题。当 ComPtr 对象离开其作用域时,其析构函数会自动被调用,从而释放 COM 对象。这种技术称为 RAII(资源获取即初始化),是 C++ 中管理资源的一种有效方式。

ComPtr 是 Microsoft Windows Runtime C++ Template Library (WRL) 的一部分,是一个智能指针专为管理COM对象的生命周期而设计。ComPtr 自动处理COM对象的引用计数,通过在适当的时候调用 AddRefRelease 来避免内存泄漏和悬挂指针问题。其使用方式类似于 C++11 标准库中的 std::shared_ptrstd::unique_ptr,但是专门用于 COM 对象。

  1. 包含头文件

    要使用 ComPtr,需要包含WRL的头文件:

    #include <wrl/client.h>
  2. 声明 ComPtr 变量

    使用 ComPtr 管理一个COM对象时,你应该指定COM接口作为模板参数:

    Microsoft::WRL::ComPtr<IUIAutomation> automation;
  3. 创建COM对象

    ComPtr 可以与 CoCreateInstance 或类似函数一起使用,来创建COM对象的实例并自动管理该实例的生命周期:

    CoCreateInstance(CLSID_CUIAutomation, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&automation));
  4. 访问COM接口

    使用 ComPtrGet() 方法或直接使用 ComPtr 对象来访问其管理的COM接口:

    automation->GetRootElement(&rootElement); // 直接使用

    或者:

    IUIAutomation* pAutomation = automation.Get(); // 通过 Get() 获取裸指针
  5. 赋值和复制

    ComPtr 支持自动的赋值和复制行为,这会自动处理 AddRefRelease 调用:

    Microsoft::WRL::ComPtr<IUIAutomationElement> element1, element2;
    // 假设 element1 已被赋值
    element2 = element1; // 自动 AddRef
  6. 重置和释放

    要显式释放 ComPtr 管理的COM对象,可以使用 Reset 方法:

    element1.Reset(); // 释放对象并将内部指针设为 nullptr
  7. 与原生COM接口交互

    有时候,你可能需要将 ComPtr 对象传递给期望接收原生COM接口指针的函数。此时,可以使用 GetAddressOf 方法获取指针的地址:

    CoCreateInstance(CLSID_CUIAutomation, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(element1.GetAddressOf()));

通过这种方式,ComPtr 管理的COM对象会在 ComPtr 对象生命周期结束时自动释放,或者当其被重置或重新赋值时释放,从而简化了COM编程中的资源管理工作。

ComPtr 的简单实现

这里可以定义一个最简单的 ComPtr 类实现,以便更好地理解:

// 定义一个模板类ComPtr,T是COM对象的类型
template<typename T>
class ComPtr {
private:
T* ptr; // ptr是指向COM对象的指针

public:
// 默认构造函数,初始化ptr为nullptr,表示没有指向任何对象
ComPtr() : ptr(nullptr) {}

// 参数化构造函数,接收一个指向T类型对象的指针,并使ptr指向这个对象
ComPtr(T* ptr) : ptr(ptr) {}

// 析构函数,在ComPtr对象被销毁时调用。如果ptr不为nullptr,则调用ptr指向的COM对象的Release方法,释放对象。
~ComPtr() { if (ptr) ptr->Release(); }

// 重载&操作符,返回指向ptr的指针。这常用于将ComPtr作为参数传递给需要直接修改指针的COM API函数。
T** operator&() { return &ptr; }

// 重载->操作符,允许通过ComPtr直接访问其管理的COM对象的成员。返回ptr,可以直接调用COM对象的方法。
T* operator->() const { return ptr; }

// 重载!操作符,用于检查ComPtr是否没有指向任何对象(即ptr是否为nullptr)。返回true如果ptr为nullptr。
bool operator!() const { return ptr == nullptr; }

// get方法,返回ptr,允许在需要原始指针的场合使用ComPtr管理的对象。
T* get() const { return ptr; }
};