Helper class 隐藏了 COM 依赖项;谁调用 CoInitialize?

Helper class has hidden COM dependencies; who calls CoInitialize?

我有一个助手 class,它为主机应用程序提供了一些诊断 API。隐藏实现依赖于 WMI,可通过 Windows COM 接口访问。

实施 "COM aware" 的 class 需要一些开销,以调用 CoInitialize/Ex 的形式使用适当的单元模型 (single-threaded/multi-threaded)。我不确定 负责设置 - 我的助手 class 或消费者。

所以,我的问题是:谁负责调用 CoInitializeCoUninitialize:我的助手 class,还是主机应用程序?除了助手 class,宿主应用程序中对 COM 的附加依赖性可能为零。

选项A:助手class在构造函数和析构函数中调用CoInitializeCoUninitialize

这个选项很方便,并且有效 'hides' COM 依赖。但是,父应用程序可能已经初始化 COM,也可能还没有,它可能匹配也可能不匹配助手 class 假定的公寓模型。如果模型没有对齐,助手 class 将收到来自 CoInitialize 的错误。

选项 B:助手 class 生成一个单独的线程,并在后台线程上使用单线程单元调用 CoInitialize。所有接口调用都分派到后台线程并返回。

这有助于确保助手 class 有一个 'clean slate' 可以使用,并避免在任何单个线程上重复进行 COM 初始化。它还增加了我的助手 class 实现的复杂性,并以线程切换和握手的形式增加了开销。

选项 C:在文档中做一个注释,并要求主机应用程序在使用助手 [=54= 之前处理对 CoInitializeCoUninitialize 的所有调用]

这使得 class 的使用量略微减少 'convenient',因为用户在使用 class 之前还有额外的初始化步骤。它还要求 class 的消费者 实际阅读 文档,这似乎很危险。

选项 A 有点聪明,似乎是个不错的选择。这假定您不关心正在使用的线程模型。如果这样做,您需要明确声明助手的客户端不应初始化 COM,以便它可以指定线程模型。

class HelperThatRequiresCOM
{
public:
   HelperThatRequiresCOM() : m_CoUninit(false)
   {
      // Attempt to init COM as a STA
      HRESULT ciResult = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
      if(ciResult == S_OK || ciResult == S_FALSE)
        m_CoUninit = true; // COM initialized or already initialized.
      else if(ciResult == RPC_E_CHANGED_MODE)
        m_CoUninit = false; // COM initialized as MTA
   }

   ~HelperThatRequiredCOM()
    {
        if(m_CoUninit == true)
           CoUninitialize();
    }

private:
bool m_CoUninit;

};

这将尝试初始化 COM。如果它已经初始化为 STA,或者刚初始化为 STA,它会记住该信息,以便稍后调用 CoUninitalize

如果 COM 已经初始化为 MTA,它会记住这一点,以后不会 CoUninitialize

这允许调用线程自行初始化 COM,如果不是,您可以自己初始化它。

在评论部分提出了一些很好的观点之后,经过进一步思考,我选择了新的选项 D:

  • 创建一个 RAII 助手 class 用于初始化 COM:class ApartmentContext
  • 需要 ApartmentContext 作为助手 class 中的构造函数参数。这迫使用户承诺 COM 已经初始化,因为 ApartmentContext 实例的存在。

这有很多好处:

  • 它让消费者可以灵活地指定自己的线程模型。
  • 它提供了一个API,明确传达了对COM的依赖,并强制用户提供公寓证明。
  • 奖励:公寓现在适合 RAII。

这是我的 ApartmentContext class 的列表:

// Specifies a single-threaded or multi-threaded COM apartment.
enum class Apartment
{
    MultiThreaded = 0,
    SingleThreaded = 2
};

// A helper class used for initializing and uninitializing a COM Apartment.
// The constructor of the ApartmentContext will initialize COM, using the 
// specified apartment model: single-threaded or multi-threaded.
// The destructor will automatically uninitialize COM.
class ApartmentContext final
{
public:
    // Initialize COM using the specified apartment mode.
    ApartmentContext(Apartment mode);

    // Uninitialize COM
    ~ApartmentContext();

    // Get the current apartment type.
    Apartment Current() const;

private:
    ApartmentContext& operator=(const ApartmentContext&) = delete;
    ApartmentContext(const ApartmentContext&) = delete;

    Apartment current_; // Store the current apartment type
};

在实例化助手之前,用户将被迫创建其中之一 class:

// Somewhere in main...
// Initialize COM:
ApartmentContext context(Apartment::SingleThreaded);

助手 class 将要求用户在其构造函数中提供上下文:

public class MyHelperClass
{
public:
    // The apartment context isn't actually used; it is required by the 
    // constructor merely as a way to ensure that COM is initialized 
    // beforehand.
    // If the class requires a certain apartment mode,
    // it could also check the mode using the "Current" API, 
    // and throw on mismatch
    MyHelperClass(const ApartmentContext&);
};

用法:

// Create the helper class, providing the context as a constructor argument
MyHelperClass helper(context);