cammm
cammm

Reputation: 188

Return contents of a std::wstring from C++ into C#

I have an unmanaged C++ DLL that I have wrapped with a simple C interface so I can call PInvoke on it from C#. Here is an example method in the C wrapper:

const wchar_t* getMyString()
{
    // Assume that someWideString is a std::wstring that will remain
    // in memory for the life of the incoming calls.
    return someWideString.c_str();
}

Here is my C# DLLImport setup.

[DllImport( "my.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl )]
private static extern string GetMyString();

However the string is not correctly marshalled, often screwing up the first character or sometimes way off showing a bunch of chinese characters instead. I have logged output from the implementation on the C side to confirm that the std::wstring is correctly formed.

I have also tried changing the DLLImport to return an IntPtr and convert with a wrapped method using Marshal.PtrToStringUni and it has the same result.

[DllImport( "my.dll", CallingConvention = CallingConvention.Cdecl )]
private static extern IntPtr GetMyString();
public string GetMyStringMarshal()
{
    return Marshal.PtrToStringUni( GetMyString() );
}

Any ideas?

Update with Answer

So as mentioned below, this is not really an issue with my bindings but the lifetime of my wchar_t*. My written assumption was wrong, someWideString was in fact being copied during my calls to the rest of the application. Therefore it existed only on the stack and was being let go before my C# code could finish marshalling it.

The correct solution is to either pass a pointer in to my method as described by shf301, or make sure my wchar_t* reference does not get moved / reallocated / destroyed before my C# interface has time to copy it.

Returning the std::wstring down to my C layer as a "const &std::wstring" means my call to c_str() will return a reference that won't be immediately dealloc'd outside the scope of my C method.

The calling C# code then needs to use Marshal.PtrToStringUni() to copy data from the reference into a managed string.

Upvotes: 3

Views: 8799

Answers (2)

shf301
shf301

Reputation: 31404

You are going to have to rewrite your getMyString function for the reasons mentioned in Hans Passant's answer.

You need to have the C# code pass a buffer in to your C++ code. That way the your code (ok, the CLR Marshaller) controls the lifetime of the buffer and you don't get into any undefined behavior.

Below is an implementation:

C++

void getMyString(wchar_t *str, int len)
{
    wcscpy_s(str, len, someWideString.c_str());
}

C#

[DllImport( "my.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode )]
private static extern void GetMyString(StringBuffer str, int len);
public string GetMyStringMarshal()
{
    StringBuffer buffer = new StringBuffer(255);
    GetMyString(buffer, buffer.Capacity);
    return buffer.ToString();
}

Upvotes: 6

hamstergene
hamstergene

Reputation: 24439

You need to specify MarshalAs attribute for the return value:

    [DllImport( "my.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
    [return : MarshalAs(UnmanagedType.LPWStr)]
    private static extern string GetMyString();

Make sure the function is indeed cdecl and that the wstring object is not destroyed when the function returns.

Upvotes: 5

Related Questions