如何正确地将.NET字符串封送为本机代码的std::wstrings

How to correctly marshal .NET Strings to std::wstrings for native code?

本文关键字:本机代码 std wstrings 正确地 NET 字符串      更新时间:2023-10-16

我有一个第三方库,它有一个构造函数接受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

我错过了什么或做得不对?

EDIT当我在createSomething函数中仅使用return 0时,当使用String时,我会得到StackImbalanceException

我不相信现成的.Net整理器支持C++ABI。

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

或者,您可以使用C++/CLI(假设为msvc)为您调解两者(通过marshal_as),它理解.Net字符串,并让marshaller将其封送为std::wstring。Microsoft提供了几种标准封送拆收器,请参阅此处的概述。

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

您的示例代码提示您可能已经控制了一段Bridge代码。考虑保持桥接中的接口尽可能简单(内置类型、POD等),它应该简化第三方库的集成。

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

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

在DLL接口边界上拥有STL类是非常脆弱的和具有高度约束性的,例如,您必须注意DLL及其客户端都是使用相同的C++编译器版本、使用同一开关、与风格相同的CRT链接等构建的。此外,它在本机C++和C#之间并不能"开箱即用"。

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

如果您选择这样做,要在本机C++代码(例如使用std::wstring)和.NET代码(使用.NET的托管String)之间封送字符串,您可以使用Microsoft构建的包装器,如下所示:

C++中的编组概述

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

#include <string>
#include <msclrmarshal_cppstd.h>
System::String^ managedString = "Connie";
std::wstring nativeString 
    = msclr::interop::marshal_as<std::wstring>(managedString);

解决方案非常简单!我只是忘记了__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);
}

现在我可以通过String s和StringBuilder s。