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 或消费者。
所以,我的问题是:谁负责调用 CoInitialize
和 CoUninitialize
:我的助手 class,还是主机应用程序?除了助手 class,宿主应用程序中对 COM 的附加依赖性可能为零。
选项A:助手class在构造函数和析构函数中调用CoInitialize
和CoUninitialize
这个选项很方便,并且有效 'hides' COM 依赖。但是,父应用程序可能已经初始化 COM,也可能还没有,它可能匹配也可能不匹配助手 class 假定的公寓模型。如果模型没有对齐,助手 class 将收到来自 CoInitialize
的错误。
选项 B:助手 class 生成一个单独的线程,并在后台线程上使用单线程单元调用 CoInitialize
。所有接口调用都分派到后台线程并返回。
这有助于确保助手 class 有一个 'clean slate' 可以使用,并避免在任何单个线程上重复进行 COM 初始化。它还增加了我的助手 class 实现的复杂性,并以线程切换和握手的形式增加了开销。
选项 C:在文档中做一个注释,并要求主机应用程序在使用助手 [=54= 之前处理对 CoInitialize
和 CoUninitialize
的所有调用]
这使得 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);
我有一个助手 class,它为主机应用程序提供了一些诊断 API。隐藏实现依赖于 WMI,可通过 Windows COM 接口访问。
实施 "COM aware" 的 class 需要一些开销,以调用 CoInitialize/Ex
的形式使用适当的单元模型 (single-threaded/multi-threaded)。我不确定 谁 负责设置 - 我的助手 class 或消费者。
所以,我的问题是:谁负责调用 CoInitialize
和 CoUninitialize
:我的助手 class,还是主机应用程序?除了助手 class,宿主应用程序中对 COM 的附加依赖性可能为零。
选项A:助手class在构造函数和析构函数中调用CoInitialize
和CoUninitialize
这个选项很方便,并且有效 'hides' COM 依赖。但是,父应用程序可能已经初始化 COM,也可能还没有,它可能匹配也可能不匹配助手 class 假定的公寓模型。如果模型没有对齐,助手 class 将收到来自 CoInitialize
的错误。
选项 B:助手 class 生成一个单独的线程,并在后台线程上使用单线程单元调用 CoInitialize
。所有接口调用都分派到后台线程并返回。
这有助于确保助手 class 有一个 'clean slate' 可以使用,并避免在任何单个线程上重复进行 COM 初始化。它还增加了我的助手 class 实现的复杂性,并以线程切换和握手的形式增加了开销。
选项 C:在文档中做一个注释,并要求主机应用程序在使用助手 [=54= 之前处理对 CoInitialize
和 CoUninitialize
的所有调用]
这使得 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);