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

2 c# pinvoke

我想创建一个C#程序,调用托管C++ DLL,然后在本机C++ DLL中执行功能.作为此调用的一部分,我想提供一个可以由本机C++ dll调用的C#回调函数.

C#代码将调用一些托管C++代码,这些代码将调用以下本机C++代码; 我希望本机C++代码调用我的C#回调cb:

int dobatch(CString str)
{
    // I want to to call c#
    if (cb)
        return(cb(str)); // this would execute the function passed from c#.
}
Run Code Online (Sandbox Code Playgroud)

任何想法......我似乎无法让C#回调与dobatch()通过托管C++ dll 调用本机C++ 函数混合.

ant*_*duh 5

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

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

  • 弄清楚如何将简单的C#回调直接传递给本机C++ DLL.
  • 弄清楚如何将C#回调传递给本机C++ DLL,但是通过托管C++库.
  • 弄清楚如何处理回调需要处理的参数的签名

第一部分 - 这个答案已经向您展示了如何使用C#委托将托管C#回调传递给本机C++库:通过Interop/pinvoke传递C#回调函数

第二部分 - 将.Net委托从托管C++传递到非托管C++ - 可以在以下MSDN页面找到:如何:使用C++ Interop编写回调和委托.我已粘贴下面的代码示例以防链接腐烂.

关于第二部分的一些讨论 - 如果您的本机C++代码没有比调用本机C++代码更长时间存储您的回调函数,那么您的工作就更容易了,因为托管C++代码中的垃圾收集不会妨碍您.如果它确实存储了你的回调的持续时间超过了它的调用时间,那么你需要告知GC这个事实,或者它可能会在本机C++代码最后一次调用它之前收集代表回调的对象; 如果发生这种情况,原生C++代码将使用已被释放的内存,从而导致崩溃.

第三部分 - 处理回调的签名.

从你的回答中,我们可以看到你想CString回传给C#.不幸的是,CString它不是一个可移植的标准类型,它的内部结构依赖于编译本机dll的C++运行时; 它也可能取决于编译器如何决定组装该类型,这意味着CString结构可能是任意的,具体取决于谁提供代码.有关此内容的更多信息,请参阅以下答案:带有CString的PInvoke.

但是,如果您可以更改本机C++库,则可能会很容易 - 更改本机函数调用托管回调的方式,以便传递托管回调a char*,如下所示:

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) );
    }
}
Run Code Online (Sandbox Code Playgroud)

在链接腐烂的情况下,以下是从托管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();
}
Run Code Online (Sandbox Code Playgroud)

清单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();
}
Run Code Online (Sandbox Code Playgroud)