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.
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.
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]));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With