Richard Adams
Richard Adams

Reputation: 601

How to pass a string from C++-CLI to C# via C++-CLI callbacks and delegates

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

Upvotes: 6

Views: 5965

Answers (4)

Alexandre C.
Alexandre C.

Reputation: 56956

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).

Upvotes: 3

Alatey
Alatey

Reputation: 379

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

Upvotes: 1

yms
yms

Reputation: 10418

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

Upvotes: 1

Ben Voigt
Ben Voigt

Reputation: 283664

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.

Upvotes: 2

Related Questions