7

遇到乍一看像 MT 问题的东西,但我试图详细了解 COM+ 使用的 STA 模型。

实际上,我有一个用 VB6 编写的遗留 COM+ 组件,它调用用 C++ 编写的本机(即非 COM)Win32 DLL。

遇到一些间歇性(并且无法在测试中重现)问题,我添加了一些调试代码以找出发生了什么,并发现当问题发生时,我在文件中交错了日志消息 - 所以它暗示 DLL被两个线程同时调用。

现在日志记录到基于 _getpid() 和 GetCurrentThreadId() 的每个线程文件,所以看起来当 C++ DLL 中的代码被调用时,它会同时在同一个线程上被调用两次。我对 STA 的理解表明,这可能是这种情况,因为 COM 将对象的各个实例编组到单个线程上,并随意挂起和恢复执行。

不幸的是,我不确定从这里去哪里。我读到我应该在 DllMain() 中调用 CoInitialiseEx() 来告诉 COM 这是一个 STA DLL,但其他地方说这仅对 COM DLL 有效,并且对本机 DLL 没有任何影响。唯一的其他选择是将 DLL 的某些部分包装为关键部分以序列化访问(承担对下巴造成的任何性能损失)。

我可以尝试重做 DLL,但没有共享状态或全局变量 - 一切都在局部变量中,所以理论上每个调用都应该有自己的堆栈,但我想知道 STA 模型是否基本上对此有一些奇怪的影响并且只是在与另一个调用相同的入口点重新进入已加载的 DLL。不幸的是,我不知道如何证明或测试这个理论。

问题基本上是:

  1. 当 STA COM+ 组件调用本机 DLL 时,STA 模型中没有任何内容可以防止活动“线程”被挂起并将控制权传递给 DLL 调用中间的另一个“线程”?
  2. CoInitialiseEx() 是否是解决此问题的正确方法?
  3. 如果 (1) 或 (2) 都不是“好的”假设,那会发生什么?
4

2 回答 2

1

在单元线程 COM 服务器中,COM 类的每个实例都保证由单个线程访问。这意味着实例是线程安全的。但是,可以使用不同的线程同时创建许多实例。现在,就 COM 服务器而言,您的本地 DLL 不必做任何特别的事情。想想每个可执行文件都使用的 kernel32.dll - 它在被 COM 服务器使用时会初始化 COM 吗?

从 DLL 的角度来看,您必须确保您是线程安全的,因为不同的实例可以同时调用您。在这种情况下,STA 不会保护您。既然您说您没有使用任何全局变量,我只能假设问题出在其他地方,并且恰好显示在似乎指向 COM 东西的情况下。你确定你没有一些普通的旧 C++ 内存问题吗?

于 2009-06-29T11:04:22.200 回答
0

我怀疑您的问题是在被调用的 DLL 深处的某个地方,它对另一个单元(同一进程中的另一个线程,或 MTA 中的对象,或完全是另一个进程)进行了出站 COM 调用。COM 允许等待出站调用结果的 STA 线程接收另一个入站调用,递归处理它。它仅用于相同对象之间的持续对话 - 即 A 调用 B,B 回调 A,A 再次调用 B - 但如果您已将接口指针分发给多个客户端或客户端,则可以接收来自其他对象的调用已将接口指针共享给另一个客户端。通常,将指向单线程对象的接口指针分发给多个客户端线程是一个坏主意,因为它们只需要相互等待。每个线程创建一个工作对象。

COM 不能在任何线程上随意挂起和恢复执行 - STA 线程上的新传入调用只能通过消息泵到达。当“阻塞”等待响应时,STA 线程实际上是在泵送消息,检查消息过滤器(请参阅 IMessageFilter)是否应该处理消息。但是,消息处理程序不得进行新的传出调用 - 如果这样做,COM 将返回 RPC_E_CANTCALLOUT_INEXTERNALCALL 错误(“在消息过滤器内调用是非法的。”)

如果您在本机 DLL 中的任何位置都有消息泵 (GetMessage/DispatchMessage),则可能会出现类似问题。我在接口过程中遇到了 VB 的 DoEvents 问题。

CoInitializeEx 只能由线程的创建者调用,因为只有他们知道他们的消息泵送行为将是什么。如果您尝试在 DllMain 中调用它很可能会失败,因为正在调用您的本机 DLL 以响应 COM 调用,因此调用者必须最终已经在线程上调用 CoInitializeEx 才能进行调用。在 DLL_THREAD_ATTACH 通知中执行此操作,对于新创建的线程,可能会在表面上工作,但如果 COM 在应该泵送时阻塞,则会导致程序发生故障,反之亦然。

于 2010-03-02T19:05:01.217 回答