-2

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();
}
S.Frank Richarrd
  • 488
  • 1
  • 3
  • 15
  • 1
    You have deep problems. The string you return is invalid as soon as the function returns. Further the pinvoke assumes it needs to be destroyed and calls CoTaskMemFree. It will all work if you allocate a character array with CoTaskMemAlloc and return that. – David Heffernan Feb 26 '19 at 12:17
  • 1
    Further, there are other ways to solve the problem, and hundreds of posts here on this topic. Try some research next. – David Heffernan Feb 26 '19 at 12:26
  • @DavidHeffernan I couldn't find relevant answer any where. I would like to know if there is any simple MarshalAs attribute to convert it directly. – S.Frank Richarrd Feb 28 '19 at 07:23
  • Search harder. No amount of MarshalAs helps you with the current design, but you can change the design. My initial comment stands regarding CoTaskMemFree. Do you understand that yet? – David Heffernan Feb 28 '19 at 07:39
  • @DavidHeffernan I couldn't understand your first comment. Can you please provide some links regarding that? Also, answer from 'Thomas Flinkow' below, seems to be working for me if i use BSTR. will that create any memory problem? – S.Frank Richarrd Feb 28 '19 at 07:48
  • Do you know what CoTaskMemAlloc and CoTaskMemFree are? – David Heffernan Feb 28 '19 at 07:54
  • Sorry. I don't know. Need to learn – S.Frank Richarrd Feb 28 '19 at 08:02
  • There's not much point in having the discussion if you haven't got the ground knowledge required to evaluate the options. – David Heffernan Feb 28 '19 at 08:05
  • U could share knowledge right?. I guess thats t reason people use ts portal – S.Frank Richarrd Feb 28 '19 at 08:08
  • 1
    You could just type those two function names into a search engine and read the docs. That's easier for me. – David Heffernan Feb 28 '19 at 08:12
  • @DavidHeffernan Thanks for the hint. i explored that and it taken me to new area to explore. https://limbioliong.wordpress.com/2011/06/16/returning-strings-from-a-c-api/ – S.Frank Richarrd Mar 01 '19 at 06:33
  • 1
    @S.FrankRicharrd as explained in the answer, using `BSTR`s as shown below does not lead to memory problems, since the `Marshal` class will handle deallocation of the `BSTR` for you. (Note that this is not the case if you were to call [`Marshal.PointerToStringBSTR`](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.marshal.ptrtostringbstr?view=netframework-4.7.2) yourself.) – Thomas Flinkow Mar 01 '19 at 06:57

2 Answers2

3

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.

Thomas Flinkow
  • 4,845
  • 5
  • 29
  • 65
0

The return value is always a special case with p/invoke methods. Because you don't use a CLR compatible string allocator (=COM) on the native side, you should define your method like this:

[DllImport("NativeCore.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr GetNodeName(IntPtr ptr);

and call it like this:

var ptr = GetNodeName(whatever);
var str = Marshal.PtrToStringUni(ptr); // unicode because you use wstring

Note this is only ok if the C string (or more generally whatever memory the returned pointer points to) is not deallocated on the native side when the called method returns. For example, if the wstring is defined locally somewhere in the method, it will be automatically deallocated on native call return (and a crash will most likely happen).

Or if you want to avoid that extra call on the .NET side, pass the string as an argument, and use the return value as an error code, as most Windows APIs do.

Simon Mourier
  • 132,049
  • 21
  • 248
  • 298