通过互操作将字符串数组从 C# 传递到 C++
Posted
技术标签:
【中文标题】通过互操作将字符串数组从 C# 传递到 C++【英文标题】:Passing string array from C# to C++ through interop 【发布时间】:2014-05-28 03:07:15 【问题描述】:我有一个用 C++ 编写的 COM 组件,我想通过互操作在我的 C# 应用程序中调用它的接口。
我要调用的接口是这样定义的:
C#互操作接口定义:
void ICertainInterface.Func(int cnt, ref string colors)
C++ 定义:
ICertainInterface : IUnknown
virtual HRESULT Func(long cnt, BSTR * colors) = 0;
我很清楚,接口应该是我的应用程序中具有特定长度的 BSTR 数组。第二个参数 BSTR * colors 假设代表我的字符串数组中的第一个字符串地址。
现在这是我用来从 C# 应用程序调用接口的代码:
ICertainInterface obj = GetInterface();
string[] strArray = new string[4];
strArray[0] = "aaa";
strArray[1] = "bbb";
strArray[2] = "ccc";
strArray[3] = "ddd";
obj.Func(4, ref strArray[0]);
运行应用程序后,出现“尝试读取或写入受保护内存”的错误。如果数组大小仅为 1,则不会发生这种情况。 在我看来,因为字符串数组是 C# 中的托管数据。所以内存分配是不可预测的,一旦我将 strArray[0] 作为对 C++ 接口的引用,该接口将把它作为数组的起始地址,并假设 add by 4 将获得下一个元素的地址,但它不能在 C# 内存分配中也是如此。
我从网上搜索了一些帖子,似乎大多数接口用户“ref string[]”作为指向 C# 数组的指针,但不像这个“ref string”,我只能引用数组中的第一个元素(我什至不知道这是否是正确的做法,因为在 C++ 中,第一个元素地址等同于数组地址。)
此外,我在调用相同接口的 excel VBA 代码中进行了相同的测试:
Dim msColor(4) As String
msColor(0) = "aaa"
msColor(1) = "bbb"
msColor(2) = "ccc"
msColor(3) = "ddd"
Dim obj as ICertainInterface
Set obj = GetInterface();
Call obj.Func(4, msColor(0))
此 VBA 代码完美运行,没有任何错误。
现在我完全不知道如何在我的 C# 应用程序中解决这个问题。有人可以指点我吗?非常感谢。
【问题讨论】:
【参考方案1】:方法签名不应该是
void ICertainInterface.Func(int cnt, ref string[] colors)
...而不是传递数组中的第一个元素,而是传递整个数组?
C# 中的数组的行为与 C/C++ 中的数组不同;您不能只传递对第一个元素的引用并期望能够使用指针算法访问其余元素。
编辑:你可能也不需要 ref
关键字。
【讨论】:
嗨布拉德利,签名是'ref string colors'而不是'ref string[] colors'..我也觉得奇怪,但它是(这是来自C#自动生成的meda文件)。正如我在我的帖子中提到的,它在 VBA 中工作,将 VBA 字符串数组的第一个元素传入..【参考方案2】:您可能需要编组为适当的类型才能在 CLR 和非 CLR 进程之间传递 给你正确的签名
所以如果你想传递一个字符串,那么
void ICertainInterface.Func(int cnt,[MarshalAs(UnmanagedType.BStr)] ref string colors)
或者如果它是一种数组,那么
void ICertainInterface.Func(int cnt,[MarshalAs(UnmanagedType.SafeArray, SafeArraySubType=VT_BSTR)] ref string[] colors)
我根据您的代码制作了这个解决方案。如果上述解决方案对您不起作用,那么您可以尝试在此处 http://msdn.microsoft.com/en-us/library/z6cfh6e6(v=vs.110).aspx 和字符串类型 http://msdn.microsoft.com/en-us/library/s9ts558h(v=vs.110).aspx 中找到适合您的应用程序的适当数组类型/子类型
使用 DllImport 属性示例
如果您尝试传递单个字符串,那么
[DllImport("yourLib.dll", EntryPoint = "Func")]
public static extern void ICertainInterface.Func(int cnt,[MarshalAs(UnmanagedType.BStr)] ref string colors)
如果它是一种数组,那么
[DllImport("yourLib.dll", EntryPoint = "Func")]
public static extern void ICertainInterface.Func(int cnt,[MarshalAs(UnmanagedType.SafeArray, SafeArraySubType=VT_BSTR)] ref string[] colors)
这可能会帮助你实现相同,DLL导入通常用于调用win api
【讨论】:
但是 C# 签名是自动生成的,我无法编辑定义。有没有办法修改签名? 如果您无法更改现有签名,您可能会使用带有 DLLImport 属性[DllImport("yourLib.dll", EntryPoint = "Func")]
的新方法签名,因此如果指定 dll,它将调用您的方法。
但这不是一个静态函数......它包括我需要从另一个接口获取引用并调用它的一堆接口的对象的定义。那个接口只是其中一个他们..
尝试传递字符串引用或指向字符串的指针而不是变量。或者您可以告诉我您实际上可以修改哪一部分代码?我猜只有 C# 调用代码。
是的,我只能修改我用来调用接口的代码。我尝试使用 IntPtr,但会抱怨说参数类型不匹配。这是 C# 的问题,类型太严格无法转换,接口签名指示第二个参数为“引用字符串”,我无法更改。跨度>
以上是关于通过互操作将字符串数组从 C# 传递到 C++的主要内容,如果未能解决你的问题,请参考以下文章
从 C++ 传递函数指针以由 C# 调用 - 函数参数包括宽字符字符串 (LPCWSTR)
使用 Dllimport 将一个非常大的字符串作为字节数组从 C++ 传递到 C#