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

cha*_*r m 4 c# pinvoke interop native marshalling

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

我的问题是:这可以做得更简单吗?例如:

  • C#delegate参数的类型为string []?
  • 没有单独的自由函数来从托管代码调用

编辑:我试过代理签名:

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
MyManagedDelegate(string[] values, int valueCount)
Run Code Online (Sandbox Code Playgroud)

和C中的功能:

void NativeCallDelegate(char *pStringValues[], int nValues)
{
    if (gSetStringValuesCB)
        gSetStringValuesCB(pStringValues, nValues);
}
Run Code Online (Sandbox Code Playgroud)

在C中调用它:

char *Values[]= {"One", "Two", "Three"};
NativeCallDelegate(Values, 3);
Run Code Online (Sandbox Code Playgroud)

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

Luc*_*ski 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);
}
Run Code Online (Sandbox Code Playgroud)

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

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);
}
Run Code Online (Sandbox Code Playgroud)

诀窍在于编组部分:

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

MarshalAs属性告诉.NET封送程序如下:

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

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


你可以参考文档:

UnmanagedType.LPArray:

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

UnmanagedType.LPStr:

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

MarshalAs.SizeParamIndex:

指示包含数组元素计数的从零开始的参数,类似于size_isCOM.