如何将 .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::string
或std::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 的托管 String
s)之间编组字符串,您可以使用 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);
现在我可以传入String
s 和StringBuilder
s。
【讨论】:
以上是关于如何将 .NET 字符串正确编组为本机代码的 std::wstrings?的主要内容,如果未能解决你的问题,请参考以下文章