使用CoInitialize和CoUninitialize抛出C ++异常

时间:2017-11-05 16:03:42

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

我有这样的功能:

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");
  }
  // ...

有更好的方法吗?

2 个答案:

答案 0 :(得分:2)

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

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失败代码时才能执行此操作。

答案 1 :(得分:1)

  

有更好的方法吗?

没有

使用RAII(责任 1 获取是初始化)在退出时清除的习惯用法是您尝试解决的问题的标准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 通常标记“资源”,但这并不完全符合它提供的多功能性。我更喜欢“责任”