2

I have a function like this:

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

I want to refactor foo() to throw exceptions:

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

But I have to call CoUninitialize() each time I have an error.

I thought about wrapping the COM init calls inside a class, so the destructor will do the cleanup, but having an unused object feels strange to my eyes:

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

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

Is there a better approach to this?

rodrigocfd
  • 6,450
  • 6
  • 34
  • 68
  • Important note is to make sure that `CoUninitialize()` doesn't throw because of https://www.linkedin.com/pulse/c-never-throw-exceptions-destructors-other-memory-serebryakov/ – Alex Nov 05 '17 at 16:08
  • 3
    better once call `CoInitialize` at startup of exe and `CoUninitialize` at cleanup. but not inside some function in arbitrary time. – RbMm Nov 05 '17 at 16:08
  • and if you code in dll - you at all must not call `CoInitialize` / `CoUninitialize`. what be if exe code already call `CoInitializeEx` with another `COINIT` value ? – RbMm Nov 05 '17 at 16:13
  • 3
    Writing a "smart" class is the way to go IMHO, you can use Raymon Chen's one: https://blogs.msdn.microsoft.com/oldnewthing/20040520-00/?p=39243 – Simon Mourier Nov 05 '17 at 16:23
  • 1
    Your RAII wrapper has a bug. In case `CoInitialize` fails, you are still calling `CoUninitialize` from the d'tor. To fix this, you need to throw from your c'tor, in case `CoInitialize` fails. Also consider using the [\[\[maybe_unused\]\]](http://en.cppreference.com/w/cpp/language/attributes) attribute specifier (C++17) on that RAII wrapper, so that your compiler doesn't issue a warning. – IInspectable Nov 05 '17 at 16:41
  • @Alex: C functions don't throw C++ exceptions. Nothing to make sure about `CoUninitialize`. – IInspectable Nov 05 '17 at 16:43
  • @IInspectable I missed the fact that `CoUninitialize()` is `C` function. Thanks for pointing. – Alex Nov 06 '17 at 07:15
  • @IInspectable, as a side note, I'm using Visual C++ 2017, and although it raises warnings on unused variables, it seems to be smart enough to **not** warn if the variable belongs to a class which the constructor actually does something. – rodrigocfd Nov 06 '17 at 19:20
  • I noticed the same: VS 2017 doesn't warn about this particular type's instances being unused. I haven't looked into it, to find out more about the heuristics it applies. I also didn't check with Clang to see, if it issues a warning. – IInspectable Nov 06 '17 at 20:12
  • @RbMm, the DLL should take care of the initialization and finalization of the threads it owns (e.g. a worker thread or a thread pool). If its threads need COM, it should `CoInitialize` and `CoUninitialize` in them. If it should create threads at all is another matter. For library-like and non-entry-point functions (as opposed to framework-like or entry-point functions), you're right that it shouldn't mess with the current thread's COM (un)initialization; it can use `CoGetApartmentType` to see if it has been initialized. – acelent Nov 07 '17 at 17:19
  • @PauloMadeira - if dll create own thread - yes, thread creator must intialize com and decide which apartment is use. however this need do once, after thread created, but not multiple time - every time initialize and deinitialize com when some function is called. – RbMm Nov 07 '17 at 17:26

2 Answers2

2

Raymond Chen has been using this method for a while so I'm sure it's OK, just remember to only call CoUninitialize if 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;
  ...
}

Some people might want to throw in the constructor if CoInitialize fails but I feel that's unnecessary because other COM calls down the line will fail. Only do it if you need to capture the exact HRESULT failure code from CoInitialize.

Anders
  • 97,548
  • 12
  • 110
  • 164
  • 4
    Throwing an exception from the c'tor in case initialization fails is the cleaner design. For one, if the c'tor fails, there is no responsibility left for the RAII object, so why keep it around? And then there's the technical aspect: A failed call to `CoInitialize` does not imply, that other COM calls will fail. They could well succeed, and instantiate a COM object into the wrong apartment. In this particular case, where the OP is moving from error codes to exceptions it feels very unnatural to mix error reporting strategies. Suggesting to not use exceptions requires a good reason. – IInspectable Nov 05 '17 at 20:11
  • If you are calling ShellExecute for example, it might need COM or it might not, depends on what you are executing and how the filetype is registered. – Anders Nov 06 '17 at 00:00
  • If your code needs to initialize COM, it doesn't matter, whether someone else on your thread requires COM or not. You need to initialize your thread. And it that fails, there is no way to recover from the failure. I don't know, why you believe that `ShellExecute` were somehow special, or how you propose to recover from a future failure sometime back in time. – IInspectable Nov 06 '17 at 00:56
  • ShellExecute is special because it **might** need COM. In that case it is OK to ignore CoInitialize failure because ShellExecute might still succeed. – Anders Nov 06 '17 at 02:25
  • If COM initialization fails, and you move on to call `ShellExecute[Ex]` nonetheless, that call may indeed succeed. Or fail halfway through. The [dangers of the implicit MTA](https://blogs.msdn.microsoft.com/oldnewthing/20130419-00/?p=4613) are fairly impossible to manage. I'm not convinced that even in case of calling a function that may or may not fail when COM hasn't been initialized on the calling thread, it would be a good idea to leave error discovery to client code. Just throw an exception and be done with it. – IInspectable Nov 06 '17 at 09:28
  • ShellExecute has 4 "modes", only 1 requires COM. 1) Plain CreateProcess 2) DDE 3) Shell extension without COM (Win95 style where they avoided loading ole32 and used the internal "mini COM") 4) Shell extension loaded by CoCreateInstance – Anders Nov 06 '17 at 12:06
  • That doesn't change anything, since you don't know ahead of time, whether COM is required or not. If you call `ShellExecute[Ex]`, you need to initialize COM on the calling thread. Failure to initialize COM is a catastrophic failure. Ignoring it will lead to even harder to detect bugs (as I already explained). You haven't made a convincing point, why not throwing an exception from a failed RAII type's c'tor would be a good idea. – IInspectable Nov 06 '17 at 12:31
1

Is there a better approach to this?

No.

Using the RAII (Responsibility1 Acquisition is Initialization) idiom to clean up upon exit is the standard C++ solution to the problem you are trying to solve. In case of COM, I would propose the following implementation:

#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();
    }
};

Which is used in your example like this:

void foo()
{
    com_init guard{};

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

With C++17 it is possible to mark objects as [[maybe_unused]], to both prevent compiler warnings as well as communicate intent.

Rationale:

The implementation uses a constructor that throws an exception upon failure. There are many good reasons to do so:

  • Space: This implementation does not need to store any state information to allow the destructor to conditionally perform cleanup.
  • Consistency: C++ is built around exceptions. Interleaving exception based code with code that reports errors through error codes is both confusing as well as harder to comprehend.
  • Reliability: Failure to initialize COM is a catastrophic failure. There is no conceivable way to recover from it. It must not be ignored. You cannot ignore exceptions. The default for error codes is to ignore them; you need not do anything to do so.


1 Usually tagged "Resource", but that doesn't quite live up to the versatility it provides. I prefer "Responsibility".
IInspectable
  • 46,945
  • 8
  • 85
  • 181
  • The default copy-constructor and assignment operator should be `=delete`d as the class is not copyable. I would also prefer to use `std::system_error` instead of `_com_error` as a standardized way to report such errors. E. g. `throw std::system_error{hr, std::system_category(), "Failed to initialize COM"};`. The error string adds useful context information. – zett42 Nov 06 '17 at 12:48
  • @zett42: `delete`'ing the default copy constructor and copy assignment operator is a good idea. Throwing a `std::system_error` in place of a `_com_error` has pros and cons. It might be helpful in case of a class library, where client code can consistently handle OS related errors. On the other hand, `_com_error` has all the COM error reporting facilities, like an `IErrorInfo` interface to query for additional information. A developer can choose whichever is more appropriate in their case. The choice of exception doesn't change the essence of this proposed answer, though. – IInspectable Nov 06 '17 at 12:58
  • Sounds reasonable! – zett42 Nov 06 '17 at 14:33