Conversion between types: C++ to C++/CLI

I am trying to wrap a C++ library into a C++/CLI library for use in C#. I am having trouble with conversion between types…

The C++ function looks like this (this is from MyCppLib.h file, I can’t change the C++ code):

int CppFunc(void* Handle, wchar_t* Feature, long long* Value)

My C++/CLI wrapper function looks like:

#include "MyCppLib.h"
#include <msclrmarshal.h>
#include <string.h>
#include <stdlib.h>

using namespace System;
using namespace System::Runtime::InteropServices;
using namespace msclr::interop;

namespace MyCppCliLib
{
    int CppCliFunc(IntPtr Handle, String^ Feature, long long* Value) {
        return ::CppFunc(Handle.ToPointer(), string_to_wchar(Feature), Value);
    }

    static wchar_t* string_to_wchar(String^ managedString)
    {
        marshal_context^ context = gcnew marshal_context();
        wchar_t* unmanagedString = context -> marshal_as<wchar_t*>(managedString);
        delete context;
        return unmanagedString;
    }
}

However, it seems I can’t Marshall the String^ to wchar_t*… there is error C2065 saying “this conversion is not supported”. Although if I replace it with const wchar_t*, it seems to work… I don’t get it… And I am not sure if I am converting Handle and Value types correctly…

How can I convert C++ void*, wchar_t* and long long* types to C++/CLI ?

Answer

The marshal_context class is a native type, not a garbage-collected type. So you cannot write gcnew marshal_context()

The intended use is as follows:

marshal_context context;
const wchar_t* unmanagedString = context.marshal_as<const wchar_t*>(managedString);
// use unmanagedString
// when context goes out of scope, both the context object and the memory for unmanagedString are freed

You have a problem though, you want to return a pointer. Nothing in your question or code has said anything about the contract between string_to_wchar and its caller about how the memory should be eventually freed. We can avoid this problem by noting that your helper function has gotten too short to be useful, so you can eliminate it:

#include <msclrmarshal.h>
int CppCliFunc(IntPtr Handle, String^ Feature, long long* Value)
{
    marshal_context context;
    return ::CppFunc(Handle.ToPointer(),
                     context.marshal_as<const wchar*>(Feature),
                     Value);
}

Now the string data is still alive at the time that ::CppFunc needs it.

However, you need a non-const pointer. System::String^ is meant to be immutable (it isn’t actually, which causes some problems) and so the marshal_as library prevents you from getting a writable pointer. You can easily get a writable pointer to a copy of the string, though:

#include <msclrmarshal_cppstd.h>
int CppCliFunc(IntPtr Handle, String^ Feature, long long* Value)
{
    std::wstring strFeature{marshal_as<std::wstring>(Feature)};
    return ::CppFunc(Handle.ToPointer(),
                     &stdFeature[0],
                     Value);
}