Abstracting away Windows COM dependencies

The Windows Component Object Model (COM) system is a massive, massive dependency.  How big and complex is it?  Well, I usually list it on my resume as a separate line item.

When you first decide to incorporate COM-aware code into your application, you’re signing up to import an entire ecosystem of macros, interfaces, types, headers, class-specific smart pointers… and headaches.  Naturally, it makes sense to try and segregate that mess from the rest of your pristine code.  Encapsulate it in the smallest container possible.  In general – when I require functionality from a COM type – I immediately abstract it away, behind a more user-friendly, ‘standard’ C++ helper class or interface.

If you do it right, consumers of your code never need to know it’s utilizing COM at all.

Goals

In order to properly ‘hide’ the messy details of a COM dependency, we need to identify what those messy details are, and outline some standard steps you can take to hide them.

Throughout all of this, the overarching end-goal will be the same: to declare a simple interface in the header (.h/.hpp) file, and hide all the COM-related dependencies in the implementation (.cpp/.cxx) file.

COM Header Files, Support Classes, and #import Statements

COM components utilize a set of header files – usually including, at a minimum, the COM support file “comdef.h.”  These header files contain COM-specific types, such as _bstr_t , _variant_t , _com_error , and _com_ptr_t .  These support types are convenient when dealing with COM interfaces, but they’re only needed for COM, so there’s no reason we should burden our shared application code with this dependency.  In order to quarantine these headers and types, we should take steps to ensure that any COM-specific data types or functions are only used in the implementation file; never include them in your class’s public header.

If you discover that your class needs to store COM-specific member variables; fear not!  You can still restrict those members to the implementation file, by using the pimpl idiom.  Speaking from experience; moving everything into an ‘Impl structure’ can sometimes feel like extra work, but given the choice between “including COM headers” and a clean pimpl-based interface, I’ll take the pimpl anyday!

Along the same vein as COM-specific header files; the #import statement is pure magic.  Given a path to a COM DLL (or a type library (.tlb) file), the ‘import’ directive will automatically generate the associated smart pointers, type-identifiers (GUIDs), and interface definitions contained therein.  Whenever possible, I recommend using #import as a way to spare yourself a lot of boilerplate COM setup.

Just make sure you only use it in your implementation file; don’t let it pollute the rest of your application code, by accidentally placing it in your interface header (or – God forbid – the precompiled header).

CoInitialize/CoUninitialize

This is a tough one.  COM classes are instantiated within a logical container called an ‘apartment.’  Your application can contain multiple apartments, but a given COM object can only exist within a single apartment at a time.

From the Microsoft documentation:

There are two types of apartments: single-threaded apartments, and multi-threaded apartments.

  • Single-threaded apartments consist of exactly one thread, so all COM objects that live in a single-threaded apartment can receive method calls only from the one thread that belongs to that apartment. All method calls to a COM object in a single-threaded apartment are synchronized with the windows message queue for the single-threaded apartment’s thread. A process with a single thread of execution is simply a special case of this model.

  • Multi-threaded apartments consist of one or more threads, so all COM objects that live in an multi-threaded apartment can receive method calls directly from any of the threads that belong to the multi-threaded apartment. Threads in a multi-threaded apartment use a model called free-threading. Calls to COM objects in a multi-threaded apartment are synchronized by the objects themselves.

The long and short of it is; in order to use a COM object, the host application needs to assign the currently-running thread into a COM apartment.  This is not ideal, because we’re trying to hide COM-related responsibilities from the host application.

Unfortunately, there’s no way around this one.  The host application must initialize a COM apartment, and it would be presumptuous of our helper class to try to do this automatically; we can’t assume the type of apartment most appropriate for the host application.  Regardless; we can take steps to do this in a clean, object-oriented and interface-driven manner.

The ApartmentContext Helper Class

Typically, initializing a COM apartment would involve a call to the CoInitialize or CoInitializeEx  functions.  Subsequently, it must be matched with a corresponding call to CoUninitialize  when the process (or thread) shuts down.

Clearly, this behavior is an ideal candidate for an RAII wrapper.

Since we’d like to avoid COM-specific header files and DWORD values, we will provide the user with a helper class:

The implementation for this helper class is simple, and effectively hides the COM-specific (and Windows-specific) includes:

The static construction methods initialize the COM apartment and leave no room for misunderstanding.  The destructor cleans up after itself.  If anything goes wrong – if the user accidentally attempts to create two conflicting ApartmentContext  instances – a _com_error  will be thrown, which should be relatively easy to debug.

Creating and maintaining a COM apartment is then as simple as instantiating an ApartmentContext, before creating or using our COM-dependant helper class.

So we have provided a means to the end user for creating a COM apartment, but how do we make it a requirement?  Remember, we want to leave no room for error!

Well; how do you express any required dependency in code?  Make it a constructor argument!

Potential Improvements

This isn’t quite foolproof, yet, and there are additional improvements we could make to this strategy.  What if the ApartmentContext is destroyed while the helper class is still being used?  That’s an error.

Currently, the ApartmentContext has copy-construction and assignment disabled.  It might be better to enable this, and invoke CoInitializeEx  every time a new copy is made!  In this way, we could pass the ApartmentContext  into our helper class by value, effectively keeping a ref-count of the number of objects using the ApartmentContext.  Only when the last object goes out of scope, will the Apartment be uninitialized.

One Reply to “Abstracting away Windows COM dependencies”

  1. COM is so bad. We got rid of it from our code when we ported to Linux, but of course we still have downstream dependencies on it.

    I also list COM on my résumé, it’s a signal flare to anyone that knows what to look for. It basically says “I don’t care how complicated you think your code is, I got this.” That’s probably not universally true, but I personally believe if you can learn and code in COM there’s not much that should scare you.

    I think your wrapper class for apartment threading is a cool idea. We didn’t use one and it wasn’t a big deal, but if I HAD to start a new project I would do it.

Leave a Reply

Your email address will not be published. Required fields are marked *