从 C++ 回调到 C#

Posted

技术标签:

【中文标题】从 C++ 回调到 C#【英文标题】:Callbacks from C++ back to C# 【发布时间】:2011-06-28 14:03:51 【问题描述】:

假设我有一个用于计算 PI 的 C++ 库函数:

// pi.h:
#ifdef BUILDING_DLL
#define DLL_MACRO __declspec(dllexport)
#else
#define DLL_MACRO __declspec(dllimport)
#endif

namespace Cpp 
  class PI 
  public:
    static double DLL_MACRO compute();
  ;
; 

// pi.cpp: 
#include "pi.h"
#include <cmath>

double 
Cpp::PI::compute() 
   // Leibnitz summation formulae:
   double sum = 0.0;
   for(long n = 0; n < 100*1000*1000; n++)
     sum += 4.0*pow(-1.0, n)/(2*n + 1.0);
   return sum;
 

我需要从 C# 调用此函数,并且我想使用 C++/CLI 作为“桥梁”。但是这个 C++ 函数有点慢。因此,调用此函数的 C# 代码需要获取回调,告诉它该函数在 % 中已经走了多远。 C# 代码可能需要一些状态,例如一个进度条,用来处理这些信息。 所以来自 C++ 的回调必须进入 C# 端的成员函数。

所以我介绍一下:

// piCLI.h: The C++/CLI "bridge" between C# and C++
#include "pi.h"
#pragma once
namespace CLI 
   public ref class PI abstract 
   public:
      double compute() 
        return Cpp::PI::compute();
      
      virtual void progress(int percentCompleted) = 0;        
   ;
 ; 

 namespace CSharp
 
    public class PI : CLI.PI
    
       public override void progress(int percentCompleted)
       
          System.Console.WriteLine(percentCompleted + "% completed.");
       
     
  

现在调用 CSharp.PI.compute() 工作正常 :-)。它按预期将调用转发给 Cpp::PI::compute()。

但是如何让 C++ 库在 Cpp::PI::compute() 运行时将进度更新转发到 CSharp.PI.progress()?

提前感谢您的任何回答!

【问题讨论】:

this post 中的一些信息可能有用。 【参考方案1】:

我也会采用函数指针/委托方法:

// pi.h:
#pragma once

#ifndef DLL_MACRO
#ifdef BUILDING_DLL
#define DLL_MACRO __declspec(dllexport)
#else
#define DLL_MACRO __declspec(dllimport)
#endif
#endif

namespace Cpp 
    typedef void (__stdcall *ComputeProgressCallback)(int);

    class PI 
    public:
        static double DLL_MACRO compute(ComputeProgressCallback callback);
    ;


// pi.cpp: 
#include "pi.h"
#include <cmath>

double Cpp::PI::compute(Cpp::ComputeProgressCallback callback) 
    double sum = 0.;
    for (long n = 0L; n != 100000000L; ++n) 
        sum += 4. * std::pow(-1., n) / (2L * n + 1.);
        callback(/*impl*/);
    
    return sum;

// piCLI.h: The C++/CLI "bridge" between C# and C++
#pragma once
#include "pi.h"

namespace CLI 
    public delegate void ComputeProgressDelegate(int percentCompleted);

    public ref class PI abstract sealed 
    public:
        static double compute(ComputeProgressDelegate^ callback) 
            using System::IntPtr;
            using System::Runtime::InteropServices::Marshal;

            IntPtr cbPtr = Marshal::GetFunctionPointerForDelegate(callback);
            return Cpp::PI::compute(
                static_cast<Cpp::ComputeProgressCallback>(cbPtr.ToPointer())
            );
        
    ;

namespace CSharp 
    public static class PI 
        public static double compute() 
            CLI.PI.compute(
                percentCompleted => System.Console.WriteLine(
                    percentCompleted.ToString() + "% completed."
                )
            );
        
    

或者,覆盖抽象的progress 方法而不是在 C# 端创建委托:

// piCLI.h: The C++/CLI "bridge" between C# and C++
#pragma once
#include "pi.h"

namespace CLI 
    public ref class PI abstract 
        delegate void ComputeProgressDelegate(int percentCompleted);

    public:
        double compute() 
            using System::IntPtr;
            using System::Runtime::InteropServices::Marshal;

            ComputeProgressDelegate^ callback = gcnew ComputeProgressDelegate(
                this,
                &PI::progress
            );
            IntPtr cbPtr = Marshal::GetFunctionPointerForDelegate(callback);
            return Cpp::PI::compute(
                static_cast<Cpp::ComputeProgressCallback>(cbPtr.ToPointer())
            );
        

    protected:
        virtual void progress(int percentCompleted) abstract;
    ;

namespace CSharp 
    public sealed class PI : CLI.PI 
        protected override void progress(int percentCompleted) 
            System.Console.WriteLine(
                percentCompleted.ToString() + "% completed."
            );
        
    

【讨论】:

谢谢!我刚刚运行了你的第二种方法,效果很好:-) 我想如果我将 Cpp::PI::compute() 编写为外部“C”函数,我可以完全跳过 C++/CLI 并使用 Marshal::GetFunctionPointerForDelegate() 将指针传递给直接将 C# 函数转换为 C++? @Andreas : "我想如果我将 Cpp::PI::compute() 写成外部“C”函数,我可以完全跳过 C++/CLI" 是确实。 “并使用 Marshal::GetFunctionPointerForDelegate() 将指向 C# 函数的指针直接传递给 C++?” 不需要 Marshal.GetFunctionPointerForDelegate(),您可以在 C# 中声明 P/Invoke 函数以获取委托适当的签名,CLR 会将委托编组为您的函数指针。 如何扩展此模型以将类(对象)传回回调? @JohnB :使用GCHandle 获取IntPtr,并让本机端绕过生成的void*【参考方案2】:

将 C++/cli 定义的本机函数作为回调传递给您的本机 C++,并在回调时使用 gcroot 调用您的托管 C# 函数。

【讨论】:

谢谢!所以你建议我向 Cpp::PI::compute() 添加一个函数指针参数,然后在 C++/CLI 中传递一个指向具有相同签名的静态方法的指针?如果可能的话,如果我可以传入一个成员函数会很好。你认为可以使用抽象虚类代替函数指针吗?

以上是关于从 C++ 回调到 C#的主要内容,如果未能解决你的问题,请参考以下文章

将回调参数从 C# 传递到非托管 C++

从 C++ 到 C# 的结构中的 Marshal 回调

C++ 回调函数到 C#

从 C++ COM DLL 回调到 C# 应用程序

C++ 到 C# 回调结构编组

注册 C# 委托给 C++ 回调,Marshal.GetFunctionPointerForDelegate 有啥作用?