Reputation: 601
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
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
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
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
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