从结构句柄的 System::String^ 成员编组到 std::string

Marshal to std::string from System::String^ member of struct handle

我正在尝试从 System::String^ 编组到 std::string。

通常这可以使用 marshal_as<T> 模板完成,即

System::String ^managedString = "test";
std::string stdString = marshal_as<std::string>(managedString);

但是如果 String^ 是通过引用访问的结构的一部分,即

value struct SomeStruct {
    String ^managedString;
};

auto structHandle = gcnew SomeStruct();
structHandle->managedString = "test";
std::string stdString = marshal_as<std::string>(structHandle->managedString);

编译器会抛出如下错误

error C2665: 'msclr::interop::marshal_as' : none of the 3 overloads could convert all the argument types

然而,如果结构不是句柄,而是实际的结构,它工作正常。

   auto structHandle = gcnew SomeStruct();

这就是问题开始的地方。 structHandle 引用指向 SomeStruct 的 boxed 副本。需要装箱,因为 SomeStruct 是值类型,struct 的副本存放在 GC 堆上。

您尝试使用的 marshal_as<> 重载的签名是:

   marshal_as<std::string,System::String^>(System::String ^const &)

const& 是问题所在,您无法获得对成员的非托管引用,其地址不稳定(不是 const),因为垃圾收集器可以在 marshal_as<> 期间移动对象正在执行。当 marshal_as 现在取消引用不再存在的对象时,这将导致灾难。

解决方法是在尝试转换之前将引用从盒装对象中复制出来:

   structHandle->managedString = "test";
   String^ refCopy = structHandle->managedString;
   std::string stdString = marshal_as<std::string>(refCopy);   // fine

但这只是解决代码中真正问题的技巧。值类型的存在是为了提高代码效率,允许将结构存储在堆栈帧或 CPU 寄存器中。就像本机 C++ 对象一样。您通过装箱结构来放弃这种优势。或者换句话说,如果您不打算将其视为一个值,则声明 value struct 没有任何意义。正确使用它以获得正确的修复:

    SomeStruct value;
    value.managedString = "test";
    auto result = marshal_as<std::string>(value.managedString);  // fine

或者,如果您通过错误地在函数参数上使用 ^ 获得引用,则重写为:

    void SomeFunction(SomeStruct% arg) {
        auto nativeString = marshal_as<std::string>(arg.managedString);
        '' etc..
    }

请注意使用 % 而不是 ^ 来通过引用传递变量。但是不要那样做,值类型的要点是复制值比必须取消引用指针来获取值更便宜。确保您的值类型不是太大,它不应超过 4 个字段。如果它更大,那么你应该使用引用类型。