Helper类隐藏了COM依赖项;谁叫CoInitialize?

时间:2017-10-11 13:29:01

标签: c++ com abstraction

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

实现一个“知道COM”的类需要一些开销,以调用CoInitialize的形式,使用适当的Apartment模型(单线程/多线程)。我不确定负责设置 - 我的助手类或消费者。

所以,我的问题是:谁负责调用CoUninitializeCoInitialize:我的助手类或主机应用程序?除了辅助类之外,主机应用程序中的COM可能没有其他依赖性。

选项A:帮助器类在构造函数和析构函数中调用CoUninitializeCoInitialize

此选项很方便,并且有效地“隐藏”了COM依赖项。但是,父应用程序可能已经或可能没有初始化COM,它可能与助手类的假定公寓模型匹配也可能不匹配。如果模型没有对齐,辅助类将从CoInitialize收到错误。

选项B:帮助程序类生成一个单独的线程,并在后台线程上使用单线程单元调用CoInitialize。所有接口调用都将调度到后台线程并返回。

这可以帮助确保帮助程序类具有“干净的平台”,并避免在任何单个线程上进行重复的COM初始化。它还增加了我的助手类实现的复杂性,并以线程切换和握手的形式增加了开销。

选项C:在文档中记下,并要求主机应用程序在使用帮助程序类之前处理对CoUninitialize{ "registry": "https://registry.bower.io" } 的所有调用

这使得该类使用起来稍微“方便”,因为用户在使用该类之前还有其他初始化步骤。它还要求类的消费者实际阅读文档,这似乎很危险。

2 个答案:

答案 0 :(得分:2)

选项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,如果不是,则可以自己初始化它。

答案 1 :(得分:0)

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

  • 创建用于初始化COM的RAII帮助程序类:class ApartmentContext
  • 要求ApartmentContext作为帮助程序类中的构造函数参数。这会强制用户通过ApartmentContext实例的存在来声明已初始化COM。

这有很多好处:

  • 它为消费者提供了指定自己的线程模型的灵活性。
  • 它提供了一个API,可以清楚地传达对COM的依赖,并强制用户提供公寓的证明。
  • 奖金:公寓现在是RAII友好的。

以下是我的ApartmentContext类的列表:

// 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
};

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

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

帮助器类将要求用户在其构造函数中提供上下文:

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);