将可写StringBuilder数组从c#传递到c++

Pass writeable StringBuilder array to C++ from C#

本文关键字:c++ StringBuilder 数组      更新时间:2023-10-16

我有一些非托管的c++动态库和c# GUI应用程序,使用它。我需要传递已知大小的字符串数组到c++库,它填充它。也有最大字符串长度值。我试图传递StringBuilder数组与合适的大小本身,和合适的内部元素容量(又名缓冲区数组)。

// C++ library part    
#define MAX_STRING_LENGTH 200
const wchar_t* stringToPopulate = L"Hello, World";
MYDLL_API uint8_t __stdcall getSomeStrings(wchar_t** strings, uint64_t count) {
    for(auto i =0; i < count; ++i){
        // trivial string population
        wcsncpy(*(providers + i), stringToPopulate,
                min(wcslen(stringToPopulate), MAX_STRING_LENGTH ));     
    }
    return 0;
}

// C# application part
[DllImport("myDllName.dll", CallingConvention = CallingConvention.StdCall)]
static extern Byte getSomeStrings(
    [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] StringBuilder[] strings,
    UInt64 size
);
// calling
// create suitable string array
StringBuilder[] stringArrayToPass= new StringBuilder[desirableStringCount];         
for(ulong i =0; i < desirableStringCount; ++i) {
    stringArrayToPass[i] = new StringBuilder(maxStringLength);
}
res = getSomeStrings(stringArrayToPass, desirableStringCount); // exception

我在库API调用上有System.AccessViolationException异常。这个问题的原因是什么?

   [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)]

这是无稽之谈,大小是由参数1给出的,而不是0。只要完全删除它,pinvoke编组程序就不需要任何帮助来计算数组的长度。它已经知道,它使用数组的Length属性。

    wcsncpy(*(providers + i), stringToPopulate,
            min(wcslen(stringToPopulate), MAX_STRING_LENGTH ));

wcsncpy是一个evil, evilevil函数。您通常会收到来自Microsoft C/c++编译器的强烈反对,警告您这是不安全的。不要忽视这样的警告。它解决了"字符串不适合"的问题,通过做不可思议的事情,它不会零终止字符串。当发生这种情况时,调用者将总是出现故障,无可救药地在超出字符串末尾的地方出错。AVE(几乎)是不可避免的,如果它不读取垃圾。您使用它的方式将总是发生。

这可能是为了使函数安全,结果却适得其反。核心问题是调用者无法指定字符串缓冲区的大小。所以你应该重写,这样就可以指定这个大小。例如:

 MYDLL_API int __stdcall getOneString(int index, wchar_t* buffer, size_t bufferLength)

现在它很简单而且总是安全的,客户端代码只是重复调用它,增加index参数。它不需要猜测需要传递的数组有多大。你的C代码不需要猜测缓冲区有多大。如果索引超出范围,则返回错误代码,以便调用者知道停止调用。另一个,如果字符串不适合,客户端可以恢复。