Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

COM initialization and cleanup appropriate at the function-level granularity?

Tags:

c++

c

com

winapi

Consider writing a reusable custom function that inside its body creates COM objects and calls methods to some COM interfaces. For this to work properly, CoInitializeEx and the matching CoUninitialize APIs must be called.

Calling those COM initialization and cleanup APIs inside the function's body would hide a COM implementation detail to the caller, and would remove a burden from the caller as well.

But is calling CoInitializeEx and the matching CoUninitialize inside function's body considered a good coding practice?

Would calling those COM init/cleanup functions at the function-granularity level imply too much overhead for each function call?

Are there other drawbacks in this design?

like image 838
Mr.C64 Avatar asked Dec 02 '16 16:12

Mr.C64


1 Answers

It is a terrible practice and fundamentally wrong. What matters a great deal is the value for the 2nd argument (dwCoInit). It must be COINIT_APARTMENTTHREADED, often abbreviated to STA, or COINIT_MULTITHREADED (MTA). This is a promise that you make, cross-your-heart-hope-to-die style. If you break the promise then the program will die. Usually by deadlocking, not getting expected events or having unacceptably slow perf.

When you select STA then you promise that the thread is well-behaved and can support COM components that are not thread-safe. Fulfilling that promise requires that the thread pumps a message loop and never blocks. The common behavior of a thread that supports a GUI for example. The vast majority of COM components are not thread-safe.

When you select MTA then you don't promise any support at all. The component must now fend for itself to keep itself thread-safe. Often done automatically by having the COM infrastructure creating a thread by itself to give the component a safe home. A further detail that you need to take care of is marshaling the interface pointer, requires CoMarshalInterThreadInterfaceInStream() helper function or the more convenient IGlobalInterfaceTable interface. This ensures that a proxy is created that takes care of the required thread context switch.

MTA sounds convenient, but not without consequences, a simple property getter call can take as much as x10000 more time. Overhead imposed by the thread context switches and the need to copy any arguments and the return value across stack frames. And marshaling the interface pointer can easily fail, authors of COM components often don't provide the necessary proxy/stub or they intentionally omitted it because it is just plain too difficult or expensive to copy the data.

Key point is that the choice between STA and MTA can never be made by a library. It does not know beans about the thread, it did not create that thread. And cannot possibly know if the thread pumps a message loop or blocks. That's all code that is entirely out of the library's reach. Otherwise the exact reason that the COM infrastructure needs to know this as well, it likewise cannot make assumptions about the thread.

The choice must be made by the code that created and initialized the thread, invariably the app itself. Unless the library creates a thread for the purpose of making the calls safe. But then with the consequence of code always being slow. You remind the caller of your library that he didn't get it right by returning the inevitable CO_E_NOTINITIALIZED error code.

Fwiw, this is something you see back in the .NET Framework. The CLR always calls CoInitializeEx() before a thread can execute any managed code. Still a choice that must be made by the programmer of the app, or more typically the project template, done with the [STAThread] attribute on Main() or the Thread.SetApartmentState() call for a worker thread.

like image 80
Hans Passant Avatar answered Oct 14 '22 15:10

Hans Passant