从C++回调到C#的回调函数

4

假设我有一个用于计算圆周率的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()。
但是当Cpp::PI::compute()正在运行时,如何让C++库将进度更新转发到CSharp.PI.progress()?
感谢您提前的回答!

1
这篇帖子中的一些信息可能会有用。 - Mark Wilkins
2个回答

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

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

```
// 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."
            );
        }
    }
}

谢谢!我刚刚运行了你的第二种方法,它非常完美 :-) - Andy
我猜想,如果我将Cpp::PI::compute()编写为extern "C"函数,就可以完全跳过C++ / CLI,并使用Marshal::GetFunctionPointerForDelegate()直接将指向C#函数的指针传递给C++? - Andy
1
@Andreas: “我猜如果我将 Cpp::PI::compute() 写成一个 extern "C" 函数,就可以完全跳过 C++/CLI 了。” 的确如此。 "并且直接使用 Marshal::GetFunctionPointerForDelegate() 将指向 C# 函数的指针传递给 C++?" 不需要使用 Marshal.GetFunctionPointerForDelegate(),你可以在 C# 中声明 P/Invoke 函数以取一个恰当签名的委托,CLR会帮你转换委托为函数指针。 - ildjarn
我该如何扩展这个模型以便将一个类(对象)传递回回调函数? - JohnB
@JohnB:使用GCHandle获取IntPtr,并让本地端传递结果的void* - ildjarn

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

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接