以下是正确的操作方法,我会提供一个完整的示例以保证可复制性。
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
...并且数组的大小由第二个参数指定。
.NET编组器在调用C#方法之前,将C字符串复制并转换为System.String
实例。因此,如果您需要将动态生成的字符串传递给C#,则可以在C代码中malloc
它们,然后调用gSetStringValuesCB
,最后可以立即free
它们,因为.NET有自己的数据副本。
您可以参考文档:
UnmanagedType.LPArray
:
指向C风格数组的第一个元素的指针。在从托管代码到非托管代码进行编组时,数组的长度由托管数组的长度确定。当从非托管代码到托管代码进行编组时,数组的长度是从MarshalAsAttribute.SizeConst
和MarshalAsAttribute.SizeParamIndex
字段中确定的,在必要时跟随数组中元素的非托管类型以区分字符串类型。
UnmanagedType.LPStr
:
单字节、以 null 结尾的 ANSI 字符串。可以在System.String
和System.Text.StringBuilder
数据类型上使用此成员。
MarshalAs.SizeParamIndex
:
指示包含数组元素计数的基于零的参数,类似于COM中的size_is
。
BSTR
数组,也许编组程序能够为你完成这项工作。 - David Heffernan