C#COM服务器互操作 - System.InvalidCastException

时间:2018-03-18 11:44:02

标签: c# c++ com com-interop

我在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,但这似乎没有任何影响。

1 个答案:

答案 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或其他网站上使用这些功能的其他(以及更好的)示例。