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

C# COM server interop - System.InvalidCastException

本文关键字:System InvalidCastException 互操作 COM 服务器      更新时间:2023-10-16

我在 C# 中创建了一个 COM 服务器,它为特定设备提供服务,并由C++ COM 客户端(客户应用程序,没有可用的源代码)使用。 客户应用程序需要定期更新,为此使用称为Notify()的功能。该函数在非托管 COM 客户端代码中实现。

当从创建此通知侦听器对象的同一线程中调用Notify()时,一切都很好。但这只能发生一次C++因为 COM 客户端最初只请求一次数据。

即使它只要求一次数据,它似乎也不能容忍超过一秒钟左右的响应。而且我的设备反应有点慢,尤其是在网络上,超过一秒钟才能获得某种读数。好的,我明白了,它想要快速响应。

因此,不仅要实现定期更新,还要减轻主线程等待数据的负担,我创建了一个单独的线程,该线程每分钟探测一次设备,然后通过调用Notify()将结果报告回非托管代码。

当从不同的线程调用此 Notify() 时,我得到了一个非常讨厌System.InvalidCastException。异常是从 CLR 内部深处引发的。

这是在生产系统上。在我自己的开发系统上,我有自己的C++ COM客户端模型,此异常在调试会话期间被ContextSwitchDeadlock屏蔽。我可以关闭这个,但不会改变Notify()在交叉线程中表现不佳的事实。事实上,根据日志,函数永远不会返回。

下一步该怎么做?

从注册表中可以看出,COM 线程模型设置为"两者"。

我试图通过创建一个小的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,但这似乎没有任何效果。

您需要将对象的接口从主线程编送到工作线程,并在此接口上使用调用 Notify 方法。

在类中定义以下两个成员变量:

IStream*            m_StreamThis ;
<your interface>*   m_pMarshalledThis ;

在创建线程之前,请调用函数 CoMarshalInterThreadInterfaceInStream()。

CoMarshalInterThreadInterfaceInStream ( <your interface>, this, &m_StreamThis ) ;

第二个参数是指向 IUnknown 的指针。在你的类中,必须实现IUnknown,我认为指定"this"是可以的。

线程入口点不能是类成员,因此常见的做法是将对象指针作为参数传递给线程,然后使用它来调用成员函数。我假设你已经在这样做了。

在线程中,调用成员函数后,调用函数 CoGetInterfaceAndReleaseStream()。

CoGetInterfaceAndReleaseStream ( m_StreamThis, <your interface>, (void**)&m_pMarshalThis ) ;

当您需要调用 Notify 函数时,请使用封送指针调用它:

m_pMarshalThis->Notify() ;

这将导致在主线程上调用函数。

当线程终止时,当然应该释放指针m_pMarshalThis。您可能希望将其附加到智能指针类。

当然,您应该真正检查两个函数调用返回的 HRESULT,但这取决于您。

您当然可以在Stack Overflow或其他站点上找到有关如何使用这些函数的其他(和更好的)示例。