如何调用C#委托从本机C最简单的方式传递字符串数组?

时间:2016-03-14 11:35:18

标签: c# interop pinvoke marshalling native

我知道这可以通过在C中进行mallocing,将malloced指针传递给参数类型为IntPtr的委托,将编组传递给string [],然后使用托管代码中单独的导出C函数释放malloced内存来完成。

我的问题是:这可以更简单的方式吗?例如。 :

  • C#delegate参数的类型为string []?
  • 没有单独的免费功能来从托管代码调用

编辑:我尝试过代理签名:

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
MyManagedDelegate(string[] values, int valueCount)

和C中的功能:

void NativeCallDelegate(char *pStringValues[], int nValues)
{
    if (gSetStringValuesCB)
        gSetStringValuesCB(pStringValues, nValues);
}

在C中调用它:

char *Values[]= {"One", "Two", "Three"};
NativeCallDelegate(Values, 3);

这导致我只能使用数组中的第一个字符串。

2 个答案:

答案 0 :(得分:7)

以下是如何正确完成,我将给出一个完整的示例,以便它可以重现。

C面

typedef void(*setStringValuesCB_t)(char *pStringValues[], int nValues);

static setStringValuesCB_t gSetStringValuesCB;

void NativeCallDelegate(char *pStringValues[], int nValues)
{
    if (gSetStringValuesCB)
        gSetStringValuesCB(pStringValues, nValues);
}

__declspec(dllexport) void NativeLibCall(setStringValuesCB_t callback)
{
    gSetStringValuesCB = callback;
    char *Values[] = { "One", "Two", "Three" };
    NativeCallDelegate(Values, 3);
}

这里没什么好看的,我只是添加了必要的胶水代码,剩下的就是其余的。

C#侧

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void MyManagedDelegate(
    [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr, SizeParamIndex = 1)]
    string[] values,
    int valueCount);

[DllImport("NativeTemp", CallingConvention = CallingConvention.Cdecl)]
public static extern void NativeLibCall(MyManagedDelegate callback);

public static void Main()
{
    NativeLibCall(PrintReceivedData);
}

public static void PrintReceivedData(string[] values, int valueCount)
{
    foreach (var item in values)
        Console.WriteLine(item);
}

诀窍在于编组部分:

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void MyManagedDelegate(
    [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr, SizeParamIndex = 1)]
    string[] values,
    int valueCount);

MarshalAs属性告诉.NET编组程序:

  • UnmanagedType.LPArray你得到一个数组......
  • ArraySubType = UnmanagedType.LPStr ...标准C字符串......
  • SizeParamIndex = 1 ...并且该数组的大小由第二个参数指定。

在调用C#方法之前,.NET编组器会复制C字符串并将其转换为System.String实例。因此,如果您需要将动态生成的字符串传递给C#,您malloc,然后调用gSetStringValuesCB,然后您可以立即free,所有这些都来自C代码,如.NET有自己的数据副本。

您可以参考the docs

  

UnmanagedType.LPArray

     
    

指向C样式数组的第一个元素的指针。从托管代码编组到非托管代码时,阵列的长度由托管阵列的长度决定。 从非托管代码封送到托管代码时,数组的长度由MarshalAsAttribute.SizeConstMarshalAsAttribute.SizeParamIndex字段确定,可选地后跟数组中元素的非托管类型有必要区分字符串类型。

  
     

UnmanagedType.LPStr

     
    

单字节,以空值终止的ANSI字符串。您可以在System.StringSystem.Text.StringBuilder数据类型上使用此成员。

  
     

MarshalAs.SizeParamIndex

     
    

表示包含数组元素计数的从零开始的参数,类似于COM中的size_is

  

答案 1 :(得分:-1)

我提出的远非最佳解决方案:

public delegate void MyManagedDelegate([MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr, SizeConst=10)]string[] values, int valueCount);

如果这样调用,则无效:

char *Values[]= {"One", "Two", "Three"};
NativeCallDelegate(Values, 3);

我可以使用固定大小为10的数组来复制值,并始终传递给委托。这不是我想要的。我想知道这是否有好的解决方案...