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

How to call C# delegate to pass array of strings from native C simplest way?

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

我的问题是:这可以用更简单的方法来完成吗?例如。 :

编辑:我尝试使用委托签名:

[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);

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

我想出了远非最优的解决方案:

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 数组,其中的值被复制并且总是传递给委托。这不是我想要的。请问有没有什么好的解决办法...

这是正确的做法,我将给出一个完整的例子,以便它可以重现。

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:

A pointer to the first element of a C-style array. When marshaling from managed to unmanaged code, the length of the array is determined by the length of the managed array. When marshaling from unmanaged to managed code, the length of the array is determined from the MarshalAsAttribute.SizeConst and MarshalAsAttribute.SizeParamIndex fields, optionally followed by the unmanaged type of the elements within the array when it is necessary to differentiate among string types.

UnmanagedType.LPStr:

A single byte, null-terminated ANSI character string. You can use this member on the System.String and System.Text.StringBuilder data types.

MarshalAs.SizeParamIndex:

Indicates the zero-based parameter that contains the count of array elements, similar to size_is in COM.