这是由 COM 自动完成的。由于您的 COM 对象是单线程的,因此 COM需要为对象提供一个合适的主目录,以确保它以线程安全的方式使用。由于您的主线程不够友好,无法提供此类保证,COM 会自动创建另一个线程并在该线程上创建对象。这个线程也会自动抽水,你无需做任何帮助。您可以看到它正在调试器中创建。启用非托管调试并查看 Debug + Windows + Threads 窗口。当您跳过新呼叫时,您会看到线程被添加。
很好很容易,但它确实有一些后果。首先,COM 组件需要提供代理/存根实现。帮助代码知道如何序列化方法调用的参数,以便可以在另一个线程上进行真正的方法调用。通常会提供,但并非总是如此。如果 E_NOINTERFACE 异常丢失,您将很难诊断它。有时 TYPE_E_LIBNOTREGISTERED,一个常见的安装问题。
最重要的是,对 COM 组件的每次调用都会被封送。这很慢,编组调用通常比直接调用本身花费很少时间的方法慢 10,000 倍左右。就像一个属性 getter 调用。当然,这确实会使您的程序陷入困境。
STA 线程避免了这种情况,因此是使用单线程组件的推荐方法。是的,STA 线程需要泵送消息循环。.NET 程序中的 Application.Run()。它是消息循环,它将 COM 中从一个线程到另一个线程的调用编组。请注意,这并不一定意味着您必须有一个消息循环。如果没有调用需要编组,或者换句话说,如果您从同一个线程对组件进行所有调用,则不需要消息循环。这通常很容易保证,尤其是在控制台模式应用程序中。当然,如果您自己创建线程,则不会。
一个更令人讨厌的细节:单线程 COM 组件有时假定它是在泵的线程上创建的。并且将使用 PostMessage() 本身,通常在它在内部使用工作线程并需要在 STA 线程上引发事件时。当您不抽水时,这当然不会再正常工作了。您通常通过注意到没有引发事件来诊断这一点。此类组件的常见示例是 WebBrowser。它在内部大量使用线程,但在创建它的线程上引发事件。如果您不抽水,您将永远不会收到 DocumentCompleted 事件。
因此,将 [STAThread] 放在 Main() 方法上可能足以获得快乐的快速代码,即使没有调用 Application.Run()。请记住后果,看到方法调用死锁或未引发事件是需要抽水的迹象。