如何使用 PInvoke return std::wstring 从 C++ 到 C# 而无需编写字符串缓冲区转换

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

我在本机 C++ 中有一个 class,我需要使用 PInvoke 在 C# 中为其创建一个包装器。我在 returning std::wstring 字符串时遇到问题。 dotnet 是否提供任何 Marshal 方法或属性?我不想像其他答案那样手动编写字符或字节转换。

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;

};

//PInvoke 的 c 外部方法

#ifdef __cplusplus
extern "C" {
#endif

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

 #ifdef __cplusplus
}
#endif

在我的 Node.cpp

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

在我的 C# 包装器中

UnManagedWrapper.cs

class UnMangedWrapper {

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

}

使用上述转换时,它不会将 return 类型的 const wchar_t* 转换为字符串。在这个 Pinvoke 中还有其他方法可以将 std::wstring 转换为字符串吗?

我不会像下面这样通过获取字符串缓冲区来手动转换它。

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

return 值始终是 p/invoke 方法的特例。因为您不在本机端使用 CLR 兼容的字符串分配器 (=COM),所以您应该这样定义您的方法:

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

并这样称呼它:

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

请注意,只有当调用方法 returns 时 C 字符串(或更一般地,returned 指针指向的任何内存)未在本机端释放时,这才可以。例如,如果 wstring 是在方法的某处本地定义的,它将在本机调用时自动释放 return(很可能会发生崩溃)。

或者,如果您想避免在 .NET 端进行额外调用,请将字符串作为参数传递,并使用 return 值作为错误代码,就像大多数 Windows API 所做的那样.

在您的 Node.cpp 文件中,使用 oleauto.h 中的 SysAllocString(或包含 Windows.h)为您分配字符串,如下所示:

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

然后调整您的本地方法包装器以使用 UnmanagedType.BStr 而不是 UnmanagedType.LPWStr:

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

使用 BSTR 的优点是您不必两次调用非托管代码(一次用于缓冲区长度,另一次用于实际字符串内容)并且编组器可以自动处理取消分配非托管字符串。