FreeThreadedDOMDocument、Neutral Apartments 和 Free-Threaded Marshaler

FreeThreadedDOMDocument, Neutral Apartments and Free-Threaded Marshaler

如 MSDN 所述:

If you are writing a single threaded application (or a multi-threaded application where only one thread accesses a DOM at one time), use the rental threaded model (Msxml2.DOMDocument.3.0 or Msxml2.DOMDocument.6.0). If you are writing an application where multiple threads access will simultaneously access a DOM, use the free threaded model (Msxml2.FreeThreadedDOMDocument.3.0 or Msxml2.FreeThreadedDOMDocument.6.0).

FreeThreadedDOMDocument、中性单元和自由线程封送拆收器之间有什么联系吗?我查看了 OleView,发现 FreeThreadedDOMDocument 线程模型是两者。据我了解,自由线程封送拆收器支持中性公寓对象。这是否意味着 FreeThreadedDOMDocument 不使用自由线程封送拆收器并且它被称为自由线程有点令人困惑?

COM 类 标记为 Free、Both 或 Neutral 的实现有什么区别?据我了解,它们都必须是线程安全的,为什么会有区别? Neutral 应该支持自由线程编组器是否正确?

这里有多个问题。

长话短说:

中性对象:

  • 比 STA 和 MTA 对象
  • 产生的进程中封送少一点
  • 避免线程切换
  • 接口指针自动编组
  • 只要有中性物体存在,中性公寓就会存在
  • 必须准备好 run under any kind of thread,根据线程类型使用 COM 实用程序函数等待或选择要使用的 Win32 等待函数

空闲线程对象:

  • 几乎没有进程中编组
  • 避免线程切换
  • 接口指针不会自动封送
  • 终身与激活公寓绑定
  • 必须准备好 run under any kind of thread,根据线程类型使用 COM 实用程序函数等待或选择要使用的 Win32 等待函数

Is there any connection between FreeThreadedDOMDocument, neutral apartments and free-threaded marshaler?

TL;DR:FreeThreadedDOMDocument 的线程模型是 "Both",因此它与激活(创建)的单元相关联。它聚合了自由线程封送拆收器,因此它是一个自由线程对象。

FreeThreadedDOMDocument 是一个 COM class,其对象聚合了 free threaded marshaler. What this marshaler does is to provide a raw pointer whenever marshaling in-process (i.e. IMarshal::MarshalInterface with dwDestContext set to MSHCTX_INPROC

我将使用自由线程对象的定义作为聚合自由线程封送拆收器的对象。

自由线程对象的线程模型在Windows2000之前应指定为"Neutral"或"Both",这样它就可以在任何线程中创建和使用,避免上下文切换。

如果将其线程模型指定为 "Both",则对象的生命周期与创建它的单元相关联。例如,如果 STA 线程终止,则在该单元内创建的所有空闲线程对象要么被销毁,要么不再有效。

As far as I understand neutral apartment objects are supported with a free-threaded marshaler.

不,中性对象的代理比其他进程中代理轻一点,因为它只设置一个 COM 上下文,但它从不进行完全编组,它避免了线程切换。

Does it mean that FreeThreadedDOMDocument doesn't use a free-threaded marshaler and it is called a bit confusing as free-threaded?

不,FreeThreadedDOMDocument 确实使用了免费的线程封送拆收器。

从历史上看,there were already free threaded objects 在 Microsoft 为它们提供自己的支持之前(由于流行,并且可能是因为那里的大多数免费线程编组器都不稳定),而中性单元仅出现在 Windows 2000.

因此,FreeThreadedDOMDocument 的实例是自由线程的,因为它们聚合了自由线程封送拆收器,并且每个实例的生命周期都与创建它的单元相关联。通常,影响很小,但例如一个 STA 线程的线程池,这种效果会被更频繁地观察到,因为 STA 随着拥有线程的终止(正常或回收资源)和创建而来来去去。例如,classic ASP 默认使用 STA 线程。

PS: 我在another answer中提到了以下主题,但我认为内容有点不同,因为问题也不同。

这是当前的线程模型值:

  • None: 使用主STA
  • "Apartment":使用任何 STA,即如果当前公寓是 STA 或 NA over STA,则使用当前 STA,否则使用 host STA(更多信息这个稍后)
  • "Free": 使用 MTA
  • "Both": 使用当前公寓
  • "Neutral": 使用 NA

对于任何不存在的公寓,如果需要,COM 会创建它。

这里有几个特点:

  • 要使用主STA,一定不要提任何线程模型,而是更明智的,比如"Main"
  • 除了 "Neutral" 之外的所有名称现在都没有意义:
    • "Apartment"感觉像现在的公寓,但又不是
    • "Free" 感觉像是 自由线程对象 ,但它不是
    • "Both"让你觉得公寓类型只有2种,其实有3种:STA、MTA和NA
      • 实际上,自 Windows 8 以来,出现了 ASTA,这是为 GUI 创建的 STA 的一个变体,它在呼出呼叫期间丢弃呼入呼叫 that are not related,从而避免了重入的重要来源错误
      • 您可以使用 message filter
      • 使常规 STA 的行为像这样

主STA是第一个创建的STA。它仅对具有未指定线程模型的 classes 很重要。

可以有多个STA,但最多有一个MTA和一个NA。

虽然有一个活动的 MTA,但任何未为 COM 初始化的线程如果不调用 CoInitializeEx(NULL, COINIT_MULTITHREADED) 则隐含在 MTA 中,但它也根本不会影响 MTA 的生命周期,这意味着MTA 可能会在线程使用它时被销毁。由于这几乎没有记录并且几乎不可靠,因此您不应该依赖它。

隐式创建的公寓称为 host STA and host MTA。你无法控制它们(除非在那个公寓里用 CoUninitialize 作弊;注意:实际上不要这样做)。事实上,如果您在 STA 外部或 NA 运行 外部通过 STA 激活 "Apartment" 对象,它将在主机 STA 中激活。为了进一步混淆,如果主机 STA 是第一个要初始化的 STA,这也可能是主 STA。

支持宿主单元的所有 COM 线程都是后台线程,因此它们不会阻止您的应用程序退出。

除了在激活中性对象时创建 NA 之外,您无法控制 NA。您不能直接输入它,但您可以使用 运行 在中性公寓上下文中回调的方法创建自己的中性对象。此回调可以是一个自由线程对象。

What is the implementation difference between COM classes that marked as Free, Both or Neutral?

单元声明为 "Free" 的

COM classes 将产生属于 MTA 的对象。这些对象可能假设它们 运行 所在的线程不必发送 window 消息。本质上,它们可能会阻塞。

必须准备自由线程对象和中性对象run under any apartment. For free threaded objects, it should be obvious why: it bypasses any context marshaling, so methods execute in any thread. For neutral objects, there's the distinction of which kind of apartment was active (through CoGetApartmentType)。

在任何一种情况下,您都应该使用 COM 的实用函数,例如 CoWaitForMultipleHandles instead of WaitForMultipleHandles[Ex], which blocks and is unacceptable in STAs, or MsgWaitForMultipleHandles[Ex],它访问 window 消息队列,可能会隐式创建它,并且通常是 unacceptable 在 MTA 中。

您可以自己检查单元类型并选择使用适当的 Win32 等待函数或使用轮询策略,该策略在 STA 中等待并超时发送消息,以防您等待的不是句柄或者如果您需要特定的等待逻辑。

自由线程对象和中性对象之间最显着的区别是其他 COM 对象的编组。

在使用中性对象时,传入和传出接口指针会自动编组。例如,您可以将传入接口指针存储在字段中。

在使用自由线程对象时,传入和传出接口指针根本没有编组,这意味着您要么获得指向同一单元中对象的原始指针,要么获得代理其他公寓中的物品。这些代理也绑定到当前的公寓。

例如,传入的原始指针意味着您正在获取属于当前公寓的对象,因此如果您打算存储对该对象的引用,则必须对其进行编组。

传入代理意味着您正在获取另一个公寓中对象的代理,但此代理与当前公寓相关联。您也不能存储此代理。具体来说,尽管有标准 proxy/stubs' 单元验证,但 STA 代理可能具有线程关联性。您也必须对其进行编组。但别担心,编组代理不会堆叠编组;当您再次解组时,您将获得对象的代理,而不是代理的代理。

当自由线程对象必须存储接口指针时,它必须始终通过手动编组来实现,而当它必须从该接口指针调用方法时,它必须通过手动解组来实现。

通常,Global Interface Table(GIT;另一个误导性名称,它实际上是一个进程中 table)用于此目的。

As far as I understand they all must be thread-safe, why is the difference?

关于线程安全,没有区别。

但正如我在上一个问题中解释的那样,在存储接口指针时存在巨大差异,在对象激活和生命周期方面存在细微差异。

Is it correct that Neutral should support a free-threaded marshaler?

自由线程封送拆收器有效地忽略了套间,因此方法有责任正确地同步 and/or 锁。因此,两个单元都必须支持自由线程封送拆收器,它是必须支持每个单元的自由线程对象。

可以在具有任何线程模型的对象中聚合自由线程封送拆收器,包括 "Neutral"。

如果您发现中性单元封送拆收器设置的上下文在某种程度上是瓶颈,那么您可以考虑使用免费的线程封送拆收器,代价是手动封送存储的接口指针。如果没有,就用中性公寓吧。