Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

c++ Managed to unmanaged conversions

I have done a number of Managed wrappers that deal with wrapping unmanged code for use in managed, but not so many that go the other way.

An experiment I was running involved using managed code to find directories and return them in a std vector. Long story short, I was messing with the following example and noticed a problem.

#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;
   }

}

Failure for ‘nativeString1’ and ‘nativeString2’ is: error C2665: 'msclr::interop::marshal_as' : none of the 3 overloads could convert all the argument types

‘nativeString2’ uses the const because that is listed in one of the signatures of the marshal_as if you look at the details of the error.

Question is why does the ‘nativeString1’ conversion fail but ‘nativeString’ work? What are my eyes just refusing to notice?

Before it comes up in the response thread: Yes, I realize this isn’t the ‘best’ solution, that it isn’t platform independent, etc. I’m trying to focus on this specific error.

like image 339
user3228938 Avatar asked Mar 18 '23 14:03

user3228938


2 Answers

This is caused by the signature Justin mentioned in a comment, to wit

template <> inline std::string marshal_as(System::String^ const & _from_obj)

This is a really bad thing to do. It's a non-tracking reference to a tracking pointer to Ssytem::String. Because it is a const reference, it can bind to a temporary, however because it is a non-tracking reference, it cannot bind to a memory location inside the garbage collected heap, because objects on the gc heap can move around.

You should have been able to work around this by an identity cast, which according to the C++ standard, creates a temporary of the same type. Temporaries aren't on the gc heap, so everything is would be fine.

Unfortunately, there are some compiler bugs related to identity casts and as a result, you don't actually get a temporary.

Justin's conversion to tracking reference and back to tracking pointer is another way of creating a temporary. Unfortunately his answer contains some mumbo-jumbo about reference counts. .NET objects aren't reference counted.

To top it all off, there was no reason to pass that parameter by const reference in the first place. Tracking pointers are small and easy to copy. This is a style error on the part of the marshal_as author bordering on being a bug. You could change the header file to

template <> inline std::string marshal_as(System::String^ const _from_obj)

without breaking the implementation.

Another fix would be to use a tracking reference, like

template <> inline std::string marshal_as(System::String^ const % _from_obj)

but again, there's no point since pass-by-value is so cheap.

like image 135
Ben Voigt Avatar answered Mar 29 '23 07:03

Ben Voigt


The compiler does not consider dirs[i] to be a constant reference to the string (this is surprising to me too). However, you can get a temporary constant reference to the value without creating a new string handle by using the % operator, which will increment the reference count of the string at dirs[i]:

// this works as well
auto nativeString1(marshal_as<std::string>(%*dirs[i]));
like image 29
Justin R. Avatar answered Mar 29 '23 06:03

Justin R.