C++通过Callback向C#传递数据

明天可比流行C#与C++融合:C#做GUI,开发成效高,C++做运算,运维作用高,二者兼得。

但是C++与C#毫无疑问存在数据交互,C#与C++dll的多少交互向来都是3个令人喉咙痛的题材。

从调用方式看也有三种情状:

1、C#调用C++函数

那种地方用的相比较多,数据流向能够是C#流向C++,通过参数将数据传递给C++(如:SetData(double[]
data));也足以是C++流向C#(如:GetData(double[] data))。

2、C++ Callback

这种地方是C++中通过Callback的法子调用C#代码,类似于C++做过局地甩卖后向C#发送事件,事件能够指点数量(如处理后的数额)。则C++中定义函数指针的艺术是:

typedef  void(*Render)(double* data, BOOL* color);

 

C#用作委托,定义的函数被C++ callback:

public delegate void RenderCallback([MarshalAs(UnmanagedType.LPArray,
SizeConst =23)]double[] data, [MarshalAs(UnmanagedType.LPArray,
SizeConst = 23)]int[] colors);

纯属注意,delegate中的double[]数组一定要加上马尔斯halAs标记,标记为传送数组,而且必须钦赐传递的数量,假设不标记数量,则每一次只传递三个数值,那么些题目折磨小编很久才化解!

其余注意事项:

1、如何在C#中有限支撑C++的函数指针

回调函数的另一个注意事项是向C++ dll传递回调函数指针的问题

只要有个函数向C++dll传递指针:

public delegate void EKFRenderCallback(string data, string colors);

public class EKFLib
{
    [DllImport("EKFLib.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
    public static extern void SetRenderCallback(EKFRenderCallback render);

  

C#中如下传递被回调的函数:

public void RenderCallback(string data, string color)
{
    // rendering
}

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    EKFLib.SetRenderCallback(RenderCallback);
    EKFLib.Init();
}

  

这就算没什么难点,然而通过SetRenderCallback()传入到C++的指针不受托管代码管理,在C#中以为此指针对象未被其它轮代理公司码引用,GC做垃圾回收时,将会把C#当地的空指针回收,导致C++不可能实施回调,出现“CallbackOnCollectedDelegate”错误:

对“MotionCapture!MotionCapture.EKFRenderCallback::Invoke”类型的已垃圾回收委托举办了回调。那大概会招致应用程序崩溃、损坏和数据丢失。向非托管代码传递委托时,托管应用程序必须让这个委托儿和保育持活动状态,直到确信不会另行调用它们。

微软官网的例子是决定GC回收机制,这是个相比呆滞的法子,越发自然的法子是把信托定义成三个属性,指向一个new出来的callback,然后再把那几个callback传递进C++dll中,那样,在C#端有对象引用,有限支撑了GC不会回收此callback:

public void RenderCallback(string data, string color)
{
    // rendering
}

private EKFRenderCallback render;
private void Window_Loaded(object sender, RoutedEventArgs e)
{
    render = new EKFRenderCallback(RenderCallback);
    EKFLib.SetRenderCallback(render);
    EKFLib.Init();
}

 

2、__stdcall与_cdecl传递数据

近年来3个档次是因而C++ 的
dll做高速运算,然后把结果数据通过Callback的办法回调给C#(界面部分),结果总是在C#中收到回调事件后就径直挂掉(程序直接在毫无提醒的景况下退出,没有别的调节和测试新闻只怕提醒)。

造成难题的缘故是,暗中认可情形下,C++中如下概念的函数指针,私下认可是以_cdecl方式调用的:

typedef  void(*Render)(double* data, BOOL* color);

这种景况下,参数堆栈是由调用者(C++一侧)维护的,在C++调用此回调函数后,会把参数弹出堆栈而自由,导致C#读取数据时出现莫明其妙的错误。

如上是回调函数字传送递数组可能出现的气象,而正如所示,只传递叁个参数的景观,甚至会在C#方莫名其妙的卡死:

typedef void (*CalibrationProgressCallback)(double percent);

改为__stdcall的艺术即可化解难题,注脚如下:

typedef  void(__stdcall *Render)(double* data, BOOL* color);

以下来自互联网的一段_cdecl和__stdcall的分解,必须铭记:

  1. __cdecl

即所谓的C调用规则,按从右至左的依次压参数入栈,由调用者把参数弹出栈。切记:对于传送参数的内部存款和储蓄器栈是由调用者来有限支持的。再次来到值在EAX中。由此,对于象printf那样变参数的函数必须用那种规则。编译器在编写翻译的时候对那种调用规则的函数生成修饰名的饿时候,仅在输出函数名前增长1个下划线前缀,格式为_functionname。

  1. __stdcall

按从右至左的次第压参数入栈,由被调用者把参数弹出栈。_stdcall是Pascal程序的缺省调用形式,平时用于Win32
Api中,切记:函数本人在剥离时清空堆栈,再次来到值在EAX中。  __stdcall调用约定在输出函数名前增进八个下划线前缀,后面加上三个“@”符号和其参数的字节数,格式为_functionname@number。如函数int
func(int a, double b)的修饰名是_func@12

所以,从C++
dll中回调函数给C#传递数据,必须由C#函数在行使完数据后(退出函数时)自个儿清空堆栈!所C++中的回调函数指针应该如下概念:

typedef void (_stdcall *CalibrationProgressCallback)(double percent);

总结:

C++通过callback向C#传递数据必须小心以下几点:

壹 、C++中的回调函数必须用_stdcall标记,使用stdcall格局回调;

二 、假如是数组,必须用 [MarshalAs(UnmanagedType.LPArray, SizeConst =
23)]标志参数,钦赐为数组且标记数高管度;

3、C#方必须说爱他美个变量,用来指向C++的回调指针函数,制止被C#回收掉。

from:http://www.roboby.com/