我在C#中创建了一个COM服务器,它服务于特定设备并被C ++ COM客户端(客户应用程序,没有可用源)使用。
客户应用程序需要定期更新,为此,使用了名为Notify()
的函数。该函数在非托管COM客户端代码中实现。
从创建此通知侦听器对象的同一线程中调用Notify()
时,一切正常。但这只会发生一次,因为C ++ COM客户端最初只需要一次数据。
即使它只询问一次数据,它似乎也不能容忍接受一秒钟左右的响应。而且我的设备反应迟钝,特别是在网络上,一秒钟以上才能获得某种读数。好吧,我明白了,它想要迅速回应。
因此,不仅要实现定期更新,还要减轻等待数据的主线程,我创建了一个单独的线程,每分钟探测一次设备,然后通过调用Notify()
将结果报告回非托管代码
当从另一个线程调用Notify()时,我得到一个非常讨厌的System.InvalidCastException
。这个例外是从CLR深处抛出的。
这是在生产系统上。在我自己的开发系统中,我有自己的C ++ COM客户端模拟,在调试会话期间,ContextSwitchDeadlock
会屏蔽此异常。我可以关掉这一个,但不会改变Notify()
不能很好地交叉线程的事实。事实上,根据日志,功能永远不会返回。
下一步做什么?
从我从注册表中可以看出,COM线程模型设置为'Both'。
我试图通过创建一个小的C ++ DLL来解决这个问题,它将充当Notify()的包装器。因此,不要使用以下方法直接调用非托管COM客户端代码:
[MethodImpl(MethodImplOptions.InternalCall)]
void Notify([In] uint dwCount, [In] [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.BStr, SizeParamIndex = 0)] string[] psAddresses, [In] [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.Struct, SizeParamIndex = 0)] object[] pvarValues, [In] [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.R8, SizeParamIndex = 0)] double[] pdtTimestamps);
我用这个函数创建了一个小dll:
extern "C" __declspec(dllexport)
void CppNotify(INotificationListener *pNotifier, unsigned long dwCount, BSTR *psAddresses, VARIANT *pvarValues, DATE *pdtTimestamps)
{
pNotifier->Notify(dwCount, psAddresses, pvarValues, pdtTimestamps);
}
使用此定义调用哪个:
[DllImport("CppNotifier.dll", CallingConvention = CallingConvention.Cdecl)]
extern public static void NotifyCpp([In] [MarshalAs(UnmanagedType.Interface)] INotificationListener pNotifier, uint dwCount, [In] [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.BStr, SizeParamIndex = 0)] string[] psAddresses, [In] [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.Struct, SizeParamIndex = 0)] object[] pvarValues, [In] [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.R8, SizeParamIndex = 0)] double[] pdtTimestamps);
我希望这会避免System.InvalidCastException,但还没有运气。
我们的C#COM服务器面向.NET 4.0,而系统上安装的.NET版本为4.7.1。这会引发这样的问题吗?
我还尝试使用RegAsm.exe重新注册C#dll,但这似乎没有任何影响。
答案 0 :(得分:0)
您需要编写从主线程到工作线程的对象接口,并在此接口上调用Notify方法。
在您的班级中定义以下两个成员变量:
IStream* m_StreamThis ;
<your interface>* m_pMarshalledThis ;
在创建线程之前,调用函数CoMarshalInterThreadInterfaceInStream()。
CoMarshalInterThreadInterfaceInStream ( <your interface>, this, &m_StreamThis ) ;
第二个参数是指向IUnknown的指针。在您的课程中,必须实现IUnknown,我认为可以指定&#34;这个&#34;。
线程入口点不能是类成员,因此通常的做法是将对象指针作为参数传递给线程,然后使用它来调用成员函数。我假设你已经这样做了。
在线程中,在调用成员函数之后,调用函数CoGetInterfaceAndReleaseStream()。
CoGetInterfaceAndReleaseStream ( m_StreamThis, <your interface>, (void**)&m_pMarshalThis ) ;
当您需要调用Notify函数时,请使用编组指针调用它:
m_pMarshalThis->Notify() ;
这将导致在主线程上调用该函数。
当你的线程终止时,你当然应该释放指针m_pMarshalThis。您可能希望将其附加到智能指针类。
当然,你应该检查两个函数调用返回的HRESULT,但这取决于你。
您当然可以找到有关如何在Stack Overflow或其他网站上使用这些功能的其他(以及更好的)示例。