C# 编组具有 wchar_t* 成员的 C++ 结构偶尔会使堆损坏

C# Marshalling a C++ struct with wchar_t* member occasionally leaves the heap corrupted

我声明了一个 struct 如下:

// C++
struct TestStruct
{
    wchar_t* TestString;
};

和相应的托管表示

// C#
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct TestStruct
{
    [MarshalAs(UnmanagedType.LPWStr)]
    public string TestString;
}

还有这个功能:

// C++
__declspec(dllexport) void __stdcall FillMultipleStructs(TestStruct* testStructures, const short arrayLength)
{   
    for(int i = 0; i < arrayLength; i++)
    {
        const wchar_t stringToAllocate[] = L"foo";
        const unsigned long size = wcslen(stringToAllocate) * sizeof(wchar_t) + sizeof(wchar_t);
        wchar_t* allocatedString = static_cast<wchar_t*>(::CoTaskMemAlloc(size));
        wcscpy_s(allocatedString, size, stringToAllocate);

        (&testStructures[i])->testString = allocatedString;
    }
}

FillMultipleStructs 方法调用,它接受多个 TestStructs 并在 C++ 代码中初始化它们。

// C#
[DllImport("the path", CallingConvention = CallingConvention.StdCall, EntryPoint = "FillMultipleStructs", ExactSpelling = true, CharSet = CharSet.Unicode)]
[SuppressUnmanagedCodeSecurity]
private static extern void _FillMultipleStructs([In, Out] TestStruct[] structures, [MarshalAs(UnmanagedType.I2)] short arrayLength);

public static IEnumerable<TestStruct> FillMultipleStructs(IEnumerable<TestStruct> structures)
{
    TestStruct[] structuresArray = structures.ToArray();
    _FillMultipleStructs(structuresArray, (short) structuresArray.Length);
    return structuresArray;
}

调用代码如下:

FillMultipleStructs(
    new List<TestStruct>()
    {
        new TestStruct(),
        new TestStruct(),
        new TestStruct()                       
    });

现在,问题是:有时,代码可以工作,但是,有时我会收到 a heap has been corrupted 错误。我不明白这是从哪里来的,也不知道为什么它偶尔会起作用,有时却不起作用。

我想这与 struct 的字符串成员的编组有关,所以,如果有人能告诉我我的错误在哪里,或者是否有人能指出正确的方向或告诉我这样做的正确方法,我将很感激。

对于遇到同样问题而遇到这个问题的任何人,总结一下 pstrjds 已经在评论中说过的话:

marshal as a BSTR and then instead of the CoTaskMemAlloc call, create BSTR objects

这实际上意味着将 C# struct 的定义从

更改为
[MarshalAs(UnmanagedType.LPWStr)]
public string TestString;

[MarshalAs(UnmanagedType.BStr)]
public string TestString;

而不是使用 ::CoTaskMemAllocC++ 中分配字符串,需要使用 ::SysAllocString

我不必更改 C++ struct 的签名,因为 BSTR(在我的例子中)最终是 typedef for wchar_t*.