Code for Concinnity


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 🙂

Published by kizzx2, on December 4th, 2010 at 3:40 pm. Filled under: UncategorizedNo Comments

No comments yet.

Leave a Reply