与许多人一样,我遇到了将C#数组传递给C ++中的COM客户端的问题。我的猜测是,这是由于CLR COM Interop生成了错误的签名。严肃的说法,是的,但这是交易。
C#COM服务器使用名为GenTechLib.tlb的类型库。
C ++ COM客户端期望来自COM服务器的通知具有如下签名的回调函数。这很明显。 C ++客户端需要获得多个元素,以及许多地址,值和时间戳。但是,C ++客户端只获取正确的第一个值,剩下的就是垃圾,而且由于没有正确地从C#传递数组。
HRESULT Notify(unsigned long dwCount, BSTR * psAddresses, VARIANT pvarValues, DATE * pdtTimestamps )
{
// let's say that received dwCount is 2, so therefore, below should work
// this gets proper value
BSTR addr0 = psAddresses[0];
// this however gets pure garbage
// result is the same for values and timestamps
BSTR addr1 = psAddresses[1];
}
在C#端,上面函数的签名,从引用的TLB文件生成时是:
void Notify(uint dwCount, ref string psAddresses, ref object pvarValues, ref DateTime pdtTimestamps);
所以像这样的代码,虽然它编译并运行良好但不会产生预期的结果。
// all of below are arrays of 2, properly new'ed and filled
string[] arrAddresses;
string[] arrValues;
object obValues = arrValues; // function signature requires 'object'
DateTime[] arrDates;
notificationListener.Notify(2, ref arrAddresses[0], ref obValues, ref arrDates[0]);
不幸的是,我无法修改Notify()的签名。它是从TLB文件生成的。这是使用编译器生成的Interop.GenTech.dll文件的ILSpy进行反编译输出的。
[ComImport]
[Guid("F97D74BE-AC1C-404E-B463-142916096A24")]
[InterfaceType(1)]
public interface INotificationListener
{
[MethodImpl(MethodImplOptions.InternalCall)]
void Notify([In] uint dwCount, [In] [MarshalAs(UnmanagedType.BStr)] ref string psAddresses, [In] [MarshalAs(UnmanagedType.Struct)] ref object pvarValues, [In] ref DateTime pdtTimestamps);
}
注意C ++和C#签名之间的区别。我们可以告诉C#COM interop生成签名以传回数组(ref string []而不是ref string)?是否可以以接受数组的方式覆盖此签名?或者首先是从dll生成TLB的问题?
生成TLB(GenTechLib.tlb)的COM接口dll是在古代VS2003中用C ++创建的dll。不幸的是,我没有它的来源。
从C#语言的角度来看,可以做些什么?如果输出参数类型为:
ref string strArray
它是否可以在内存中填充字符串顺序,并通过COM互操作傻瓜C ++ COM客户端,以便它接收BSTR *形式的字符串数组? 也许使用不安全的块?但即使如此,我也有一些疑问。
如果由我决定,整个代码库将使用C ++,但它就是它。
有什么想法?提示? 感谢。
更新
我停止使用.tlb类型库的引用,而是手动更正某些函数的签名,最明显的是Notify()
查看下面这个函数签名的怪异(不再是ref关键字),在C ++ COM客户端代码中正常工作,现在接收到数组的所有元素。我的C ++ COM客户端代码只是模拟我认为真实系统的样子,但我99%确定不使用安全数组。
[ComImport]
[Guid("F97D74BE-AC1C-404E-B463-142916096A24")]
[InterfaceType(1)]
public interface INotificationListener
{
[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);
}
我还需要手动修正另一个功能的签名,但这是主要阻止程序。在C#项目中引用类型库似乎并不总是意味着您将获得正确的函数签名。也许Visual Studio类型库代码生成器只能到目前为止。