长话短说:在一个与COM inproc-server(dll)一起工作的C#应用程序中,遇到“0x80010100:系统调用失败”异常,而在调试模式下也遇到ContextSwitchDeadlock异常。
现在更详细说明:
1)C#app初始化STA,创建一个COM对象(注册为“Apartment”);然后订阅它的连接点,并开始使用该对象。
2)在某个阶段,COM对象会生成许多事件,并将同一个公寓中创建的COM对象集合作为参数传递。
3)C#端的事件处理程序处理上面的集合,偶尔调用对象的一些方法。在某些阶段,后面的调用开始失败,但有上述例外。
在COM方面,公寓使用一个隐藏窗口,其winproc如下所示:
typedef std::function<void(void)> Functor;
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case AM_FUNCTOR:
{
Functor *f = reinterpret_cast<Functor *>(lParam);
(*f)();
delete f;
}
break;
case WM_CLOSE:
DestroyWindow(hwnd);
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
事件从COM服务器的其他部分发布到此窗口:
void post(const Functor &func)
{
Functor *f = new Functor(func);
PostMessage(hWind_, AM_FUNCTOR, 0, reinterpret_cast<LPARAM>(f));
}
这些事件是与实际参数绑定的标准ATL CP实现,它们归结为这样的事情:
pConnection->Invoke(id, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, ¶ms, &varResult, NULL, NULL);
在C#中,处理程序如下所示:
private void onEvent(IMyCollection objs)
{
int len = objs.Count; // usually 10000 - 25000
foreach (IMyObj obj in objs)
{
// some of the following calls fail with 0x80010100
int id = obj.id;
string name = obj.name;
// etc...
}
}
==================
那么,上述问题是否只是因为公寓的消息队列过载了它试图提供的事件?或者应该完全阻止消息循环导致这种行为?
让我们假设消息队列有2个连续事件,评估为“onEvent”调用。第一个输入C#托管代码,它尝试重新输入非托管代码,即同一个公寓。通常,这是允许的,我们做了很多。何时,在什么情况下会失败?
感谢。
答案 0 :(得分:2)
即使有多个公寓,这也应该有效:
和
<强>首先:强> 看起来某些物体与其他物体不在同一个公寓中。您确定在STA中创建了所有对象吗?
你所描述的是一个典型的死锁 - 两个独立的线程,每个线程都在等待另一个。这就是我期望在不同线程上使用C#和COM端进行操作的设计。
如果所有对象在同一个线程上,以及该线程上的隐藏窗口,那么你应该没问题,所以我认为你需要检查一下。 (显然,这包括由COM端创建并传递给C#端的任何其他对象。)
您可以尝试通过在调试器中按“pause”并检查每个线程中的代码来调试它(如果您看到RPCRT * .DLL这意味着您正在查看代理)。或者,您可以从C#和COM端以及WndProc中的各个关键点DebugPrint当前线程ID - 它们应该都是相同的。
其次:它应该使用多个线程,前提是只有一个线程生成工作项,而另一个除了主机响应调用的COM对象之外什么都不做(即不生成调用从计时器,网络流量,发布消息等),在这种情况下,可能是线程队列已满,COM无法回复呼叫。
您应该使用受关键部分保护的双端队列,而不是使用线程队列。
每个邮件队列的发布邮件数量限制为10,000。这个限制应该足够大。如果您的应用程序超出限制,则应重新设计,以避免消耗这么多系统资源。