Question

I'm trying to use CoRegisterClassObject to customize the way I load dll's that have com objects in them. I'm trying something out that will solve a problem I was having when the thread's apartment type didn't match the com object's. The basic idea is that since using coregisterclassobject ignores the registry when creating the com object, I need to make sure that STA objects are created in STA threads, and the same for MTA objects. Here's a sample that I wrote as a proof of concept that's not always behaving as I expect.

LPSTREAM factory_stream = NULL; //GLOBAL VARIABLE FOR TEST

DWORD __stdcall FactoryThread(LPVOID param)
{
   CoInitialize(NULL);
   //CoInitializeEx(NULL, COINIT_MULTITHREADED);

   cout << GetCurrentThreadId(); //THREAD_ID_2

   CustomClassFactory *factory = new CustomClassFactory();
   factory->AddRef();
   CoMarshalInterThreadInterfaceInStream(IID_IClassFactory, (IClassFactory*)factory, &factory_stream);
   MSG msg;
   while (GetMessage(&msg, NULL, 0, 0)) 
   {
     TranslateMessage(&msg);
     DispatchMessage(&msg);
   }
   factory->Release();
   CoUninitialize();
   return 0;
}

And here's the relevant part of my main function.

//CoInitialize(NULL);
CoInitializeEx(NULL, COINIT_MULTITHREADED);

cout << GetCurrentThreadId(); //THREAD_ID_1

HANDLE regThread = CreateThread(NULL, 0, FactoryThread, NULL, 0, NULL);
Sleep(5000); //ensures that the factory is registered

IClassFactory *factory = NULL;
CoGetInterfaceAndReleaseStream(factory_stream, IID_IClassFactory, (void**)&factory);

DWORD regNum = 0;
HRESULT res = CoRegisterClassObject(clsid, factory, CLSCTX_INPROC_SERVER, REGCLS_MULTI_SEPARATE, &regNum);
{
   TestComObjLib::ITestComObjPtr ptr;
   HRESULT hr = ptr.CreateInstance(__uuidof(TestComObjLib::TestComObjCoClass), NULL);
   ptr->OutputOwningThreadId(); //THREAD_ID_3 is just from cout << GetCurrentThreadId()

   TestComObjLib::ITestComObjPtr ptr2;
   HRESULT hr = ptr2.CreateInstance(__uuidof(TestComObjLib::TestComObjCoClass), NULL);
   ptr2->OutputOwningThreadId(); //THREAD_ID_4
}
CoRevokeClassObject(regNum);
CoUninitialize();

The idea was that since the registry shouldn't be used with CoRegisterClassObject, I needed to manually create the apartment threaded objects in an STA instead of the current MTA thread, and vice versa. I noticed that when not using CoRegisterClassObject, CoGetClassObject spawns a new thread and calls DllGetClassObject in that thread, so I figured the class factory just needed to be created in an STA and then the objects will live there.

The problem I'm seeing is that in the example above, the thread ids don't always end up looking like I expect them to. If FactoryThread is initialized as Apartment threaded, and the main thread as multithreaded, then THREAD_ID_2 == THREAD_ID_3 == THREAD_ID_4 != THREAD_ID_1 as expected (the factory is creating these objects and they can live in the factory's thread). If those threading models are switched though, then thread_id_3 == thread_id_4, but they are different than thread_id_2 and thread_id_1, even though the com objects can be created in thread 2.

This seems inconsistent, and can cause unwanted behavior in situations where another thread is involved. When relying only on the registry and not using coregisterclassobject, if i created a free-threaded object in a STA, the object would be created in a different thread spawned by com that was in the MTA, and then if i spawned a third thread that was also in a STA, creating the object there would put it in the first com-spawned MTA thread, not a new one (The same would be true if the object's threadingmodel and the threads' apartment types were reversed). However, if I was to use coregisterclassobject to make my own factory like above, and the object was multithreaded but the thread was in the STA, then each new thread that created these multithreaded objects would spawn a new MTA thread, which seems wasteful and inconsistent with what normally happens.

Was it helpful?

Solution

When you create your class factory in a multi-threaded apartment, it can be called from multiple threads. Hence the name "multi-threaded". Why exactly do you find it surprising?

Specifically, COM runtime maintains a pool of threads that execute cross-apartment calls into MTA. Any object that declares itself to be multi-threaded can then be called on any of those threads.

and then if i spawned a third thread that was also in a STA, creating the object there would put it in the first com-spawned MTA thread, not a new one

This statement doesn't make much sense. Multi-threaded objects don't belong to any particular thread, so it's not clear what you mean by "object ... put ... in MTA thread". A multi-threaded object may be created, and called on, any thread that joined MTA (whether a thread your program created explicitly, or one created by COM runtime); it may be called by several such threads simultaneously.

The difference in behavior you observe is due to this fact. Cross-apartment calls are delivered to STA threads in the form of window messages. An STA thread signals its readiness to accept incoming calls by calling GetMessage. On the other hand, cross-apartment calls into MTA do not use window messages, but some other, undocumented and unspecified, mechanism. Such a call can only be served by a thread from the COM-created thread pool - COM runtime cannot just commandeer a thread you've explicitly created, since it doesn't know what that thread is doing at any given time. There is no API that allows your thread to say "I'm ready to accept and execute arbitrary COM calls" - to join COM's thread pool, in effect.

With this in mind, let's look at your scenarios. Case A: you have a regular COM object registered with ThreadingModel=Free, no funny business with custom class factory. An STA thread calls CoCreateInstance with that object's CLSID. COM reads the information from the registry, discovers that the object is multi-threaded, and marshals the call to one of the threads in its MTA thread pool, which creates the object and marshals its interface pointer back. If an STA thread (either the same one, or another one) calls CoCreateInstance again with the same CLSID, the process is repeated, and it may just so happen that the same thread from the pool handles it.

By the way, the thread that created the object doesn't have to be the same thread that handles OutputOwningThreadId call. In fact, if you call OutputOwningThreadId twice in a row - and especially if you call it simultaneously on the same object from multiple threads - chances are high that it will report different thread IDs. It's a misnomer: in MTA, there ain't no such thing as an "owning thread".

Case B: you spin your explicit FactoryThread, which creates the class factory, and then gets busy doing something (the fact that it's spinning a message pump is irrelevant in MTA; it could just as well Sleep(INFINITE)). This thread is off-limits to COM runtime; as I said, COM can't magically interrupt it in the middle of whatever it's doing, and make it execute some COM call. So, just as in case A, all subsequent CreateInstance and (badly named) OutputOwningThreadId calls are executed on some threads from COM-maintained thread pool, but never on FactoryThread.

Yes, in your approach, you are basically wasting one thread. This doesn't seem like a huge price to pay for the benefit of being able to avoid the registry.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top