COM 线程/单元行为与编组工厂不一致
Com Threading/Apartment behavior inconsistent with a marshalled factory
我正在尝试使用CoRegisterClassObject来自定义加载包含com对象的dll的方式。我正在尝试一些方法,以解决线程的单元类型与 com 对象的单元类型不匹配时遇到的问题。基本思想是,由于使用 coregisterclassobject 在创建 com 对象时会忽略注册表,因此我需要确保在 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, ®Num);
{
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中创建类工厂,然后对象将驻留在那里。
我看到的问题是,在上面的示例中,线程 ID 并不总是看起来像我期望的那样。如果 FactoryThread 初始化为 Apartment 线程,主线程初始化为多线程,则 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 中创建了一个自由线程对象,则该对象将在 MTA 中的 com 生成的不同线程中创建,然后如果我生成了也在 STA 中的第三个线程,在那里创建对象会将其放在第一个 com-surwed MTA 线程中, 不是一个新的(如果对象的线程模型和线程的单元类型颠倒,则也是如此(。但是,如果我使用 coregisterclassobject 像上面一样创建我自己的工厂,并且该对象是多线程的,但线程在 STA 中,那么创建这些多线程对象的每个新线程都会生成一个新的 MTA 线程,这似乎是浪费并且与通常发生的情况不一致。
在多线程单元中创建类工厂时,可以从多个线程调用它。因此得名"多线程"。你为什么觉得它令人惊讶?
具体而言,COM 运行时维护一个线程池,这些线程池在 MTA 中执行跨单元调用。然后,可以在任何这些线程上调用声明自己为多线程的任何对象。
然后,如果我生成了也在 STA 中的第三个线程,则创建 那里的对象会把它放在第一个 MTA 线程中,而不是 一个新的
这种说法没有多大意义。多线程对象不属于任何特定线程,因此不清楚"对象...放。。。在 MTA 线程中"。可以创建并调用加入 MTA 的任何线程(无论是程序显式创建的线程,还是由 COM 运行时创建的线程(;它可以由多个这样的线程同时调用。
你观察到的行为差异是由于这个事实。跨单元调用以窗口消息的形式传递到 STA 线程。STA 线程通过调用 GetMessage
来表示其已准备好接受传入呼叫。另一方面,对 MTA 的跨单元调用不使用窗口消息,而是使用其他一些未记录和未指定的机制。此类调用只能由 COM 创建的线程池中的线程提供服务 - COM 运行时不能只命令显式创建的线程,因为它不知道该线程在任何给定时间正在执行的操作。没有 API 允许线程说"我已准备好接受并执行任意 COM 调用" - 实际上加入 COM 的线程池。
考虑到这一点,让我们看一下你的方案。案例A:你有一个在ThreadingModel=Free
注册的常规COM对象,没有自定义类工厂的有趣业务。STA 线程使用该对象的CLSID
调用CoCreateInstance
。COM 从注册表中读取信息,发现对象是多线程的,并封送对其 MTA 线程池中的一个线程的调用,该线程池将创建对象并将其接口指针封送回来。如果 STA 线程(同一线程或另一个线程(使用相同的CLSID
再次调用CoCreateInstance
,则重复该过程,并且可能会碰巧池中的同一线程处理它。
顺便说一下,创建对象的线程不必是处理OutputOwningThreadId
调用的同一线程。事实上,如果您连续调用OutputOwningThreadId
两次 - 特别是如果您从多个线程同时调用同一对象 - 它很有可能会报告不同的线程 ID。这是一个用词不当:在MTA中,没有"拥有线程"这样的东西。
情况 B:你旋转显式FactoryThread
,这会创建类工厂,然后忙于做一些事情(它旋转消息泵的事实在 MTA 中无关紧要;它也可以Sleep(INFINITE)
(。此线程禁止 COM 运行时使用;正如我所说,COM 不能在它正在做的任何事情中神奇地中断它,并让它执行一些 COM 调用。因此,就像在案例 A 中一样,所有后续CreateInstance
和(名称不正确的(OutputOwningThreadId
调用都在 COM 维护的线程池中的某些线程上执行,但永远不会在 FactoryThread
上执行。
是的,在你的方法中,你基本上是在浪费一个线程。对于能够避免注册表的好处来说,这似乎并不是一个巨大的代价。
- 大于65535的C++数组[size]引发不一致的溢出
- 在 C++(和 C)中进行类型转换时明显不一致
- 填充上编译器生成的复制构造函数之间的不一致
- 犰狳的 print() 方法和 cout 在从 Rcpp 调用时顺序不一致
- CreateDIBSection为同一图像返回不一致的位图位值
- 在 Qml 中从 QSqlTableModel 中删除单行时视图不一致
- 模板参数推导不一致
- 声明中不一致的no是否违反ODR?
- 如何删除分支因子不一致的树,最大为 30,40
- 从 C++ 函数与 Python 函数返回的不一致值用于偏斜正态分布
- 从 C 字符串构造 std::string 与从另一个 std::string 构造 std::string 不一致
- 这种比较是否不一致(或者存在其他问题)?
- 以下可变参数模板行为是否不一致?
- 如何修复我的链表读数不一致的问题?
- 在C++17中,为什么类模板和函数模板的指针类型推导明显不一致
- void 函数中的指针参数返回不一致的值
- 如何查找导致结果不一致的代码
- 跨平台 mySQL 与字符集不一致
- C++:不一致的 std::p ow( 类型 ) 定义
- COM 线程/单元行为与编组工厂不一致