如何将 .NET 字符串正确编组为本机代码的 std::wstrings?

Posted

技术标签:

【中文标题】如何将 .NET 字符串正确编组为本机代码的 std::wstrings?【英文标题】:How to correctly marshal .NET Strings to std::wstrings for native code? 【发布时间】:2015-06-23 09:21:43 【问题描述】:

我有一个第三方库,它有一个类,其中构造函数采用std::wstring

构造函数由第三方在头文件中这样定义:

Something(const std::wstring &theString);

我的头文件有这个:

extern "C" __declspec(dllexport) ThirdParty::Something* createSomething(const std::wstring &theString);

我的实现是这样的:

ThirdParty::Something* Bridge::createSomething(const std::wstring &theString) 
    return new ThirdParty::Something(theString);

现在在我的 C# 示例程序中,我有:

[DllImport("Bridge.dll", CallingConvention=CallingConvention.StdCall, CharSet=CharSet.Unicode)]
public static extern IntPtr createSomething(StringBuilder theString);

当我现在尝试这样称呼时:

IntPtr ip = createSomething(new StringBuilder("foo"));

我收到了AccessViolationException。当我使用String 而不是StringBuilder 时,我得到一个SEHException

我遗漏了什么或做错了什么?

编辑当我在createSomething 函数中只使用return 0 时,我在使用String 时得到一个StackImbalanceException

【问题讨论】:

您需要一些 C++/CLI 和/或一些纯 C++(您可以选择)。您不能从 C# 创建 C++ 对象。 我尝试按照本教程进行操作:codeproject.com/Articles/18032/How-to-Marshal-a-C-Class,他们在那里做我正在尝试的事情。 一般来说,与第三方 C++ 库的链接是一团糟……如果你使用相同的编译器和 C++ 库的相同选项,那么就没有问题……否则就是糟糕... 该教程不会创建std::stringstd::wstring。而且这两种解决方案都需要创建一个 C/C++ 桥,其中桥不接受 C++ 对象... 【参考方案1】:

在 DLL 接口边界处具有 STL 类非常脆弱并且高度约束,例如,您必须注意 DLL 及其客户端都是使用 same C++ 编译器版本,带有 same 开关,与 CRT 的 same 风格的链接等。 此外,它不能在原生 C++ 和 C# 之间“开箱即用”地工作。

对于 C++ 和 .NET 互操作,我建议您使用 C++/CLI 在您的原生 C++ 代码组件和您的 .NET C# 代码之间构建一个桥接层。

如果您选择这样做,在本机 C++ 代码(例如,使用 std::wstring)和 .NET 代码(使用 .NET 的托管 Strings)之间编组字符串,您可以使用 Microsoft 构建并在此处列出的包装器:

Overview of Marshaling in C++

例如,要将 .NET 托管字符串转换为本机 C++ std::wstring,您可能需要使用如下代码:

#include <string>
#include <msclr\marshal_cppstd.h>

System::String^ managedString = "Connie";
std::wstring nativeString 
    = msclr::interop::marshal_as<std::wstring>(managedString);

【讨论】:

请务必注意,使用 C++/CLI 不会解决第一段中描述的问题。使用 C++/CLI 进行互操作的前提是没有版本不匹配。 @xanatos:你是对的。我认为最好的“隔离”是构建 COM 组件(确实有学习曲线)或具有 纯 C 接口的 DLL。【参考方案2】:

我不相信 .Net marshaller 支持 C++ ABI。

您需要将 .Net 字符串编组为 wchar_t*,然后在本机端创建 std::wstring

或者,您可以使用 C++/CLI(假设 msvc)为您调解两者(通过 marshal_as),它理解 .Net 字符串并有编组器将其编组为 std::wstring。 Microsoft 提供了几种标准封送处理程序,请参阅他们的overview here。

我的经验通常是 C++/CLI 存根在这些情况下更简洁更容易(您的里程会在此处有所不同),否则您可以尝试为第三方库提供简单的 C 样式 API你。

您的示例代码暗示了一段 Bridge 可能已经在您的控制之下的代码。考虑使桥接器中的接口尽可能简单(内置类型、POD 等),并且应该简化第三方库的集成。

另外值得注意的是,如果你要链接第三方的C++库,是使用相同的编译器、设置、调用约定和运行时等。做,否则你仍然会遇到 ABI 问题。为外部库提供导出 C++ 接口(包括 STL)并不总是一个好主意。

基本上有几种方法可以将这些代码片段链接在一起,您必须选择一种适合所使用的工具链的方法。

【讨论】:

链接第三方 DLL 的主要问题是 C++ 运行时版本的混合...例如参见 siomsystems.com/mixing-visual-studio-versions 参见 传递 C/C++ 对象(非原始数据)对象)章节。 在 .Net 字符串编组之外,还有许多其他 C++ 问题在起作用。假设他们具有与供应商相同的编译器等。好点,值得添加到答案中。【参考方案3】:

解决方案要简单得多、简单得多!我只是忘记了__stdcall,需要使用wchar_t

我的标题条目现在看起来像:

extern "C" __declspec(dllexport) ThirdParty::Something* __stdcall createSomething(wchar_t* theString);

实现也是如此:

ThirdParty::Something* __stdcall Bridge::createSomething(wchar_t* theString) 
    std::wstring theWString = std::wstring(theString);
    return new ThirdParty::Something(theWString);

现在我可以传入Strings 和StringBuilders。

【讨论】:

以上是关于如何将 .NET 字符串正确编组为本机代码的 std::wstrings?的主要内容,如果未能解决你的问题,请参考以下文章

在 c# 中将 uchar[] 从本机 dll 编组为 byte[] 的正确方法

如何编组多维数组

如何将 C++ 本机对象编组到托管 C++ CLI

如何手动将 .NET 对象编组为双 COM 接口?

.NET 中本机互操作性的代码组织

.NET 中本机互操作性的代码组织