【问题标题】:COM object methods are not executed on the thread that CoInitialize-d and created the objectCOM 对象方法不在 CoInitialize-d 并创建对象的线程上执行
【发布时间】:2012-09-11 07:45:46
【问题描述】:

我正在开发一个 UI 应用程序,它会一路创建一个 COM 对象。 问题是,我想将这个 COM 对象完全“移动”到不同的线程上。

我的做法是这样的:

  • 创建我想将对象移动到的新线程(使用 CreateThread API)
  • 进入该线程后,我正在调用 PeekMessage 为其设置消息队列
  • 调用CoInitialize,CoCreateInstance创建COM对象,QueryInterface得到我想要的接口
  • 最后,我在界面上调用了一个方法,该方法显示一个带有 GetCurrentThreadId() 返回值的 MessageBox(我可以访问该对象所在的 COM 库的 VB6 代码)。

问题是,正如此消息框所示,对象方法仍然在原始 UI 线程上执行,而不是在我创建并完成所有这些步骤的线程上执行。还有一点要提一下,在调用接口方法之后,我也在里面设置了一个经典的消息循环。

如何改变这种行为并实现我想要的? (也就是说,我希望源自我新创建的线程的 COM 对象调用在 IT 上执行,而不是在原始应用程序线程上)

这里有一些伪代码使它更清晰:

void myMainUIMethod(){
  MessageBox(GetCurrentThreadId()); // displays 1
  CreateThread(&myCOMObjectThreadProc);
}
void myCOMObjectThreadProc(){
  MessageBox(GetCurrentThreadId()); // displays 2
  CoInitialize(NULL);
  myObject = CoCreateInstance(myObjectsCLSID);
  myObjectInterface = myObject->QueryInterface(myObjectInterfaceCLSID);
  myObjectInterface->showThreadIDMessageBox(); // this would be the COM object method call
}

And, in the VB6 code of the object, here's the pseudo-definition of showThreadIDMessageBox.
Public Sub showThreadIDMessageBox()
  Call MessageBox(GetCurrentThreadId()) //displays 1, I want it to display 2
End Sub

在创建新线程之前,我通过在主线程上进行 CoUninitalizing 实现了我想要的。但是为什么会这样呢?如果在我创建新线程之前在主线程上初始化了 COM,也许由于某种原因它必须是......我不希望应用程序稍后崩溃,因为我必须在创建新线程之前调用 CoUninitialize。下面是一些伪代码,说明无论哪个线程首先调用 CoInitialize 都会被 STA 对象选中。

void myMainUIMethod(){
  MessageBox(GetCurrentThreadId()); // displays 1
  CoUninitialize(); // uninitialize COM on the main thread
  CreateThread(&myCOMObjectThreadProc);
  ***i: MessageBox("When you want to initialize COM on main thread, confirm this");
  CoInitialize();
}
void myCOMObjectThreadProc(){
  MessageBox(GetCurrentThreadId()); // displays 2
  ***ii: MessageBox("When you want to initialize COM on the new thread, confirm this");
  CoInitialize(NULL);
  myObject = CoCreateInstance(myObjectsCLSID);
  myObjectInterface = myObject->QueryInterface(myObjectInterfaceCLSID);
  myObjectInterface->showThreadIDMessageBox(); // this shows 2 IF ***ii is confirmed before ***i, 1 otherwise
}

非常感谢您, 科尼留

【问题讨论】:

    标签: multithreading com methods invoke


    【解决方案1】:

    看起来您的问题是您的 COM 组件线程模型在注册表项 InprocServer32未指定。这意味着该对象被视为 STA(单线程单元),但将被加载到 主(或主机)STA,而不是创建它的 STA。这是第一个调用CoInitialize 的线程。要在名为 CoCreateInstance 的同一 STA 中创建,您必须创建 HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{Your CLSID}\InprocServer32@ThreadingModel 注册表值并将其设置为 Apartment

    引用自 MSDN (InprocServer32 registry key documentation):

    如果 ThreadingModel 不存在或未设置为值,则服务器将加载到进程中初始化的第一个单元中。该单元有时称为主单线程单元 (STA)。如果进程中的第一个 STA 由 COM 初始化,而不是通过显式调用 CoInitialize 或 CoInitializeEx,则称为主机 STA。例如,如果要加载的进程内服务器需要 STA,但当前进程中没有 STA,则 COM 会创建一个主机 STA。

    【讨论】:

    • 谢谢!你是完全正确的。现在 COM 对象在我创建的线程上执行,即使我没有在主线程上进行 CoUninitialized。我会将这篇文章和 Brendan 的上一篇文章标记为答案,因为他指出我的文章也指出了这一点(顺便说一下,Brendan 的文章非常好)。谢谢大家的回答!
    【解决方案2】:

    我终于实现了我想要的! 在主 UI 线程中添加 CoUninitialize 调用,在创建新线程之前已解决此问题。发生这种情况是因为 STA COM 对象将在首先调用 CoInitialize 的线程上处理。 现在报告对对象方法的所有调用都在我创建的线程和对象的主窗口(COM 组件有一个表格)据报道也属于它! (使用 WinSpy++ 进行测试)。

    但仍有一个问题(和一个问题)......为什么它会这样? 无论我在互联网上搜索的哪个地方,我都会看到答案告诉 STA COM 组件将在创建它的线程上完全执行(前提是之前调用过带有 COINIT_APARTMENTTHREADED 的 CoInitialize 或 CoInitializeEx),无论如何。为什么我之前在另一个线程上调用 CoInitialize 有什么关系……我认为 Microsoft 这样做是很愚蠢的 :),而且它可能会损害我的应用程序的未来行为,正如我之前所说的那样。

    编辑:正确答案是 Frost 发布的答案。再次感谢。

    【讨论】:

    • 这是不正确的,明确标记为STA 的服务器将在调用CoCreateInstance 的同一STA 线程中创建。问题在于标记为使用任何特定线程模型的服务器...请参阅下面的答案:-)
    【解决方案3】:

    线程并行运行,这就是它们的目的。如果您希望一个对象等待另一个线程上的某些操作完成,则需要在两个线程之间进行同步。事件对象将为您服务。

    【讨论】:

    • 对不起,我想你误解了我的问题。我做了一些编辑来澄清它。谢谢。
    【解决方案4】:

    创建COM类时需要选择Free Threading作为COM类的Threading Model。对于 C++ ATL,当您选择 New -> COM class(或类似的东西)时,这是向导中的一个选项。在 .NET 语言中,我认为这被指定为类中的属性。

    顺便说一句,您不需要在 CoCreateInstance 之后调用 QueryInterface(除非您需要多个接口指针)。只需将所需接口的 GUID 作为第四个参数传递给 CoCreateInstance。

    【讨论】:

    • 感谢有关查询接口的提示。问题是,我无法更改 VB6 组件的线程模型,因为它已在其他应用程序中使用,并且可能会损坏它的行为。我的问题是为什么我不能让它在创建它并调用 CoInitialize 的线程上运行,即使它的线程模型是 STA?谢谢。
    • ...有趣...这里有别的东西。我认为在创建新线程之前可能会在主线程上调用 CoInitialize,并且它可能与组件选择的线程有关,因此在创建新线程之前,在主线程上我调用了 CoUninitialize。现在对 COM 对象的调用在我想要的线程上执行,我在其中创建了对象。为什么会这样?
    • 我正在开发的上下文是Firebreath,它是一个跨浏览器跨平台插件开发的框架。生成了一些代码,这就是为什么在我实际创建新线程之前可能会调用 CoInitialize。
    • 我做了一些额外的测试,似乎哪个线程首先调用 CoInitialize 很重要。在主线程上,我在创建新线程之前调用 CoUninitialize。创建新线程后,我正在调用 CoInitialize 以在主线程上再次初始化 COM。不过,为了给新线程一些时间,在我这样做之前,我会显示一个消息框,我会在需要时确认它。同样在新线程中,我也在 IT'S CoInitialize 之前放置了一个 MessageBox。两者中的第一个消息框决定了 COM 对象将执行其代码的线程。检查编辑
    • 我不知道发生了什么:) ...但我有另一个想法,按照 BrendanMcK 所说的做,通过将 COINIT_MULTITHREADED 作为第二个参数传递给 CoInitializeEx,使所有线程的线程模型成为多线程的.虽然我不确定如果 object 的 线程模型是单线程模型还是单元模型,这是否会有所帮助。我只是觉得 objectsthreads 都有线程模型,这很令人困惑。
    【解决方案5】:

    啊,我想我现在可能知道问题所在了:听起来您正在创建的 VB6 COM 对象被注册为单线程,而不是单元线程;这意味着对象是在您的应用程序第一个调用 CoInitialize() 的线程上创建的。

    这解释了您所看到的行为:如果您首先让主线程 CoInitialize(),就 COM 而言,它成为“主线程”,因此 CoCreate 最终会在其上创建对象,即使它是在不同的线程上共同创建。 (这仅适用于单线程对象。)

    但是当你首先让你的另一个线程 CoInitialize() 时,它是 COM 的“主线程”,所以对象会在你想要的地方创建。

    您能否将 VB 对象的线程模型更改为单元而不是单个?这将使它能够在调用 CoCreate() 的线程上创建。

    问题是,我无法更改 VB6 组件的线程模型,因为它已在其他应用程序中使用,并且可能会损坏它的行为。

    ...看起来这对你不起作用。我想你可以检查一下当前的线程模型是什么,如果你能确认它是单一的,那么你就会解释它为什么会这样,这可能会帮助你使用它。

    --

    那么为什么 COM 会这样呢? - A:遗留兼容性问题。单线程模型是 Windows 最初拥有线程之前的保留,当时每个进程只有一个线程,并且代码不必对进程内对象之间的同步做出任何假设。为了保持这种错觉并允许在多线程环境中使用假设单线程 COM 编写的对象,COM 引入了“单”模型,也称为“传统 STA”。更多详情on this page,向下滚动或搜索“Legacy STA”了解详情。 COM 基本上将所有这些“单个”对象放在同一个 [STA] 线程上 - 并使用恰好是第一个调用 CoInitialize 的线程。当您在另一个线程上再次 CoUninit 和 CoInit 时,您实际上是在重新启动 COM;所以现在第二个线程是新的“调用 CoInit 的第一个线程”,这就是 COM 最终使用那个的原因...

    (Legacy STA 是个老问题,实际上很难找到任何细节;几乎所有其他文章都提到了公寓、免费和两种选择;但很少有关于“单身”的细节。)

    【讨论】:

    • 大声笑,你删除了你的第一个答案并写了另一个很长的答案,只是为了重复我的发现。对不起,我不能将其标记为答案,这不公平 :)
    • 是的,第一个答案没有意义,因为我不明白你的问题 - 有一个与问题不匹配的答案没有意义:) 至少现在你知道为什么你正在看到你所看到的;这就是 COM 的“单线程”模型的工作原理,并且实际上应该以这种方式运行。但是对你修复它没有多大帮助。 (如果您无法更改现有对象,您可以克隆项目并创建一个新副本或一个单元线程的新构建吗?)
    • 这没有多大意义。其实STAApartment是一回事。
    • @Rost;很多 COM 似乎没有多大意义:-P STA 和 Apartment 不太一样; STA 指的是单元的类型——它更多是线程的属性——而单元指定了组件的线程要求。 Apartment 指定了一个对象可以存在于任何 STA中,但是Single更加严格,COM保证所有Single对象都在同一个 STA中。
    • @BrendanMcK 但是文档中没有“STA”一词的这种用法,因此您的文字令人困惑。 MS指的是您将“STA”称为“未指定的线程模型”。 STA 用作 Apartment 线程模型的同义词。见MSDN
    猜你喜欢
    • 1970-01-01
    • 2011-03-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-11
    • 2013-04-10
    • 1970-01-01
    • 2013-10-15
    相关资源
    最近更新 更多