如何在 C# 中编组 wstring *?

Posted

技术标签:

【中文标题】如何在 C# 中编组 wstring *?【英文标题】:How do I marshal a wstring * in C#? 【发布时间】:2017-05-09 15:26:12 【问题描述】:

我在 C++ 中有一个函数 _GetFileList,我试图将字符串列表/数组返回给 C# 代码。我没有与wstring * 或任何其他类型结婚,这就是我找到的示例的方式,但我试图让它返回一个列表/数组,而不仅仅是一个字符串。如何编组它以在 C# 端有多个字符串?

C++ 代码:

extern "C" __declspec(dllexport) wstring * __cdecl _GetFileList(int &size)
    std::list<wstring> myList;
    //Code that populates the list correctly
    size = myList.size();
    wstring * arr = new wstring[size];
    for( int i=0;i<size;i++)
        arr[i] = myList.front();
        myList.pop_front();
    
    return arr;

我在 C# 中这样称呼它:

[DllImport(@"LinuxDirectory.Interface.dll")]
private static extern IntPtr _GetFileList(ref int size);

public bool GetFileList() 
    int size = 0;
    IntPtr ptr = _GetFileList( ref size );
    //WHAT DO I DO HERE TO GET THE LIST OF STRINGS INTO SOMETHING I CAN READ?

我尝试过使用 Marshal.PtrToStringUni、Marshal.PtrToStringAnsi 以及结构一,但似乎无法获得正确的语法。

【问题讨论】:

***.com/questions/7051097/… @pm100:很好的相关问题,但它不是重复的,因为这个问题实际上(不幸的是)在函数签名中有std::wstring,而不仅仅是在内部使用。 【参考方案1】:

你没有。 P/Invoke 只能处理与 C 兼容的函数签名,这意味着指针必须指向普通旧数据,而不是完整的 C++ 类。

如果您可以重写 C++ 代码,请将其更改为调用 SysAllocString 并返回 BSTR,这是操作系统提供的字符串类型,用于在以不同语言编写的组件之间进行数据交换。 P/invoke 已经拥有接收BSTR 的所有正确魔法,只需在 p/invoke 声明中使用 C# string 类型。

如果您无法更改 C++ 签名,则必须对其进行包装。要么使用可以处理 C++ 类和 .NET 的 C++/CLI,要么使用标准 C++ 创建并返回 BSTR,从 std::wstring 复制数据。


对于多个字符串,您可以使用操作系统提供的数组类型 (SAFEARRAY),它能够在内部携带 BSTR,并且与语言无关。或者,使用适当的(不自然地出现在数据中)分隔符进行连接和拆分可能会更容易。

这里有一些非常棒的信息:https://msdn.microsoft.com/en-us/magazine/mt795188.aspx

运行 .NET Framework(Windows 上的 Microsoft 实现),您可以使用帮助类:

#include <windows.h>
#include <atlbase.h>
#include <atlsafe.h>
#include <string>

extern "C" __declspec(dllexport) LPSAFEARRAY GetFileList()

    std::vector<std::wstring> myList;
    //Code that populates the list correctly
    size = myList.size();

    CComSafeArray<BSTR> sa(size);
    int i=0;
    for( auto& item : myList ) 
        CComBSTR bstr(item.size(), &item[0]);
        sa.SetAt(i++, bstr.Detach()); // transfer ownership to array
    
    return sa.Detach(); // transfer ownership to caller

在 C# 方面,p/invoke 处理一切:

[DllImport("NativeDll.dll"),
 return:MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_BSTR)]
public static extern string[] GetFileList();

您的问题表明您可能使用的是 Linux。不幸的是,我找到的文档似乎说 Mono 的 p/invoke 根本不知道如何处理可变大小的数组。所以你被困在手工做所有事情,包括释放(你问题中的代码泄漏new wstring[size] - C# 完全不知道如何调用 C++ delete[] 运算符)。看看Mono.Unix.UnixMarshal.PtrToStringArray

【讨论】:

我也有单字符串的想法,我被明确告知我不应该/不能这样做 我不明白如何使用 BSTR,我以前从未见过。你能提供和例子吗? 我和 Wayne 一起工作,我可以告诉你他已经在使用 C++/CLI。 @DCShannon:这让它变得容易多了。在 C++/CLI 类中,您可以返回一个 cli::array&lt;System::String^&gt;^,它只是 C# 的 string[] 的 C++ 名称(SAFEARRAY 将自动转换,这已经是 C# 类型),System::String 有一个构造函数,它采用大小 (wstr.size()) 和指针 (&amp;wstr[0])。 @BenVoigt 非常感谢您的帮助。可能是一个错字,但我无法让它按照你的方式工作。我需要将DllImportreturn 部分放在两个单独的[ ] 块中

以上是关于如何在 C# 中编组 wstring *?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 C# 中编组结构数组?

如何在 C# 中编组数据类型 unsigned char**?

如何在 C# 中编组指向一系列以 null 结尾的字符串的指针?

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

如何编组包含未知大小的 int 数组的结构?

如何将 C# 字符串 [] 编组(理想情况下在 SWIG 中)到 C++ 字符串*?