【问题标题】:Pass writable string array to C++ from C# via P/Invoke通过 P/Invoke 从 C# 将可写字符串数组传递给 C++
【发布时间】:2020-02-27 21:28:50
【问题描述】:

我正在尝试将可写(预分配)字符串数组从 C# 传递到 C++ dll。它因“访问冲突写入位置”而失败。

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)按预期工作。

【问题讨论】:

  • 我认为编组器不会为您编组所有内容。您将需要找到一种替代方法
  • 如果你想改变原始数组,你必须传递一个指向数组char*** valueOut的指针。调用者可能需要传递一个引用 (ref string[] valuesOut)。此外,我不认为 ulong 映射到 size_t 使用 uint

标签: c# c++ pinvoke


【解决方案1】:

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 函数的引用传递。

【讨论】:

  • 谢谢!一个小修复,您可以将第一行更改为 var buffer = Marshal.AllocCoTaskMem(IntPtr.Size * arraySize); 并将字符串数组更改为 long[arraySize](以及将下一个 AllocCoTaskMem 的结果转换为 long)以使其在 32 位和 64 位上工作。
  • 愚蠢的我!无需将IntPtr 转换为int,因为Marshal.Copy 有一个方法重载,它采用IntPtr[]。 @TylerLewis 不过,您并不完全正确。因为sizeof(long) 即使在 32 位版本上也始终为 8。但更新后的解决方案并不重要。
猜你喜欢
  • 1970-01-01
  • 2020-11-14
  • 1970-01-01
  • 1970-01-01
  • 2015-06-28
  • 1970-01-01
  • 2011-07-08
  • 2010-12-15
  • 1970-01-01
相关资源
最近更新 更多