Search code examples
c#c++pinvoke

How to return std::wstring from C++ to C# using PInvoke without writing stringbuffer conversion


i have a class in Native C++ for which i need to create a wrapper in c# using PInvoke. I'm facing issues while returning std::wstring to string. Is there any Marshal methods or attributes provided by dotnet? I don't want to manually write char or byte conversions as like other answers.

Node.h

#ifndef MYAPI // defined export in preprocessor
#define MYAPI __declspec(dllimport)
#endif


class MYAPI Node
{
public:
    Node();

    ~Node();

    inline std::wstring GetName() { return mName; }

    inline void SetName(const wchar_t* name) { mName = std::wstring(name); }

private:
    std::wstring mName;

};

//c extern methods for PInvoke

#ifdef __cplusplus
extern "C" {
#endif

    MYAPI const wchar_t* GetNodeName(NativeCore::Node* obj);

 #ifdef __cplusplus
}
#endif

in my Node.cpp

MYAPI const wchar_t * GetNodeName(NativeCore::Node* obj)
{
    if (obj != NULL)
        return obj->GetName().c_str();
    return NULL;
}

In my c# wrapper

UnManagedWrapper.cs

class UnMangedWrapper {

    [DllImport("NativeCore.dll", CallingConvention = CallingConvention.Cdecl)]
    [return: MarshalAs(UnmanagedType.LPWStr)]
    public static extern string GetNodeName(IntPtr ptr);

}

it is not converting the return type const wchar_t* to string when using the above conversion. Is there any other method to convert std::wstring to string in this Pinvoke?

I don't to manually convert it by getting the string buffer as like below.

[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();
}

Solution

  • In your Node.cpp file, use SysAllocString from oleauto.h (or include Windows.h) to allocate the string for you like this:

    MYAPI BSTR GetNodeName(NativeCore::Node* obj)
    {
        if (obj != NULL)
            return SysAllocString(obj->GetName().c_str());
        return NULL;
    }
    

    Then adjust your native method wrapper to use UnmanagedType.BStr instead of UnmanagedType.LPWStr:

    class UnMangedWrapper 
    {
        [DllImport("NativeCore.dll", CallingConvention = CallingConvention.Cdecl)]
        [return: MarshalAs(UnmanagedType.BStr)]
        public static extern string GetNodeName(IntPtr ptr);
    }
    

    Using BSTR has the advantage that you don't have to call into the unmanaged code twice (once for the buffer length and another time for the actual string content) and the marshaller can automatically take care of deallocating the unmanaged string.