通过 P/Invoke 从 C# 将可写字符串数组传递给 C++

Pass writable string array to C++ from C# via P/Invoke

我正在尝试将可写(预分配)字符串数组从 C# 传递到 C++ dll。它因 "Access violation writing location".

而失败

C++:

int StringArrayTest(size_t numberOfStrings, char **valueOut, size_t maxStringLength) {
    for (unsigned int i = 0; i < numberOfStrings; i++) {
        auto str = std::to_string(i); //Create simple string
        strncpy(valueOut[i], str.c_str(), maxStringLength); //Copy to output
    } 
    return 0;
}

C#:

[DllImport("MyDLL", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] 
static extern int StringArrayTest(ulong arraySize, [MarshalAs(UnmanagedType.LPArray)]StringBuilder[] valuesOut, ulong maxStringLength);

public string[] GetTestStrings(ulong arraySize, ulong maxStringLength) {
    var stringBuilder = new StringBuilder[(int)arraySize];
    for (var i = 0; i < (int)arraySize; i++) {
        stringBuilder[i] = new StringBuilder((int)maxStringLength);
    }
    var result = StringArrayTest(arraySize, stringBuilder, maxStringLength);
    var returnValues = new string[arraySize];
    for (var i = 0; i < (int)arraySize; i++) {
        returnValues[i] = result.ToString();
    }
    return returnValues;
}

请注意,使用单个字符串(char * 签名并传递单个 StringBuilder)可以按预期工作。

C++

extern "C" {
  __declspec(dllexport) int StringArrayTest(size_t numberOfStrings, char*** ptrValueOut, size_t maxStringLength)
  {
    char** valueOut = *ptrValueOut;
    for (unsigned int i = 0; i < numberOfStrings; i++) {
      auto str = std::to_string(i); //Create simple string
      strncpy(valueOut[i], str.c_str(), maxStringLength); //Copy to output
    }
    return 0;
  }
}

将声明从 ... char** valueOut, ... 更改为 ... char*** ptrValueOut, ...

C#:

[DllImport("StringArrayTest", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
static extern int StringArrayTest(int arraySize, ref IntPtr valuesOut, int maxStringLength);

public static string[] GetTestStrings(int arraySize, int maxStringLength)
{
    var buffer = Marshal.AllocCoTaskMem(IntPtr.Size * arraySize);
    var strings = new IntPtr[arraySize];
    for (int i = 0; i < strings.Length; i++)
        strings[i] = Marshal.AllocCoTaskMem(maxStringLength);
    Marshal.Copy(strings, 0, buffer, strings.Length);            

    StringArrayTest(arraySize, ref buffer, maxStringLength);

    var returnValues = new string[arraySize];
    for (var i = 0; i < arraySize; i++)
    {
        returnValues[i] = Marshal.PtrToStringAnsi(strings[i]);
        Marshal.FreeCoTaskMem(strings[i]);
    }
    Marshal.FreeCoTaskMem(buffer);
    return returnValues;
}

该代码旨在说明该过程,可能并非没有错误。内存通过 Marshal 方法分配。指向内存的指针随后作为对 StringArrayTest 函数的引用传递。