Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Marshalling managed string from class member

Tags:

c++-cli

Using marshal_cppstd.h and msclr::interop::marshal_as<> I can marshal a managed string to std::string like this:

String^ managed = "test";
std::string unmanaged = marshal_as<std::string>(managed);

Now when managed is a member of the class I am writing code in, I get an error when doing simply this:

std::string unmanaged = marshal_as<std::string>(this->managed);

The error says:

no instance of overloaded function "marshal_as" matches the argument list

Or as compiler error C2665:

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

When I change the code to use a helper variable, it works:

String^ localManaged = this->managed;
std::string unmanabed = marshal_as<std::string>(localManaged);

There must be some implicit casting here, isn't it? Why does this happen and how I can make the simple one-liner work?

like image 915
ZoolWay Avatar asked Dec 23 '16 09:12

ZoolWay


1 Answers

Yeah, that's a pretty lousy error message and doesn't help you discover the real problem. Template error messages are often very hard to understand. It could use some repro code:

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

using namespace System;
using namespace msclr::interop;

ref class Example {
    String^ managed;
public:
    void test() {
        auto bad  = marshal_as<std::string>(this->managed);   // C2665
        auto copy = this->managed;
        auto good = marshal_as<std::string>(copy);
    }
};

You have to look in the Output window to see the compiler laboring to try to find a version of the marshal_as<> template function that matches the argument type. You'll see it considering two template specializations but not the one you want. Which is:

template <class _To_Type, class _From_Type>
inline _To_Type marshal_as(const _From_Type&);

The _From_Type& argument is the troublemaker, note how it is an unmanaged reference, &. As opposed to a tracking reference, %. Or just plain ^ as behooves a reference type like System::String.

What is hard to see is that there is a huge difference between taking a reference of this->managed vs copy. In an object like Example^, the this pointer is not stable. Its value can change while this code is running, happens when a garbage collection is triggered. Unlikely odds in most programs, but not zero. Happens when another thread in the program allocates from the GC heap and triggers a collection. The kind of thing that happens ~once a year.

That would be quite disastrous if the collection happens just as marshal_as<>() is doing its job. The unmanaged reference becomes invalid and points to garbage after the GC compacts the heap. The C++/CLI compiler cannot let this happen, so it doesn't consider this->managed& as a valid substitute for _From_Type&. Never even looks at it. The template specializations can't match it either, C2665 is the inevitable outcome.

The big, big difference with the copy argument is that its address is always stable. Stored in the stack frame in unoptimized code, often in a CPU register after the optimizer is done with it. So copy& is a valid substitute for _From_Type& and the compiler can generate the template code without trouble.

So the workaround you found is entirely valid and the most optimal way to do this. Would be nice if the compiler just did this for us, but it doesn't. The aliasing problems are not nice either, sometimes you have to copy the value back. Just something you have to know about writing C++/CLI code and a consequence of mixing managed and native code, you'll surely encounter it again some day.

like image 197
Hans Passant Avatar answered Oct 17 '22 04:10

Hans Passant