C# Marshalling a C++ struct with wchar_t* member o

2019-09-19 09:59发布

问题:

I have declared a struct as follows:

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

and the corresponding managed representation

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

As well as this function:

// 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;
    }
}

which is called by the FillMultipleStructs method, that takes multiple TestStructs and initializes them in the C++ code.

// 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;
}

Calling the code works like this:

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

Now, the question is: sometimes, the code works, however, sometimes I get an a heap has been corrupted error. I do not understand where that comes from nor why it does work occasionally and sometimes it does not.

I guess it has to do with the marshalling of the struct's string member, so, if anyone can tell me where my error is or if anyone can point me in the right direction or show me the proper way to do that, I would gladly appreciate that.

回答1:

For anyone coming across this question struggling with the same problem, to summarize what pstrjds already said in a comment:

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

which effectively means changing the C# struct's definition from

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

to

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

and instead of using ::CoTaskMemAlloc to allocate a string in C++, ::SysAllocString needs to be used.

I did not have to change the signature of the C++ struct, since BSTR (in my case) was in the end a typedef for wchar_t*.