我有一个有趣的问题,我在其他任何地方都看不到(至少不是这个特定问题)。
此问题是COM,VB6和.NET的组合,使它们的播放效果很好。
这是我所拥有的:
是的,我知道首先要做的就是冒险,因为VB6对多线程应用程序一开始并不十分友好,但是不幸的是,这是我目前所坚持的。
我已经修复了许多导致代码死锁的问题(例如,确保实际上创建了COM对象并从单独的STA线程调用了这些对象,确保在线程退出之前明确释放了COM对象,以防止垃圾回收器和COM Interop代码之间发生了死锁等),但是有一种死锁场景我似乎无法解决。
在WinDbg的一些帮助下,我能够弄清楚正在发生什么,但是我不确定如何(或是否)可以解决这种特殊的僵局。
发生了什么事
如果一个请求处理程序线程正在退出,而另一个请求处理程序线程正在同时启动,则可能会由于VB6运行时初始化和终止例程的工作方式而发生死锁。
在以下情况下会发生死锁:
MSVBVM60!CThreadPool::InitRuntime
),该函数获取互斥量并进入关键部分以完成其部分工作。此时,即将调用 LoadLibrary 来将 oleaut32.dll 加载到进程中,同时保持此互斥量。因此,现在它持有此内部VB6运行时互斥对象并等待OS加载程序锁定。 DLL_THREAD_DETECH
消息,这又调用了一种方法来终止线程上的VB6运行时(MSVBVM60!CThreadPool::TerminateRuntime
)。现在,该线程尝试获取与要初始化的其他线程已经具有的互斥体。 经典的僵局。线程A具有L1并需要L2,但是线程B具有L2并需要L1。
问题(如果您到目前为止已经关注了我)是我无法控制VB6运行时在其内部线程初始化和拆卸例程中的工作。
从理论上讲,如果我可以强制VB6运行时初始化代码在OS加载程序锁中运行,则可以避免死锁,因为我可以肯定VB6运行时所持有的互斥锁仅在初始化和终止例程中使用。
要求
我尝试过的解决方案无效
将ActiveX DLL转换为ActiveX EXE
首先,我尝试了一种显而易见的解决方案,并创建了一个ActiveX EXE(进程外服务器)来处理COM调用。最初,我对其进行编译,以便为每个传入请求创建一个新的ActiveX EXE(进程),并且还使用每个对象的线程编译选项进行了尝试(创建了一个进程实例,并在一个新的实例上创建了每个对象。 ActiveX EXE中的线程)。
这解决了有关VB6运行时的死锁问题,因为VB6运行时从不加载到适当的.NET代码中。但是,这导致了另一个问题:如果并发请求进入服务,则ActiveX EXE会随机失败,并出现
RPC_E_SERVERFAULT
错误。我认为这是因为COM编码和/或VB6运行时无法在ActiveX EXE中处理并发对象创建/销毁或并发方法调用。强制VB6代码在OS加载程序锁中运行
接下来,我切换回对COM类使用ActiveX DLL。为了强制VB6运行时在OS加载程序锁内运行其线程初始化代码,我创建了 native (Win32)C++ DLL,其中的代码用于处理 DllMain 中的
DLL_THREAD_ATTACH
。 DLL_THREAD_ATTACH
代码调用 CoInitialize ,然后实例化虚拟VB6类以强制加载VB6运行时,并强制运行时初始化例程在线程上运行。Windows服务启动时,我使用 LoadLibrary 将此C++ DLL加载到内存中,以便该服务创建的任何线程都将执行该DLL的
DLL_THREAD_ATTACH
代码。问题是该代码在服务创建的每个线程上运行,包括.NET垃圾收集器线程和异步网络代码使用的线程池线程,它们运行不佳(这似乎导致线程永远无法运行)。正常启动,我想在GC和线程池线程上初始化COM通常是一个非常糟糕的主意)。
是否可以解决这些问题?
所以,我的问题是,有什么办法可以解决原始的死锁问题?
我唯一想到的另一件事是创建自己的锁对象,并在.NET
lock
块中包含实例化COM对象的代码,但是我(我知道)无法在该对象周围放置相同的锁。 (操作系统的)线程退出代码。这个问题是否有更明显的解决方案,还是我在这里很不幸?
最佳答案
只要所有模块都在一个进程中工作,就可以通过用包装器替换某些系统调用来挂接Windows API。然后,您可以将调用包装在单个关键部分中,以避免死锁。
有几个库和示例可以实现此目的,该技术通常称为绕行:
http://www.codeproject.com/Articles/30140/API-Hooking-with-MS-Detours
http://research.microsoft.com/en-us/projects/detours/
当然,包装程序的实现应使用 native 代码(最好是C++)完成。 .NET绕道也可以用于MessageBox等高级API函数,但是如果尝试在.NET中重新实现LoadLibrary API调用,则可能会遇到循环依赖性问题,因为.NET运行时在执行过程中内部使用LoadLibrary函数,并且经常这样做。
因此,解决方案在我看来是这样的:一个单独的.DLL模块在您的应用程序开始时加载。该模块通过使用自己的包装器修补几个VB和Windows API调用来解决死锁问题。所有包装器都做一件事:将调用包装在关键部分中,并调用原始API函数来完成实际工作。
关于c# - 有什么方法可以解决由第三方库引起的OS加载程序锁死锁?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/9528100/