转载:出处

现在比较流行C#与C++融合:C#做GUI,开发效率高,C++做运算,运行效率高,二者兼得。

但是C++与C#必然存在数据交互,C#与C++dll的数据交互从来都是一个让人头疼的问题。

从调用方式看也有两种情况:

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[]数组一定要加上MarshalAs标记,标记为传递数组,而且必须指定传递的数量,如果不标记数量,则每次只传递一个数值,这个问题折磨我很久才搞定!

其他注意事项:

1、如何在C#中保持C++的函数指针

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

假设有个函数向C++dll传递指针:

1 public delegate void EKFRenderCallback(string data, string colors);
2  
3 public class EKFLib
4 {
5     [DllImport("EKFLib.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
6     public static extern void SetRenderCallback(EKFRenderCallback render);

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

01 public void RenderCallback(string data, string color)
02 {
03     // rendering
04 }
05  
06 private void Window_Loaded(object sender, RoutedEventArgs e)
07 {
08     EKFLib.SetRenderCallback(RenderCallback);
09     EKFLib.Init();
10 }

这虽然没什么问题,但是通过SetRenderCallback()传入到C++的指针不受托管代码管理,在C#中认为此指针对象未被任何代码引用,GC做垃圾回收时,将会把C#本地的空指针回收,导致C++无法执行回调,出现“CallbackOnCollectedDelegate”错误:

对“MotionCapture!MotionCapture.EKFRenderCallback::Invoke”类型的已垃圾回收委托进行了回调。这可能会导致应用程序崩溃、损坏和数据丢失。向非托管代码传递委托时,托管应用程序必须让这些委托保持活动状态,直到确信不会再次调用它们。

微软官网的例子是控制GC回收机制,这是个比较笨拙的方法,更加理所当然的方法是把委托定义成一个属性,指向一个new出来的callback,然后再把这个callback传递进C++dll中,这样,在C#端有对象引用,保证了GC不会回收此callback:

01 public void RenderCallback(string data, string color)
02 {
03     // rendering
04 }
05  
06 private EKFRenderCallback render;
07 private void Window_Loaded(object sender, RoutedEventArgs e)
08 {
09     render = new EKFRenderCallback(RenderCallback);
10     EKFLib.SetRenderCallback(render);
11     EKFLib.Init();
12 }

2、__stdcall与_cdecl传递数据

最近一个项目是通过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这样变参数的函数必须用这种规则。编译器在编译的时候对这种调用规则的函数生成修饰名的饿时候,仅在输出函数名前加上一个下划线前缀,格式为_functionname。
2. __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#传递数据必须注意以下几点:

1、C++中的回调函数必须用_stdcall标记,使用stdcall方式回调;

2、如果是数组,必须用 [MarshalAs(UnmanagedType.LPArray, SizeConst = 23)]标记参数,指定为数组且标记数组长度;

3、C#方必须申明一个变量,用来指向C++的回调指针函数,避免被C#回收掉。

C++通过Callback向C#传递数据,注意问题的更多相关文章

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

    现在比较流行C#与C++融合:C#做GUI,开发效率高,C++做运算,运行效率高,二者兼得. 但是C++与C#必然存在数据交互,C#与C++dll的数据交互从来都是一个让人头疼的问题. 从调用方式看也 ...

  2. Android开发学习之路-回调实现Service向activity传递数据

    开启服务的时候,如果我们是通过bindService来绑定服务并且要向服务传递数据,可以直接在Intent中设置bundle来达到效果,但是如果是我们需要从服务中返回一些数据到Activity中的时候 ...

  3. MVC中前台如何向后台传递数据------$.get(),$post(),$ajax(),$.getJSON()总结

    一.引言 MVC中view向controller传递数据的时候真心是一个挺让人头疼的一件事情.因为原理不是很懂只看一写Dome,按葫芦画瓢只能理解三分吧. 二.解读Jquery个Ajax函数 $.ge ...

  4. Android 回调函数的理解,实用简单(回调函数其实是为传递数据)

    作者: 夏至,欢饮转载,也请保留这段申明 http://blog.csdn.net/u011418943/article/details/60139910 一般我们在不同的应用传递数据,比较方便的是用 ...

  5. cocos2d JS 自定义事件分发器(接收与传递数据) eventManager

    简而言之,它不是由系统自动触发,而是人为的干涉 较多情况用于传递数据 var _listener1 = cc.EventListener.create({ event: cc.EventListene ...

  6. 客户端相关知识学习(十)之app给h5传递数据

    方法一: app可以把参数传到h5的链接里,用类似?xx=xx&xx=xx的形式拼接,js解析参数即可. 方法二: 情况一:app调用h5 原生app都可以对js的function进行触发,前 ...

  7. 探究Spring Boot中的接收参数问题与客户端发送请求传递数据

    结合此篇参考Spring框架学习笔记(9)--API接口设计相关知识及具体编码实现 在使用Spring Boot进行接收参数的时候,发现了许多问题,之前一直都很忙,最近才稍微有空研究一下此问题. 网上 ...

  8. Android中Service通信(一)——启动Service并传递数据

    启动Service并传递数据的小实例(通过外界与服务进行通信): 1.activity_main.xml: <EditText android:layout_width="match_ ...

  9. angular input标签只能单向传递数据的问题

    angularjs input标签只能单向传递数据的问题 <ion-view title = "{{roomName}}" style = "height:90%; ...

随机推荐

  1. mysql 货币字段类型的存储

    loat类型是可以存浮点数(即小数类型),但是float有个坏处,当你给定的数据是整数的时候,那么它就以整数给你处理.这样我们在存取货币值的时候自然遇到问题,我的default值为:0.00而实际存储 ...

  2. Swift 与 JSON 数据 浅析

    转载自:http://www.cnblogs.com/theswiftworld/p/4660177.html 我们大家平时在开发 App 的时候,相信接触最多的就是 JSON 数据了.只要你的 Ap ...

  3. CDockablePane 关闭的问题

    显示或者隐藏 当点击CDockablePane上的关闭按钮时,并不能将其关闭,知识将其隐藏了起来,如果需要重新显示或隐藏,则相关命令的响应函数如下: if(m_Panes.GetSafeHwnd()) ...

  4. surpersocket客户端

    大家在学习surpersocket时候,都是拿telnet测试的吧,是不是没有 客户端 而感到 烦恼. 我么,就抽了一点时间 写了个简单的客户端代码. 针对QuickStart的 1-Basic 第一 ...

  5. VC与ADO数据库操作

    VC与ADO数据库操作 学研部的同志们,大家好! 想开一次学习会,实习时间冲突了,只好把文档发给大家看了.重点推荐李振龙的BMP读图教程! 尤其是大三GIS班的同志,注意了,可能实习用得上的! 一.A ...

  6. EnglishWords----1

    upon在...上面 upright垂直的,直立的 upward向上的 outward外面的,向外的 downward向下的 vertical垂直的 parallel平行的 sow播种 cut切 ch ...

  7. 主机windwo7+虚拟机centos如何配置虚拟机可以上网,且与主机互ping通

    /etc/sysconfig/network-scripts/ifcfg-eth0的配置如下: E=eth0PROTO=static#IPADDR=10.1.1.138IPADDR=192.168.0 ...

  8. zabbix Lack of free swap space

    Zabbix初始设计是大型公司用于监控服务器集群的,但日常中也用于监控VPS或云主机.后者情况下Zabbix的很多配置和属性就没有经过优化,取决于监控的对象和用途,经常需要对一些Zabbix配置进行调 ...

  9. 使用JQuery结合HIghcharts实现从后台获取JSON实时刷新图表

    项目做了一个报表,可以实时的观察呼叫中心的电话访问量,之前的版本是使用JFreechart做的,使用一段时间后出现内存溢出,服务器的内存使用量会变得很大,所以改用Ajax前台加载数据的方式实现实时报表 ...

  10. 二分图匹配之最佳匹配——KM算法

    今天也大致学了下KM算法,用于求二分图匹配的最佳匹配. 何为最佳?我们能用匈牙利算法对二分图进行最大匹配,但匹配的方式不唯一,如果我们假设每条边有权值,那么一定会存在一个最大权值的匹配情况,但对于KM ...