我已经完成了许多托管包装器,这些包装器处理包装非托管代码以便在托管中使用,但没有那么多是相反的。
我正在运行的一个实验涉及使用托管代码来查找目录并将它们返回到 std vector 中。长话短说,我在处理以下示例时发现了一个问题。
#include "Helper.h"
#include <msclr/marshal.h>
#include <msclr/marshal_cppstd.h>
using namespace msclr::interop;
using namespace System;
namespace CLIWrapper {
std::vector<std::string> Helper::GetDirs(const char* root)
{
std::vector<std::string> rval;
String^ path = gcnew System::String(root);
array<String^,1>^ dirs = System::IO::Directory::GetDirectories(path);
for (int i=0; i < dirs->Length; i++)
{
//this fails
std::string nativeString1(marshal_as<std::string>(dirs[i]));
//this fails as well
std::string nativeString2(marshal_as<std::string>((String ^ const)dirs[i]));
// this works
String ^mStr = dirs[i];
std::string nativeString(marshal_as<std::string>(mStr));
rval.push_back(nativeString);
}
return rval;
}
}
“nativeString1”和“nativeString2”的失败是: 错误 C2665:“msclr::interop::marshal_as”:3 个重载中没有一个可以转换所有参数类型
“nativeString2”使用 const,因为如果您查看错误的详细信息,它会列在 marshal_as 的签名之一中。
问题是为什么“nativeString1”转换失败但“nativeString”有效?我的眼睛拒绝注意到什么?
在它出现在响应线程之前:是的,我意识到这不是“最佳”解决方案,它不是平台独立的,等等。我正在努力关注这个特定的错误。
最佳答案
这是由评论中提到的 Justin 签名引起的,即
template <> inline std::string marshal_as(System::String^ const & _from_obj)
这是一件非常糟糕的事情。它是对指向 Ssytem::String
的跟踪指针的非跟踪引用。因为它是一个 const
引用,它可以绑定(bind)到一个临时引用,但是因为它是一个非跟踪引用,它不能绑定(bind)到垃圾收集堆内的内存位置,因为 gc 堆上的对象可以四处走动。
您应该已经能够通过身份转换来解决这个问题,根据 C++ 标准,身份转换会创建一个相同类型的临时对象。临时对象不在 gc 堆上,所以 是 的一切都可以。
不幸的是,有一些compiler bugs related to identity casts因此,您实际上并没有得到临时的。
Justin 转换为跟踪引用并返回跟踪指针是另一种创建临时对象的方法。不幸的是,他的回答包含一些关于引用计数的胡言乱语。 .NET 对象不进行引用计数。
最重要的是,一开始就没有理由通过 const 引用传递该参数。跟踪指针很小,易于复制。这是 marshal_as
作者的一个风格错误,接近于一个错误。您可以将头文件更改为
template <> inline std::string marshal_as(System::String^ const _from_obj)
不破坏实现。
另一个修复方法是使用跟踪引用,例如
template <> inline std::string marshal_as(System::String^ const % _from_obj)
但同样,没有意义,因为按值传递是如此便宜。
关于C++托管到非托管的转换,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25650435/