4

这是由另一个问题引发的。

具体来说,我有一个进程中的 COM 类,它在CLSID 注册表中定义为具有ThreadingModel.Both

CoCreateInstance我们的进程通过( CoCreateInstanceEx,如果这对进程内 dll 服务器也很重要)激活此对象

给定文档中列出的线程模型Both和规则:

Threading model of server | Apartment server is run in
------------------------------------------------------
Both                      | Same apartment as client

并鉴于汉斯在另一个答案中写道:

...当需要在不同的线程上进行客户端调用时,就会发生编组。...当 comClass 元素中指定的 ThreadingModel 需要它时,可能会发生。换句话说,当 COM 对象在一个线程上创建但在另一个线程上调用时,服务器不是线程安全的。

我的初步结论是,这样的对象永远不需要对其接口的调用进行隐式编组,因为该对象将始终与其客户生活在同一个公寓中。

这是正确的,即使客户端进程作为STA运行?

4

4 回答 4

6

是的,可能有编组。

如果您的 COM 类的客户端在 STA 中运行,并且您尝试从另一个单元调用您的类,则它必须编组到创建它的单元。

COM 术语可能真的很混乱。在这种情况下,当您提及“客户端”时,您实际上指的是一个线程,而不是整个应用程序(正如它所暗示的那样)。

Both只是意味着服务器的线程模型符合实例化它的客户端。也就是说,当您实例化您的类时,它采用创建它的线程的线程模型。由于您在 STA 中实例化服务器,因此您的服务器将使用 STA,这意味着它只能在创建它的线程上调用;如果另一个线程试图调用它,它将编组到创建它的线程。

于 2013-11-07T22:05:02.997 回答
3

我忍不住发布这个,虽然它不是问题的直接答案。

有一篇来自 COM 黄金时代的精彩 MSKB 文章:INFO:OLE 线程模型的描述和工作原理。仍然在那里,并拥有所有相关信息。关键是,如果你遵守规则,你不应该担心是否有编组。只需将您的对象注册为ThreadingModel=Both,将 Free-Threaded Marshaler 与 聚合起来CoCreateFreeThreadedMarshaler,就可以完成了。如果需要,COM 将以最好的方式进行编组。根据客户端的公寓模型,客户端代码可能会收到指向您接口的直接指针,如果它也遵循规则的话。

当您的接口的方法被调用时,您可能收到的任何“外星人”接口在调用范围内都是有效的,因为您保持在同一个线程上。如果您不需要存储它,那才是最重要的。

但是,如果您确实需要缓存“外星人”接口,那么正确的做法是使用CoMarshalInterThreadInterfaceInStream/来存储它CoGetInterfaceAndReleaseStream

要存储它:

  • 进入临界区;
  • 调用CoMarshalInterThreadInterfaceInStream并将IStream指针存储在成员字段中;
  • 离开临界区;

检索它

  • 进入临界区;
  • 调用CoGetInterfaceAndReleaseStream检索接口
  • 再次调用CoMarshalInterThreadInterfaceInStream并存储它以IStream备将来使用
  • 离开临界区;
  • 使用当前调用范围内的接口

要释放它:

  • 当您不再需要保留它时,只需释放存储的IStream(在关键部分内)。

如果“外星人”对象也是自由线程的,并且事情发生在同一个进程中,那么您可能会在CoGetInterfaceAndReleaseStream. 但是,您不应该做任何假设,并且您真的不需要知道您处理的对象是原始对象还是 COM 编组器代理。

这可以通过使用CoMarshalInterfacew/ MSHLFLAGS_TABLESTRONG/ CoUnmarshalInterface/ IStream::Seek(0, 0)/CoReleaseMarshalData而不是CoGetInterfaceAndReleaseStream/来稍微优化CoGetInterfaceAndReleaseStream,以便在不释放流的情况下根据需要多次解组相同的接口。

更复杂(并且可能更有效)的缓存场景是可能的,涉及线程本地存储。但是,我认为这将是矫枉过正。我没有做任何计时,但我认为CoMarshalInterThreadInterfaceInStream/的开销CoGetInterfaceAndReleaseStream真的很低。

也就是说,如果您需要维护存储任何可能需要线程关联的资源或对象的状态,除了上述 COM 接口,您不应将您的对象标记为ThreadingModel=Both或聚合 FTM。

于 2013-11-09T05:27:16.957 回答
2

是的,编组仍然是可能的。几个例子:

  1. 该对象是从 MTA 线程实例化的,因此被放置到 MTA 单元中,然后其指针被传递到任何 STA 线程,并且该 STA 线程调用该对象的方法。在这种情况下,STA 线程只能通过编组访问对象。

  2. 该对象是从 STA 线程实例化的,因此被放置到属于该线程的 STA 单元中,然后其指针被传递到另一个 STA 线程或 MTA 线程。在这两种情况下,这些线程只能通过编组访问对象。

实际上,您只能在以下两种情况下期望没有编组:

  1. 该对象从 MTA 线程实例化,然后仅由 MTA 线程访问 - 实例化对象的线程和同一进程的所有其他 MTA 线程。
  2. 该对象是从 STA 线程实例化的,然后仅由该线程访问

在所有其他情况下,编组将发挥作用。

于 2013-11-08T07:07:53.207 回答
1

ThreadingModel = Both 只是意味着 COM 服务器作者可以保证他的代码是线程安全的,但不能保证他未编写的其他代码将以线程安全的方式调用。执行此类外部代码的最常见情况是通过回调,连接点是最常见的示例(通常在客户端运行时中称为“事件”)。

因此,如果服务器实例是在 STA 中创建的,那么客户端程序员将期望事件在同一线程上运行。即使触发此类事件的服务器方法是从另一个线程调用的。这需要编组该调用。

于 2013-11-09T16:59:43.140 回答