【问题标题】:Legacy VB6 COM+ DLL calling into native Win32 DLL -- threading issues with STA?旧版 VB6 COM+ DLL 调用本机 Win32 DLL - STA 的线程问题?
【发布时间】:2009-06-29 10:04:07
【问题描述】:

遇到乍一看像 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) 都不是“好的”假设,发生了什么?

【问题讨论】:

  • >> 它在同一个线程上同时被调用两次。

标签: multithreading com vb6 apartments


【解决方案1】:

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

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

【讨论】:

  • 该问题似乎仅在负载时出现在生产系统上。 DLL 的代码没有全局变量,只使用局部变量。以线程和进程 ID 为基础唯一命名的输出日志文件包含交错输出,表明它在中间状态被同一线程调用两次。在所有其他环境中,代码执行完全正确。
  • 1.您实际遇到什么样的问题?崩溃、挂断、行为不端? 2. 您是直接还是间接地创建线程或发送消息? 3. 发现问题后立即尝试创建小型转储(或完整转储),堆栈跟踪可能有助于在您的开发环境中轻松识别问题。
  • Re - “普通的旧 C++ 内存问题”:是的,最终是这样,但不是在 malloc() 类型的领域,而是将 char 数组定义为静态。 Raymond Chen 在这里写了博客blogs.msdn.com/oldnewthing/archive/2004/03/08/85901.aspx,我已经在其他几个地方以及网站上找到了确认。重构代码以去除静态似乎解决了它。
【解决方案2】:

我怀疑您的问题是在被调用 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 在应该泵送时阻塞,反之亦然,会导致程序发生故障。

【讨论】:

    猜你喜欢
    • 2018-09-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多