我希望创建一个调用托管C ++ dll的C#程序,然后在本机C ++ dll中执行功能。作为此调用的一部分,我想提供一个可以由本机C ++ DLL调用的C#回调函数。
C#代码将调用一些托管C ++代码,这些代码将调用以下本机C ++代码;我希望本机C ++代码调用我的C#callback cb
:
int dobatch(CString str)
{
// I want to to call c#
if (cb)
return(cb(str)); // this would execute the function passed from c#.
}
任何想法......我似乎无法让C#回调与通过托管C ++ dll调用本机C ++ dobatch()
函数混合。
答案 0 :(得分:5)
这归结为为本机C ++ dll提供C#回调函数。这里唯一的额外细节是您似乎通过中间托管C ++ DLL提供C#回调函数。
我会在以下任务中对此进行攻击,一次添加一个并发症:
第一部分 - 这个答案已经向您展示了如何使用C#委托将托管C#回调传递给本机C ++库:Passing a C# callback function through Interop/pinvoke
第二部分 - 将.Net委托从托管C ++传递到非托管C ++ - 可以在以下MSDN页面找到:How to: Marshal Callbacks and Delegates By Using C++ Interop。我已粘贴下面的代码示例,以防链接腐烂。
关于第二部分的一些讨论 - 如果您的本机C ++代码没有比调用本机C ++代码更长时间存储您的回调函数,那么您的工作就更容易了,因为托管C ++代码中的垃圾收集不会进入你的方式。如果它确实存储了你的回调的持续时间超过了它的调用时间,那么你需要告知GC这个事实,或者它可能会在本机C ++代码最后一次调用它之前收集代表回调的对象;如果发生这种情况,原生C ++代码将使用已被释放的内存,从而导致崩溃。
第三部分 - 处理回调的签名。
从您的回答中,我们可以看到您希望将CString
传递给C#。不幸的是,CString
不是可移植的标准类型,其内部结构依赖于本机dll编译的C ++运行时;它也可能取决于编译器如何决定组装该类型,这意味着CString
的结构可能是任意的,具体取决于谁提供代码。有关详细信息,请参阅以下答案:PInvoke with a CString。
但是,如果您可以更改本机C ++库,则可能会很容易 - 更改本机函数调用托管回调的方式,以便托管回调传递char*
,如下所示:< / p>
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) );
}
}
在链接腐烂的情况下,以下是从托管C ++到非托管C ++传递.Net委托的MSDN页面中的代码示例:
清单1 - 本机C ++库不会比调用本机C ++更长时间地存储回调:
// 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();
}
清单2 - 本机C ++存储回调,因此,我们必须告知GC这一事实:
// 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();
}