PInvoke编组返回字符串数组C#

时间:2018-06-15 13:53:36

标签: c# c string delegates pinvoke

我有这个问题,我需要使用反向pinvoke(从C代码到C#代码调用的委托),但是这个委托返回一个字符串数组,我将从C代码中读取。这是函数指针的typdef。

org.junit

这是与代理相关的

typedef wchar_t** (__cdecl TestPassingString)();

在C代码中调用此函数指针,例如:

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate string[] TestPassingString();

但我有一个AccessViolationException,我该如何解决这个问题?

感谢。

1 个答案:

答案 0 :(得分:0)

C面代码:

typedef wchar_t** (__cdecl *TestPassingString)();

// The variable containing the callback to C#
TestPassingString callbackTestPassingString;

// The allocator that can be used by C#. The C code must
// use the corresponding deallocator (free in this case) to
// free the memory.
__declspec(dllexport) void* Allocate(size_t bytes)
{
    return malloc(bytes);
}

// Method used to set the callback. You can change it however you want
__declspec(dllexport) void SetCallbackTestPassingString(TestPassingString callback)
{
    callbackTestPassingString = callback;
}

// Test method that uses the callback. Note the free-ing!
__declspec(dllexport) void Test()
{
    wchar_t** array = callbackTestPassingString();

    const int arraySize = 5;

    for (int i = 0; i < arraySize; i++)
    {
        wprintf(L"%s\n", array[i]);
    }

    // Here we deallocate the array and its elements
    // Here I'm using free()... But the only important thing
    // is that you use the corresponding free-er of Allocate()
    for (int i = 0; i < arraySize; i++)
    {
        free(array[i]);
    }

    free(array);
}

C# - 代码:

[DllImport("CPlusPlusSide.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr Allocate(IntPtr bytes);

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate IntPtr TestPassingString();

[DllImport("CPlusPlusSide.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void SetCallbackTestPassingString(TestPassingString callback);

[DllImport("CPlusPlusSide.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void Test();

// Simple method to copy a string to a IntPtr, including the terminating \0
public static void CopyStringUnicodeToPtr(string str, IntPtr ptr)
{
    for (int j = 0; j < str.Length; j++)
    {
        Marshal.WriteInt16(ptr, j * sizeof(char), str[j]);
    }

    // \0 terminator
    Marshal.WriteInt16(ptr, str.Length * sizeof(char), 0);
}

public IntPtr MyTestPassingString()
{
    string[] strings = new[]
    {
        "Foo",
        "Bar",
        "FooBar",
        "Baz",
        "FooBarBaz",
    };

    IntPtr ptr = Allocate((IntPtr)(strings.Length * IntPtr.Size));

    for (int i = 0; i < strings.Length; i++)
    {
        string str = strings[i];

        // The +1 is for the terminating \0
        IntPtr ptr2 = Allocate((IntPtr)((str.Length + 1) * sizeof(char)));
        Marshal.WriteIntPtr(ptr, i * IntPtr.Size, ptr2);

        CopyStringUnicodeToPtr(str, ptr2);
    }

    return ptr;
}

然后C#-side初始化委托:

private TestPassingString TestPassingStringDelegate;

public void InitializeDelegate()
{
    TestPassingStringDelegate = MyTestPassingString;
    SetCallbackTestPassingString(TestPassingStringDelegate);
}

一些注意事项:

  • C-side我正在导出C分配器(Allocate方法),以便C#-side可以使用它来分配内存
  • C-side我正在导出一个SetCallbackTestPassingString方法,C#可以使用该方法将C委托设置为C#方法。
  • C#-side:非常重要:TestPassingStringDelegate字段的生命周期必须是>= C端可以调用C#委托的时间。如果TestPassingStringDelegate丢失(通常是因为包含TestPassingStringDelegate字段的对象被垃圾收集),那么如果C端调用委托,则会收到错误。解决问题的最简单方法是使TestPassingStringDelegate成为static字段。