Dissecting obscure COM errors
Here’s how I solved these hair-pulling COM errors:
- 0x800401fd `CO_E_OBJNOTCONNECTED` Object is not connected to server.
- 0x8001010e `RPC_E_WRONG_THREAD` The application called an interface that was marshalled for a different thread.
And this one when I called IGlobalInterfaceTable::GetInterfaceFromGlobal
(through CComGITPtr
):
- 0x80070057 `E_INVALIDARG` The parameter is incorrect.
The basic framework
To make sense of the following code segments, here’s the whole program’s framework to use for the following scenarios:
We use the boost library for threading to make the code less noisy of plumbing work. You don’t really need to grasp them to understand what’s going on.
I have chosen to use the SHDocVw::IShellWindowsPtr interface because it should be present in every Windows environment and simplifies our example without polluting it with the IDL of an example object.
#import "C:/Windows/system32/shdocvw.dll" #include <iostream> #include <Windows.h> #include <comutil.h> #include <boost/thread.hpp> #include <boost/ref.hpp> namespace { // Convenient error checking -- these will fail our // program with nicely printed error message in case of // any COM error void PrintHR(HRESULT hr) { std::wcerr << L"0x" << std::hex << hr << L" " << _com_error(hr).ErrorMessage() << std::endl; } void EnsureSuccess(HRESULT hr) { if(FAILED(hr)) { PrintHR(hr); exit(EXIT_FAILURE); } } // A simple wrapper with integrated error checking void MyCoInitialize() { HRESULT hr = CoInitialize(NULL); EnsureSuccess(hr); } // This method can be called in a thread to demonstrate // multi-threading issues void CreateShellWindow(SHDocVw::IShellWindowsPtr & retval) { // Because we might be in a different thread, we need // to call CoInitialize() just to make sure MyCoInitialize(); HRESULT hr = retval.CreateInstance(__uuiof(SHDocVw::ShellWindows)); EnsureSuccess(hr); CoUninitialize(); } // This should print a numeric number // On my machine it always printed "1". The meaning of that // is not really important. void UseShellWindow(const SHDocVw::IShellWindowsPtr & ptr) { MyCoInitialize(); std::cout << ptr->Count << std::endl; CoUninitialize(); } void Example() { // Our example code segments would go here... } } int main() { try { Example(); } catch(const _com_error & e) { PrintHR(e.Error()); return EXIT_FAILURE; } return 0; }
0x800401fd CO_E_OBJNOTCONNECTED Object is not connected to server.
This one is pretty straight forward. You get this when you try to use a COM object after you have called CoUninitialize()
:
How to reproduce
void Example() { MyCoInitialize(); SHDocVw::IShellWindowsPtr ptr; CreateShellWindow(ptr); CoUninitialize(); UseShellWindow(ptr); // ka-boom! }
0x8001010e RPC_E_WRONG_THREAD The application called an interface that was marshalled for a different thread.
How to reproduce
void Example() { MyCoInitialize(); SHDocVw::IShellWindowsPtr ptr; CreateShellWindow(ptr); // This will use another thread to call `UseShellWindow()` boost::thread(&UseShellWindow, ptr); // ka-boom CoUninitialize(); }
What happened
The error message is pretty obvious — we need to marshal the pointer for thread A if the pointer was created in thread B, in order to use it in thread A.
How to fix
If you use ATL, one very convenient way to marshal them is to use the GIT (Global Interface Pointer) through CComGITPtr.
(The below serves as a quick fix and introduction. Please look into Global Interface Pointer and stuffs if you want to understand what’s going on).
// A GIT cookie is something you can use to retrieve a pointer // from the GIT void UseShellWindowFromCookie(DWORD cookie) { MyCoInitialize(); SHDocVw::IShellWindowsPtr ptr; CComGITPtr<SHDocVw::IShellWindows>(cookie).CopyTo(&fred); UseShellWindow(ptr); CoUninitialize(); } void Example() { MyCoInitialize(); SHDocVw::IShellWindowsPtr ptr; CreateShellWindow(ptr); // This will register our pointer in the GIT and // retrieve a GIT cookie in one line DWORD cookie = CComGITPtr<SHDocVw::IShellWindows>(ptr).Detach(); // Spawn a new thread of the GIT aware version of // UseShellWindow() boost::thread(&UseShellWindowFromCookie, cookie).join(); CoUninitialize(); }
0x80070057 E_INVALIDARG The parameter is incorrect.
After discovering how easy it is to marshal COM objects across threads, you may sometimes get E_INVALIDARG
when inside IGlobalInterfaceTable::GetInterfaceFromGlobal()
Now this one is really really obscure:
How to reproduce
void CreateShellWindowCookie(DWORD & retval) { MyCoInitialize(); SHDocVw::IShellWindowsPtr ptr; CreateShellWindow(ptr); retval = CComGITPtr<SHDocVw::IShellWindows>(ptr).Detach(); CoUninitialize(); } void Example() { MyCoInitialize(); // Instead of using a different thread for UseShellWindow() // This time around we use a different thread to // create the COM pointer DWORD cookie; boost::thread(&CreateShellWindowCookie, boost::ref(cookie)).join(); // Let's try to use it from the current thread UseShellWindowFromCookie(cookie); // ka-boom! CoUninitialize(); }
What happened
This is similar to what happened with CO_E_OBJNOTCONNECTED
, only a lot more subtle and obscure this time around.
To understand this, keep this in mind: A COM object is thread aware. A COM object can only ever live in one single thread, even though it can be accessed from another thread (through marshaling).
What that simply means is that when the thread holding the COM object terminates, the COM object becomes invalid. In our example above, CreateShellWindowCookie()
was run from a separate thread and finished before we even tried to use it. The actual COM object went down with the thread. Even though we had a cookie, the GIT would fail to find the actual COM object when it tries to do GetInterfaceFromGlobal()
. Too bad that it gives such a confusing error message.
To summarize, here are the things to check when you get E_INVALIDARG
from GetInterfaceFromGlobal()
:
- Maybe the originating thread that called `CoCreateInstance()` has ended
- Maybe the originating thread has already called `CoUninitialize()`
- Maybe the actual object has already been terminated by someone `Release`ing it.
- Maybe your GIT cookie has already been used. Strangely enough, it seems like the GIT cookie can only be used to get the interface back _once_.
How to fix
There are 2 basic approaches:
- Do not create any COM objects from threads other than the main thread (which is guaranteed to last through the program’s life time)
- Have an elaborate inter-thread communication so that the creator-thread is kept-alive until the COM object’s users are finished with it (This can get really complicated).
To demonstrate, here’s the code that would work:
void CreateShellWindowCookie(DWORD & retval) { MyCoInitialize(); SHDocVw::IShellWindowsPtr ptr; CreateShellWindow(ptr); retval = CComGITPtr<SHDocVw::IShellWindows>(ptr).Detach(); // Sleep 10 seconds Sleep(10 * 1000); CoUninitialize(); } void Example() { MyCoInitialize(); // Create a cookie from another thread // We do not wait for it to finish DWORD cookie; boost::thread t(&CreateShellWindowCookie, boost::ref(cookie)); // We wait 3 seconds Sleep(3 * 1000); // Now try to use it from cookie UseShellWindowFromCookie(cookie); // OK! CoUninitialize(); }
There you go
Hopefully this saved you from some hair-pulling experiences 🙂