Com Threading / квартира поведение несовместимо с маршаллической фабрикой

Я пытаюсь использовать CoRegisterClassObject, чтобы настроить способ загрузки библиотек DLL, в которых есть объекты com. Я пробую что-то, что решит проблему, с которой я столкнулся, когда тип квартиры потока не соответствовал объекту com. Основная идея заключается в том, что поскольку при создании com-объекта использование coregisterclassobject игнорирует реестр, мне необходимо убедиться, что объекты STA создаются в потоках STA, и то же самое для объектов MTA. Вот пример, который я написал в качестве доказательства концепции, которая не всегда ведет себя так, как я ожидаю.

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;
}

И вот соответствующая часть моей основной функции.

//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();

Идея заключалась в том, что, поскольку реестр не должен использоваться с CoRegisterClassObject, мне нужно было вручную создавать потоковые объекты квартиры в STA вместо текущего потока MTA, и наоборот. Я заметил, что когда CoRegisterClassObject не используется, CoGetClassObject порождает новый поток и вызывает DllGetClassObject в этом потоке, поэтому я подумал, что фабрику классов нужно просто создать в STA, а затем объекты будут там жить.

Проблема, которую я вижу, заключается в том, что в приведенном выше примере идентификаторы потоков не всегда выглядят так, как я ожидал. Если FactoryThread инициализируется как квартира многопоточная, а основной поток многопоточный, то THREAD_ID_2 == THREAD_ID_3 == THREAD_ID_4! = THREAD_ID_1, как и ожидалось (фабрика создает эти объекты, и они могут жить в потоке фабрики). Если эти модели потоков переключаются, то thread_id_3 == thread_id_4, но они отличаются от thread_id_2 и thread_id_1, даже если в потоке 2 могут быть созданы объекты com.

Это кажется противоречивым и может вызвать нежелательное поведение в ситуациях, когда задействован другой поток. Если я полагаюсь только на реестр и не использую coregisterclassobject, если бы я создал объект с бесплатной резьбой в STA, объект был бы создан в другом потоке, созданном com, который был в MTA, а затем, если бы я создал третий поток, который Также был в STA, создание объекта, который поместил бы его в первый созданный поток MTA, а не в новый (То же самое было бы верно, если бы потоковая модель объекта и типы квартир потоков были изменены на обратные). Однако, если бы я использовал coregisterclassobject для создания своей собственной фабрики, как описано выше, и объект был многопоточным, но поток находился в STA, то каждый новый поток, создавший эти многопоточные объекты, породил бы новый поток MTA, который кажется расточительным и противоречивым с тем, что обычно происходит.

0

Решение

Когда вы создаете фабрику классов в многопоточной квартире, ее можно вызывать из нескольких потоков. Отсюда и название «многопоточный». Почему именно ты находишь это удивительным?

В частности, среда выполнения COM поддерживает пул потоков, которые выполняют вызовы между квартирами в MTA. Любой объект, который объявляет себя многопоточным, может затем вызываться в любом из этих потоков.

а затем, если я породил третий поток, который был также в STA, создавая
объект там поместил бы его в первый поток MTA,
новенький

Это утверждение не имеет особого смысла. Многопоточные объекты не принадлежат какому-либо конкретному потоку, поэтому неясно, что вы подразумеваете под «объект … положить … в поток MTA». Многопоточный объект может быть создан и вызван в любом потоке, присоединившемся к MTA (будь то поток, созданный вашей программой явно, или поток, созданный во время выполнения COM); он может вызываться несколькими такими потоками одновременно.

Различие в поведении, которое вы наблюдаете, связано с этим фактом. Звонки между квартирами доставляются потокам STA в виде оконных сообщений. Поток STA сигнализирует о своей готовности принимать входящие вызовы, вызывая GetMessage, С другой стороны, звонки между квартирами в MTA используют не оконные сообщения, а какой-то другой, недокументированный и неуказанный механизм. Такой вызов может обслуживаться только потоком из пула потоков, созданного COM — среда выполнения COM не может просто управлять потоком, который вы явно создали, так как он не знает, что этот поток делает в любой момент времени. Нет API, позволяющего вашему потоку сказать «я готов принимать и выполнять произвольные вызовы COM» — по сути, присоединиться к пулу потоков COM.

Имея это в виду, давайте посмотрим на ваши сценарии. Случай A: у вас есть обычный COM-объект, зарегистрированный в ThreadingModel=FreeНикаких смешных дел с фабрикой нестандартного класса. Поток STA вызывает CoCreateInstance с этим объектом CLSID, COM считывает информацию из реестра, обнаруживает, что объект является многопоточным, и направляет вызов одному из потоков в своем пуле потоков MTA, который создает объект и отправляет обратно указатель на интерфейс. Если поток STA (или тот же, или другой) вызывает CoCreateInstance снова с тем же CLSIDпроцесс повторяется, и может случиться так, что его обрабатывает тот же поток из пула.

Кстати, поток, создавший объект, не обязательно должен быть тем же потоком, который обрабатывает OutputOwningThreadId вызов. На самом деле, если вы позвоните OutputOwningThreadId два раза подряд — и особенно если вы вызываете его одновременно для одного и того же объекта из нескольких потоков — высока вероятность того, что он сообщит разные идентификаторы потоков. Это неправильно: в MTA не существует такой вещи, как «собственный поток».

Случай Б: вы раскручиваете свое явное FactoryThread, который создает фабрику классов, а затем начинает делать что-то (тот факт, что он вращает насос сообщений, не имеет значения в MTA; Sleep(INFINITE)). Этот поток недоступен во время выполнения COM; как я уже сказал, COM не может волшебным образом прервать его в середине того, что он делает, и заставить его выполнить некоторый вызов COM. Итак, как и в случае А, все последующие CreateInstance и (плохо названный) OutputOwningThreadId вызовы выполняются в некоторых потоках из поддерживающего COM пула потоков, но никогда не включаются FactoryThread,

Да, в вашем подходе вы в основном тратите один поток. Это не кажется огромной ценой, чтобы заплатить за возможность избежать регистрации.

3

Другие решения

Других решений пока нет …