4

我正在开发一个 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

在创建新线程之前,我通过在主线程上进行 CoUninitializing 实现了我想要的。但是为什么会这样呢?如果在我创建新线程之前在主线程上初始化了 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
}

非常感谢你,科内利乌

4

5 回答 5

6

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

引用 MSDN(InprocServer32 注册表项文档):

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

于 2012-09-19T10:26:17.573 回答
1

I have finally achieved what I wanted! Adding a CoUninitialize call in the main UI thread, before creating the new thread has solved it. This happens because STA COM objects will be handled on the thread that first calls CoInitialize. Now all the calls to the objects methods are reported to be executed on the thread I created and the main window of the object (the COM component has a Form) is reported to belong to it too! (used WinSpy++ to test that).

There is still a question (and a problem) though..why does it behave this way? Everywhere I search on the internet I see answers telling that a STA COM component will be fully executed on the thread it is created on (provided that CoInitialize or CoInitializeEx with COINIT_APARTMENTTHREADED had been called before), no matter what. Why does it matter if I called CoInitialize on another thread before..that's just plain stupid in my opinion for Microsoft to do so :), plus it might damage the future behaviour of my application, as I stated before.

EDIT: The correct answer is the one posted by Frost. Thank you again.

于 2012-09-19T10:09:50.107 回答
0

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

于 2012-09-19T01:19:36.910 回答
0

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

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

于 2012-09-19T06:19:26.100 回答
0

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

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

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

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

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

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

--

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

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

于 2012-09-19T09:50:44.507 回答