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):
- 0×80070057
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| #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
1 2 3 4 5 6 7 8 9
| 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
1 2 3 4 5 6 7 8 9 10 11 12
| 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).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| // 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();
} |
0×80070057 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| 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
Releaseing 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| 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