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

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

问题描述:

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

I am looking to create a C# program that calls a managed C++ dll, which then executes functionality in a native C++ dll. As part of this call, I want to provide a C# callback function that could be invoked by the native C++ dll.

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

The C# code would call some managed C++ code, which would call the following native C++ code; I want the native C++ code to call my 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()函数混在一起.

Any ideas...I just can't seem to get the C# callback to mix with calling the native C++ dobatch() function via the managed C++ dll.

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

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:

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

第一部分-此答案已经向您展示了如何使用C#委托将托管的C#回调传递给本机C ++库:

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

第二部分-将.Net委托从托管C ++传递到非托管C ++-可以在下面的MSDN页面上找到:

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.

第二部分的一些讨论-如果您的本机C ++代码存储回调函数的时间不超过调用本机C ++代码的时间,那么您的工作就容易了,因为托管C ++代码中的垃圾回收将无法进入你的方式.如果它确实将回调存储的时间长于对其进行调用的持续时间,则您需要将此事实通知GC,否则它可能在上一次本机C ++代码调用该回调之前收集表示该回调的对象;否则,您可能需要保留此回调.如果发生这种情况,本机C ++代码将使用已释放的内存,从而导致崩溃.

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.

从您的答案中,我们可以看到您想将CString传递回C#.不幸的是,CString不是可移植的标准类型,其内部结构取决于编译本机dll所使用的C ++运行时.它还可能取决于编译器也决定如何汇编该类型,这意味着CString的结构可能是任意的,具体取决于谁提供代码.有关此问题的更多信息,请参考以下答案:使用CString进行P调用.

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.

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

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) );
    }
}


如果发生链接腐烂,这是MSDN页面上的代码示例,这些代码示例将.Net委托从托管C ++传递到非托管C ++:


In case of link rot, here are the code samples from the MSDN page on passing .Net delegates from managed C++ to unmanaged C++:

列出一个-本机C ++库存储回调的时间不超过调用本机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();
}

列出两个-本地C ++存储回调,因此,我们必须将此事实通知GC:

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();
}