【问题标题】:Throwing C++ exceptions with CoInitialize and CoUninitialize使用 CoInitialize 和 CoUninitialize 引发 C++ 异常
【发布时间】:2018-04-17 19:57:03
【问题描述】:

我有这样的功能:

bool op1();
bool op2();

bool foo() {
  CoInitialize(nullptr);
  if (!op1()) {
    CoUninitialize();
    return false;
  }
  // do more stuff, then call op2...
  if (!op2()) {
    CoUninitialize();
    return false;
  }
  // happy path
  CoUninitialize();
  return true;
}

我想重构foo() 来抛出异常:

void foo() {
  CoInitialize(nullptr);
  if (!op1()) {
    CoUninitialize(); // I'm lazy, can't I automate this call?
    throw std::exception("Failed");
  }
  // ...

但我每次遇到错误都必须致电CoUninitialize()

我曾想过将 COM 初始化调用封装在一个类中,所以析构函数 will do the cleanup,但是有一个未使用的对象对我来说感觉很奇怪:

class comlib {
public:
  comlib() { CoInitialize(nullptr); }
  ~comlib() { CoUninitialize(); } // automated!
};

void foo() {
  comlib nobodyEverCallsMe;
  if (!op1()) {
    throw std::exception("Failed");
  }
  // ...

有更好的方法吗?

【问题讨论】:

  • 重要提示是确保CoUninitialize()不会因为linkedin.com/pulse/…而抛出
  • 最好在启动 exe 时调用 CoInitialize 和在清理时调用 CoUninitialize。但不是在任意时间的某个函数内。
  • 如果你在 dll 中编码 - 你根本不能调用 CoInitialize / CoUninitialize。如果 exe 代码已经用另一个 COINIT 值调用 CoInitializeEx 会怎样?
  • 写一个“智能”类是要走的路,恕我直言,你可以使用 Raymon Chen 的:blogs.msdn.microsoft.com/oldnewthing/20040520-00/?p=39243
  • 您的 RAII 包装器存在错误。如果CoInitialize 失败,您仍然会从d'tor 调用CoUninitialize。要解决此问题,您需要从您的 c'tor 中抛出,以防 CoInitialize 失败。还可以考虑在该 RAII 包装器上使用 [[maybe_unused]] 属性说明符 (C++17),这样您的编译器就不会发出警告。

标签: c++ winapi exception exception-handling com


【解决方案1】:

有更好的方法吗?

没有。

使用 RAII (Responsibility1 Acquisition is Initialization) 习惯用法在退出时进行清理是您试图解决的问题的标准 C++ 解决方案。如果是 COM,我会提出以下实现:

#include <Windows.h>
#include <comdef.h>

struct com_init
{
    com_init()
    {
        HRESULT hr{::CoInitialize(nullptr)};
        if (FAILED(hr))
        {
            throw _com_error{hr};  // _com_error is declared in comdef.h
        }
    }
    com_init(com_init const&) = delete;
    com_init& operator=(com_init const&) = delete;

    ~com_init()
    {
        ::CoUninitialize();
    }
};

在您的示例中使用如下:

void foo()
{
    com_init guard{};

    if (!op1())
        throw std::exception{"Failed"};
    // ...

使用 C++17 可以将对象标记为 [[maybe_unused]],以防止编译器警告以及传达意图。

理由:

该实现使用了一个在失败时抛出异常的构造函数。这样做有很多充分的理由:

  • 空格:此实现不需要存储任何状态信息以允许析构函数有条件地执行清理。
  • 一致性:C++ 是围绕异常构建的。将基于异常的代码与通过错误代码报告错误的代码交织在一起既令人困惑,也更难理解。
  • 可靠性:未能初始化 COM 是灾难性的故障。没有可以想象的方法可以从中恢复。不容忽视。您不能忽略异常。错误代码的默认设置是忽略它们;您无需为此做任何事情。


1 通常标记为 “资源”,但这并不完全符合它提供的多功能性。我更喜欢“责任”

【讨论】:

  • 默认的复制构造函数和赋值运算符应该是=deleted,因为类是不可复制的。我也更喜欢使用std::system_error 而不是_com_error 作为报告此类错误的标准化方式。例如。 throw std::system_error{hr, std::system_category(), "Failed to initialize COM"};。错误字符串添加了有用的上下文信息。
  • @zett42: delete'ing 默认复制构造函数和复制赋值运算符是个好主意。用std::system_error 代替_com_error 有利有弊。在类库的情况下它可能会有所帮助,其中客户端代码可以一致地处理与操作系统相关的错误。另一方面,_com_error 具有所有 COM 错误报告功能,例如用于查询附加信息的IErrorInfo 接口。开发人员可以选择更适合他们的情况。不过,例外的选择并没有改变这个建议答案的本质。
  • 听起来很合理!
【解决方案2】:

Raymond Chen 已经using this method 有一段时间了,所以我确定没关系,只要记住如果CoInitialize SUCCEEDED 时才拨打CoUninitialize

class CCoInitialize {
  HRESULT m_hr;
public:
  CCoInitialize() : m_hr(CoInitialize(NULL)) { }
  ~CCoInitialize() { if (SUCCEEDED(m_hr)) CoUninitialize(); }
  operator HRESULT() const { return m_hr; }
};


void Something()
{
  CCoInitialize init;
  ...
}

如果 CoInitialize 失败,有些人可能想在构造函数中抛出,但我觉得这是不必要的,因为其他 COM 调用将失败。仅当您需要从 CoInitialize 捕获确切的 HRESULT 失败代码时才执行此操作。

【讨论】:

  • 在初始化失败的情况下从 c'tor 抛出异常是更简洁的设计。一方面,如果 c'tor 失败了,那么 RAII 对象就没有责任了,那么为什么要保留它呢?然后是技术方面:对CoInitialize 的调用失败并不意味着其他COM 调用将失败。他们很可能会成功,并将 COM 对象实例化到错误的单元中。在这种特殊情况下,当 OP 从错误代码转移到异常时,混合错误报告策略感觉非常不自然。建议不使用异常需要充分的理由。
  • 例如,如果您调用 ShellExecute,它可能需要 COM,也可能不需要,这取决于您正在执行的内容以及文件类型的注册方式。
  • 如果你的代码需要初始化 COM,不管你线程上的其他人是否需要 COM。你需要初始化你的线程。如果失败了,就没有办法从失败中恢复过来。我不知道,为什么你认为 ShellExecute 有点特别,或者你打算如何从未来的失败中恢复过来。
  • ShellExecute 很特别,因为它可能需要 COM。在这种情况下,可以忽略 CoInitialize 失败,因为 ShellExecute 可能仍会成功。
  • 如果 COM 初始化失败,并且您继续调用 ShellExecute[Ex],那么该调用可能确实会成功。或者中途失败。 dangers of the implicit MTA 是相当不可能管理的。我不相信即使在调用线程上尚未初始化 COM 时调用可能会或可能不会失败的函数,将错误发现留给客户端代码也是一个好主意。只需抛出一个异常并完成它。
猜你喜欢
  • 2013-10-26
  • 1970-01-01
  • 1970-01-01
  • 2015-07-13
  • 2011-04-11
  • 2021-10-30
  • 1970-01-01
  • 2020-09-03
  • 1970-01-01
相关资源
最近更新 更多