C#:请向C++传递全局委托对象以用于回调

手头有个C++的DLL,是公司的一个远程通话功能组件。长久以来,这个DLL对外只有三个操作接口。而从合理的角度来说,这个DLL本应再提供一些回调接口或事件之类的东西,用于在通话状态被动改变时通知外层应用程序。不过银行里的规矩多多,不能用ActiveX;而使用回调,却又似不那么的稳定,尤其在与C#交互时。所以才一直就这么用着。

直到最近,外层应用程序的开发人员提意见抱怨这个DLL。于是,一狠心,决定向这个DLL添加回调的支持。虽然不能确定之前使用回调不稳定的问题在哪,但现有资料和之前自己单独写的测试却都表明C#向C++传递的回调是可以正常工作的。因此,必定是这个DLL的什么地方存在错误才会不稳定,大不了搭上一个周末的时间,总应该能解决吧。

一般来说,实现回调的要点就两点:

1. 回调应加上WINAPI或CALL关键字,以声明其调用方式;

2. 注意参数类型尤其是长度,在C++和C#中的声明要一致;避免使用指针和引用类型。

由于之前已经单独测试过,所以就差在DLL中实战演练了。花了一天时间,根据他们的要求改好了程序。为了避免遇到传说中的住在迷宫里的那个会吃迷路的人的魔鬼。其间还打了几针预防针,先在定义回调时先排除了任何字符参数,只用整数;然后去掉了之前的那个中间DLL。原来的核心DLL是MFC扩展动态链接库,不能直接用在C#中,所以又在外边套了个正规DLL。虽然间接调用DLL很正常,但让结构更简单,总没有坏处。

一切妥当,开始调试,改掉几个小问题后,发现程序竟然正常的跑起来了,回调的输出欢快的显示到屏幕上了。又各式各样的环境各测了几遍,依然OK,看来这次运气不差,哈。接下来打包,写接口和回调说明文档,画状态图。

在发出去前,想起来又改了改一些调试和日志信息,然后又神经质的测试了一把。事后证明,这是一个让人后悔也庆幸的动作。因为,这次遇到Demo的老兄Demon了,哎,怎一个惨字了得.....

幸亏本命年的我早有时刻面对打击的觉悟,早前没有得意忘形,要不这下心情落差大去了。额,不过,来就来吧,有周末两天做后盾,不信搞不定你。

看看Demon老兄说了些啥:SYSTEM.WINDOWS.FORMS.NI.DLL 0xC0000005 access violation。

完全摸不着头脑,只能慢慢来了。

先检查了一遍DLL程序,应该没有问题,所有的回调也都是在主线程上触发的,应该跟线程也没有关系。

再来确定到底是C++ DLL的还是C# EXE的问题,把DLL和EXE在DEBUG和Release下各编译了一份,然后组合运行了一下,发现当且仅当使用EXE的Release版程序时,会出现该异常。那么,就应该是C#程序的问题了。

这只是一个非常简单的示例,实在想不明白代码可能会有什么问题。而且还是Debug正常,Release异常。先改改编译选项试试,把Optimze Code关了试试,问题照旧;再把Define Debug constant开了,这就跟Debug版本的编译选项完全一样了,还是不行。看来此路不通了。

看看Google同学有没有遇到过这样的问题,把Demon老兄说的搜了一下,结果没有相关资料…看来这下有得头疼了。

忽然想起似乎刚才DLL和EXE组合测试的时刻似乎出过一个挺不寻常的提示。再去找找看,发现Debug版的DLL和Release版的EXE一起运行会有这样的提示:

File: i386\chkesp.c
Line: 42

The value of ESP was not properly saved across a function call. This is usually a result of calling a function declared with one calling convention with a function pointer declared with a different calling convention.

再搜索一下,这次倒有不少同样不幸的人,不过正如那句话“不幸的人各有各的不幸”,他们的原因似乎跟我的情况扯不上啥关系。

这下没招了,再求助Baidu一下,看看大家用C++回调C#代码都遇到些啥情况。结果试了好几个关键字,都一无所获,正当我感觉鸭梨很大的时候,突然间,用关键字“C++ C# 回调 委托”搜到了一个有点意思的帖子 C#处理C++库回调报错_非托管代码传递委托被垃圾回收,虽然这位老兄什么都没说明,不过贴上来的异常内容很特别:

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

向C++传递的委托对象回垃圾回收了,导致回调时异常?感觉有点怪怪,不过死马当活马医吧,改Demo,传了一个全局委托对象给DLL,再测试~哈,回调输出又欢快滴出来了~

看来果真如此了,确认一下,改回原来的传递临时对象的方式,在Demo的Main函数里加个异常捕获,发现确实异常会传递到此处,内容则是“Object reference not set to an instance of an object”。虽然与上述的不一致,不过应该就是一回事啦。

现在想想,这也不难理解。C#中通过引用计数来判断对象是否可被回收,在Demo中,创建的临时委托对象只是被临时使用了一下,没有任何引用,虽然在C++ DLL中保存了,但俨然那已经不属于Demo的范围了。因此被垃圾回收很正常。

再想到之前自己单独写的测试似乎并没有发现这样的问题,那又是为什么呢?难道因为程序占用内在太少还没来得及进行垃圾回收操作?再去翻出来,点住调用按钮不放,果然,在输出三四十条信息之后程序异常了。

原文地址:https://www.cnblogs.com/yedaoq/p/1807206.html