将C#回调函数传递给托管和非托管C ++库

时间:2015-10-28 14:25:54

标签: c# pinvoke

我希望创建一个调用托管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()函数混合。

1 个答案:

答案 0 :(得分:5)

这归结为为本机C ++ dll提供C#回调函数。这里唯一的额外细节是您似乎通过中间托管C ++ DLL提供C#回调函数。

我会在以下任务中对此进行攻击,一次添加一个并发症:

  • 弄清楚如何将简单的C#回调直接传递给本机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();
}