传统的VB6 COM + DLL调用本机Win32 DLL - 与STA的线程问题?

时间:2009-06-29 10:04:07

标签: multithreading com vb6 apartments

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

2 个答案:

答案 0 :(得分:1)

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

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

答案 1 :(得分: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阻塞它应该泵送,则会导致程序出现故障,反之亦然。