tags:

views:

114

answers:

2

I have created an in-process COM object (DLL) using ATL. Note that this is an object and not a control (so has no window or user-interface.) My problem is that I am trying to fire an event from a second thread and I am getting a 'Catastrophic failure' (0x8000FFFF). If I fire the event from my main thread, then I don't get the error. The second thread is calling CoInitializeEx but this makes no difference. I am using the Apartment threading model but switching to Free Threaded doesn't help.

The fact I am trying to do this from a second thread is obviously crucial. Is there an easy way to do this or am I going to have to implement some hidden-window form of messaging?

For example, in my main object's source file:

STDMETHODIMP MyObject::SomeMethod(...)
{
  CreateThread(NULL, 0, ThreadProc, this, 0, NULL);
  // Succeeds with S_OK
  FireEvent(L"Hello, world!");
  return S_OK;
}

DWORD WINAPI ThreadProc(LPVOID param)
{
  CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
  MyObject* comObject = reinterpret_cast<MyObject*>(param);
  // Fails with 0x8000FFFF
  comObject->FireEvent(L"Hello, world!");
}

void MyObject::FireEvent(BSTR str)
{
  ...
  // Returns 0x8000FFFF if called from ThreadProc
  // Returns S_OK if called from SomeMethod
  pConnection->Invoke(dispid, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &params, NULL, NULL, NULL);
}
A: 

I think the real problem is not what your component is configured with, but the host process. Many hosts, like the Office Object Model ones, live in a single threaded apartment in which case it is not allowed to call them from anything but the main thread.
If that is the case you can let COM do the work by using the single threaded apartment model and moving the actual call to a function in a CoClass and invoke that function from your thread.
That only passes window messages behind the scenes as well, but spares you from implementing this yourself.

Threading in COM (wikipedia):
The Single-Threaded Apartment (STA) model is a very commonly used model. Here, a COM object stands in a position similar to a desktop application's user interface. In an STA model, a single thread is dedicated to drive an object's methods, i.e. a single thread is always used to execute the methods of the object. In such an arrangement, method calls from threads outside of the apartment are marshalled and automatically queued by the system (via a standard Windows message queue). Thus, there is no worry about race conditions or lack of synchronicity because each method call of an object is always executed to completion before another is invoked.

See also Message Pumping in the same article.

Georg Fritzsche
Do you have more details on what would be involved here?
Rob
Updated the answer with more information - don't have the time now to crawl through msdn for concise details :)
Georg Fritzsche
To cut it short though: if your host application is a STA you need to call from the owning thread or invoke the call on the right thread through COM (marshalling). Otherwise you have to take care of calling from the right thread yourself.
Georg Fritzsche
+2  A: 

COM basics

In STA your object lives on a single thread (The Thread). This thread is the one it's created on, it's methods are executed on and it's events are fire on. The STA makes sure that no two methods of your object are executed simultaneously (because they have to be executed on The Thread so this is a nice consequence).

This does not mean that your object can't be accessed from other threads. This is done by creating proxies of your object for every thread other than The Thread. On The Thread you pack an IUnknown with CoMarshalInterThreadInterfaceInStream and on the other thread you unpack with CoGetInterfaceAndReleaseStream which actually creates the proxy on the other thread. This proxy uses the message pump to sync calls to you object, calls that are still executed on The Thread, so The Thread has to be free (not busy) to executed a call from another thread.

In your case you want your object to be able to execute methods on one thread and rise events on another thread. So this has to happen in MTA, so your object has to live in MTA, so your class has to be free-threaded. Threads belong to exactly one apartment, so a thread can not be in MTA and STA simultaneously. If your object lives in MTA whenever an STA object tries to use it, it will have to create a proxy. So you get a slight overhead.

What I guess is that you are thinking of some very clever "technique" to offload your main thread, and do some "async" events, which will not fly in the end :-)) If you think about it there has to a listener on this second [worker] thread...

Btw, this line

MyObject* comObject = reinterpret_cast<MyObject*>(param);

can be done in MTA only.

wqw
Very good explanation. Thank you very much.
Rob