如何使用 PInvoke 将 std::wstring 从 C++ 返回到 C# 而无需编写字符串缓冲区转换 [重复]

Posted

技术标签:

【中文标题】如何使用 PInvoke 将 std::wstring 从 C++ 返回到 C# 而无需编写字符串缓冲区转换 [重复]【英文标题】:How to return std::wstring from C++ to C# using PInvoke without writing stringbuffer conversion [duplicate] 【发布时间】:2019-02-26 11:08:28 【问题描述】:

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

节点.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 用于 PInvoke 的外部方法

#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

类 UnMangedWrapper

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


在使用上述转换时,它不会将返回类型 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();

【问题讨论】:

你有很深的问题。一旦函数返回,您返回的字符串就无效了。此外,pinvoke 假定它需要被销毁并调用 CoTaskMemFree。如果你用 CoTaskMemAlloc 分配一个字符数组并返回它,这一切都会起作用。 此外,还有其他解决问题的方法,这里有数百篇关于这个主题的帖子。接下来尝试一些研究。 @DavidHeffernan 我在任何地方都找不到相关答案。我想知道是否有任何简单的 MarshalAs 属性可以直接转换它。 您可以在搜索引擎中输入这两个函数名称并阅读文档。这对我来说更容易。 @S.FrankRicharrd 如答案中所述,使用如下所示的BSTRs 不会导致内存问题,因为Marshal 类将为您处理BSTR 的释放。 (请注意,如果您自己打电话给Marshal.PointerToStringBSTR,情况并非如此。) 【参考方案1】:

在您的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 的优点是您不必调用非托管代码两次(一次用于缓冲区长度,另一次用于实际字符串内容),并且编组器可以自动处理解除分配非托管字符串.

【讨论】:

【参考方案2】:

返回值始终是 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

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

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

【讨论】:

这不好。函数返回后返回的 C 字符串无效。 @DavidHeffernan - 仅当您假定 C 字符串是在函数本身中分配的,而问题似乎并非如此。 好的,你是对的,对不起 不过,值得在答案中添加一些文本以明确要求,以防万一当 DLL 函数使用本地字符串而不是实例拥有的字符串时有人尝试使用这种方法.

以上是关于如何使用 PInvoke 将 std::wstring 从 C++ 返回到 C# 而无需编写字符串缓冲区转换 [重复]的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 pinvoke 将二进制数据缓冲区从 C 传递到 C#

如何使用 PInvoke 将 std::wstring 从 C++ 返回到 C# 而无需编写字符串缓冲区转换 [重复]

pinvoke:不确定如何使用 dllimport 和导入的库

cmd.exe 在使用 CreateProcess 调用后立即关闭

调用外部 SetWindowsHookEx 和 GetModuleHandle 的 PInvoke 错误

如何制作一个 PInvoke 友好的原生 API?