This boils down to providing a C# callback function to a native C++ dll. The only extra detail here is that you seem to be providing the C# callback function through an intermediate managed C++ DLL.
I would attack this in the following tasks, adding one complication at a time:
- Figure out how to pass a simple C# callback directly to a native C++ dll.
- Figure out how to pass that C# callback to a native C++ dll, but through a managed C++ library.
- Figure out how to handle the signature of the arguments that your callback needs to handle
Part one - this answer already shows you how to pass managed C# callbacks to native C++ libraries using C# delegates: Passing a C# callback function through Interop/pinvoke
Part two - passing .Net delegates from managed C++ to unmanaged C++ - can be found at the following MSDN page: How to: Marshal Callbacks and Delegates By Using C++ Interop. I've pasted the code samples below in case of link rot.
Some discussion on part two - if your native C++ code does not store your callback function for any longer than you call your native C++ code, then your job is easier, because Garbage Collection in the managed C++ code won't get in your way. If it does store your callback for a longer than the duration of your call into it, then you need to inform the GC of this fact or it may collect the objects representing the callback before the last time the native C++ code calls it; if that were to happen, the native C++ code would using memory that's been freed, causing crashes.
Part three - dealing with the signature of the callback.
From your answer, we can see that you'd like to pass a CString
back to C#. Unfortunately, CString
is not a portable, standard type, and its internal structure depends on the C++ runtime the native dll was compiled with; it also likely depends on how the compiler decided to assemble that type too, which means that CString
's structure is probably arbitrary depending on who's providing the code. Refer to the following answer for more information about this: PInvoke with a CString.
However, you may have an easy out if you can change the native C++ library - change how your native function invokes your managed callback, so that the managed callback is passed a char*
, something like the following:
int dobatch(CString str)
{
// I want to to call c#
if (cb)
{
// Invoke the 'CString::operator LPCSTR' operator
// Note that 'LPCSTR' is define for 'char*'.
// See: https://msdn.microsoft.com/en-us/library/aa300569%28v=vs.60%29.aspx
char* strCharPtr = (char*)str;
return( cb(strCharPtr) );
}
}
In case of link rot, here are the code samples from the MSDN page on passing .Net delegates from managed C++ to unmanaged C++:
Listing one - the native C++ library does not store the callback any longer than the native C++ is being called:
// MarshalDelegate1.cpp
// compile with: /clr
#include <iostream>
using namespace System;
using namespace System::Runtime::InteropServices;
#pragma unmanaged
// Declare an unmanaged function type that takes two int arguments
// Note the use of __stdcall for compatibility with managed code
typedef int (__stdcall *ANSWERCB)(int, int);
int TakesCallback(ANSWERCB fp, int n, int m) {
printf_s("[unmanaged] got callback address, calling it...\n");
return fp(n, m);
}
#pragma managed
public delegate int GetTheAnswerDelegate(int, int);
int GetNumber(int n, int m) {
Console::WriteLine("[managed] callback!");
return n + m;
}
int main() {
GetTheAnswerDelegate^ fp = gcnew GetTheAnswerDelegate(GetNumber);
GCHandle gch = GCHandle::Alloc(fp);
IntPtr ip = Marshal::GetFunctionPointerForDelegate(fp);
ANSWERCB cb = static_cast<ANSWERCB>(ip.ToPointer());
Console::WriteLine("[managed] sending delegate as callback...");
// force garbage collection cycle to prove
// that the delegate doesn't get disposed
GC::Collect();
int answer = TakesCallback(cb, 243, 257);
// release reference to delegate
gch.Free();
}
Listing two - the native C++ stores the callback, and thus, we have to inform the GC of this fact:
// MarshalDelegate2.cpp
// compile with: /clr
#include <iostream>
using namespace System;
using namespace System::Runtime::InteropServices;
#pragma unmanaged
// Declare an unmanaged function type that takes two int arguments
// Note the use of __stdcall for compatibility with managed code
typedef int (__stdcall *ANSWERCB)(int, int);
static ANSWERCB cb;
int TakesCallback(ANSWERCB fp, int n, int m) {
cb = fp;
if (cb) {
printf_s("[unmanaged] got callback address (%d), calling it...\n", cb);
return cb(n, m);
}
printf_s("[unmanaged] unregistering callback");
return 0;
}
#pragma managed
public delegate int GetTheAnswerDelegate(int, int);
int GetNumber(int n, int m) {
Console::WriteLine("[managed] callback!");
static int x = 0;
++x;
return n + m + x;
}
static GCHandle gch;
int main() {
GetTheAnswerDelegate^ fp = gcnew GetTheAnswerDelegate(GetNumber);
gch = GCHandle::Alloc(fp);
IntPtr ip = Marshal::GetFunctionPointerForDelegate(fp);
ANSWERCB cb = static_cast<ANSWERCB>(ip.ToPointer());
Console::WriteLine("[managed] sending delegate as callback...");
int answer = TakesCallback(cb, 243, 257);
// possibly much later (in another function)...
Console::WriteLine("[managed] releasing callback mechanisms...");
TakesCallback(0, 243, 257);
gch.Free();
}