6

I have a C++-CLR wrapper around a standard C++ library called from C#. To receive status messages from the library I use a delegate assigned to a callback in the C++ code via Marshal::GetFunctionPointerForDelegate.

This has taken me quite some time to get working and I'm very, very close (I think). The C# delegate is called but the string isn't passed correctly across the boundary.

When I call TakesCallback("Test String") from the C++ code I just get rubbish back in the C# function.

--- The original C++ class and callback function ---

class Solver
{
    private:

    std::string TakesCallback(const std::string message) 
    {
        return cb(message);
    }

    public:

    // Declare an unmanaged function type that takes a string
    typedef std::string (__stdcall* ANSWERCB)(const std::string);
    ANSWERCB cb;
};

--- Function to set the callback from the managed wrapper ----

// Set the delegate callback
void ManagedSolver::SetMessageCallback(SendMessageDelegate^ sendMessageDelegate)
{
    _sendMessage = sendMessageDelegate;

    // Use GetFunctionPointerForDelegate to get the pointer for delegate callback
    IntPtr ip = Marshal::GetFunctionPointerForDelegate(sendMessageDelegate);
    _solver->cb = static_cast<Solver::ANSWERCB>(ip.ToPointer());
} 

--- C# function passed to the C++ \ CLR wrapper SetMessageCallBack ----

private void Message(string message)
{
    XtraMessageBox.Show(message, "Done", MessageBoxButtons.OK);
}
Deduplicator
  • 44,692
  • 7
  • 66
  • 118
Richard Adams
  • 591
  • 1
  • 9
  • 23
  • 2
    Are you sure you mean Managed C++? Managed Extensions for C++ stopped being supported in (I think) Visual Studio 2003. C++/CLI is it's replacement, and it's a completely different language... – Billy ONeal Apr 26 '11 at 00:11
  • Hi Billy, sorry you're right of course. My wrapper is C++ / CLR around a standard C++ library. I've updated the title and body. – Richard Adams Apr 26 '11 at 00:17
  • OK I've solved it std::string won't work with interop due to the variety of implementations as stated here http://stackoverflow.com/questions/874551/stdstring-in-c I'll come back and answer this as soon as StackOverflow allows me to. – Richard Adams Apr 26 '11 at 00:41
  • Pass message.c_str() instead, compatible with the default marshaling. – Hans Passant Apr 26 '11 at 01:58

4 Answers4

3

I have been using the code from this page for some months now and I find it very good. It is only one header file you can copy across your projects and it does the job quickly and cleanly.

You use for instance

std::wstring s = clix::marshalString<E_UNICODE>(myCliString);

or

System::String^ s = clix::marshalString<E_ANSI>(mystdstring);

it works both ways and let you specify what encoding you want (ANSI, UTF8 or Windows's unicode -- actually UTF16).

Alexandre C.
  • 55,948
  • 11
  • 128
  • 197
2

C++ std::string and .NET System::String are not interchangeable. C# cannot use the first, and native C++ code cannot use the second. What you need is a C++/CLI function that accepts std::string and converts it to System::String^ before calling the delegate.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
1

C++ (Unmanaged)

class DllContext
{
public:
typedef void (__stdcall *LOG_CALLBECK)(const wchar_t* LogString);
DllContext(LOG_CALLBECK WriteLog) // Constructor
{
    this->WriteLog = WriteLog;
    this->WriteLog(L"Creating сontext..."); // UNICODE string!
}

private:
LOG_CALLBECK WriteLog;
}

// Export function
SENDAUDIOCLIENT_API DllContext *CreateContext(DllContext::LOG_CALLBECK WriteLog)
{
    return new DllContext(WriteLog);
}

C#

class MyClass
{
private delegate void WriteLog_Delegate([MarshalAs(UnmanagedType.LPWStr)]string Mess);
private WriteLog_Delegate WriteLog;

[DllImport("My.dll", EntryPoint = "?CreateContext@@YAPAVDllContext@@P6GXPB_W@Z@Z", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr CreateContext(WriteLog_Delegate WriteLogProc);

private IntPtr Context = IntPtr.Zero;

public MyClass()
{
    this.WriteLog = new WriteLog_Delegate(this.WriteLogToConsole);
        this.Context = CreateContext(this.WriteLog);
}

private void WriteLogToConsole(string Mess)
{
    Console.WriteLine("Message from unmanaged code: {0}", Mess);
}
}
Alatey
  • 371
  • 2
  • 6
1

In general, std c++ classes cannot be marshaled to/from C#. If you are building your C++ code as Unicode, I would recommend to change your code into the following:

C++

// Declare an unmanaged function type that takes a string
typedef int (__stdcall* ANSWERCB)(LPCTSTR);

C#

private void Message([MarshalAs(UnmanagedType.LPWStr)]string message)
{
    XtraMessageBox.Show(message, "Done", MessageBoxButtons.OK);
}

Take a look at the following example from MSDN

yms
  • 10,361
  • 3
  • 38
  • 68