4

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.

user3228938
  • 155
  • 1
  • 8
  • Const-correctness in managed code is quite troublesome, the CLR has no support for it whatsoever. Your workaround is a decent pragmatic solution, that extra variable gets optimized away so nothing to worry about. – Hans Passant Sep 03 '14 at 18:26
  • This is interesting! I wonder if it's because dirs[i] is not a constant reference, while mStr is. Here is the germane function definition: `template <> inline std::string marshal_as(System::String^ const & _from_obj)`. You might try creating a constant reference and see if you get any traction there. – Justin R. Sep 03 '14 at 18:47
  • Yes, I'd originally did it with the const definition in the "this works" section but found it to not be required. String ^const mStr = dirs[i]; – user3228938 Sep 03 '14 at 20:26

2 Answers2

4

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.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • Having run into this issue with VS2013.3, this explanation of what is going on makes the most sense (bugs and all). – Niall Nov 12 '14 at 08:30
  • You make a good point about reference counting in .NET (sorry it sounds like mumbo jumbo!). The docs for `%` state, "A tracking reference (%) behaves like an ordinary C++ reference (&) except that when an object is assigned to a tracking reference, the object’s reference count is incremented." I'm unclear on the interop internals, but assumed that the reference counting is for the unmanaged side. If you could clarify why % increments the ref count that would be awesome. – Justin R. Nov 13 '14 at 02:35
  • @JustinR.: Might you be looking at documentation for C++/CX instead of C++/CLI? C++/CX accessed WinRT objects, which are reference counted, not .NET objects which use generational garbage collection based on reachability, not ref counting. – Ben Voigt Nov 13 '14 at 02:39
  • @JustinR.: and I've just filed a documentation bug against the page you linked in your answer, since it claims to cover both C++/CX and C++/CLI, but is very careless about keeping C++/CX information within the "Windows Runtime" heading. – Ben Voigt Nov 13 '14 at 02:52
3

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]));
Justin R.
  • 23,435
  • 23
  • 108
  • 157
  • Ah! the '%' as reference indicator, of course! I've used that a couple times, but not often enough to remember about it apparently. The const part I think is a free here, so I think the literal translation of their signature becomes: "A reference to a constant string". – user3228938 Sep 08 '14 at 15:54
  • Though I am wondering why the signature isn't defined as "System::String& const %" as the '&' for reference definitions is on the unmanaged side. – user3228938 Sep 08 '14 at 16:03
  • This is a neat trick. I wish they would fix this. I've found references to this issue dating back to VS2008. – Niall Nov 12 '14 at 08:13