从 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++ 回调,Marshal.GetFunctionPointerForDelegate 有啥作用?