WPF调用C/C++DLL出现异常:System.StackOverflowException
一、问题描述
项目要求调用某公司的DLL链接库操作LED设备,要求使用WPF开发该应用程序。链接库为pArmSendQt.dll ,操作为:
【函数格式】
int SS_Send_Power_On (void)
【函数参数】
无
【函数返回值】
成功:0
失败:返回错误码代码
项目开始,应用程序与链接库调用程序分开开发,为了方便,链接库调用程序使用控制台应用程序开发,部分代码如下:
1 | static void Main(string[] args) |
运行无任何问题,然后进入合并阶段——将wpf和该调用程序合并。以为很顺利的就完成任务,结果……开始了漫长的debug
1 | public partial class MainWindow : Window |
运行即出现
提示为堆栈溢出,可能是出现死循环或者无限递归。
可是这个程序里那里有循环可言呢?
开始逐行调试:
发现程序没有任何问题,只是调试到button1事件结束后,弹出该错误,没有任何代码跟踪提示!
二、debug过程
1.由于逐行调试,没有发现问题所在,而最初使用的控制台程序却没有问题,所以首先把问题引到了项目类型,于是,使用winform操作一番:
1 | public partial class Form1 : Form |
居然发现程序没有任何错误,每一次button1都能顺利将调用函数的返回值添加到listbox中。难道是因为wpf和winform控件的问题么?
2.为了确认或排除上一步的问题,我将winform控件引入到wpf中
然后对winform的listbox控件进行操作,发现也出现错误。那就排除了第一个可能。在不停调试中,无意用label控件来记录函数返回值,发现没有错误弹出了,难道是listbox控件引发的问题?
3.分别将函数返回值放到label,textbox中,发现都没有问题,尝试用messagebox也没有问题了。真的是listbox的问题么?那为什么只有listbox会触发这个问题。
1 | int i; |
运行几次程序后,发现问题没这么“简单”,一眼看上去没有错误弹出了,但点击窗口上任意一个控件,都会触发那个错误。也就是说,只要调用dll函数后,点击控件就会出现错误。这也解释了为什么listbox会自动触发该错误的原因了:因为listbox的add方法自动触发了SelectChange方法,而后面的控件通过点击等操作也触发了相应的方法。所以得出一个猜测结论:在WPF中调用C/C++
DLL后,触发任何一个控件方法,都会弹出堆栈溢出的错误。
4.这个猜测有没有较好的根据,此时还没法说明。但仔细考虑一番,MS会允许这样大一个问题出现在WPF中么?会不会是DLL本身的问题,其实这个才应该是最先考虑并确认的,只是第一个控制台程序的成功运行麻痹了人的思想。回过头来,解决这个问题:
从网上搜索了几个c/c++的DLL,调用运行一番,发现有的不能运行,有的没有问题,有的弹出相同的错误。这个结果很不理想,因为并没有说明该问题是否由DLL本身引出。同时由于没有自己编写DLL的经验,不能确认猜测是否准确。于是这个计划破产。
5.综合了种种想法,最后给出一个猜测:DLL本身可能对程序造成了一定的影响,同时,WPF的某些机制与winform不同,这个机制也是引起问题可疑因素。不过由于我自身也是初学WPF,对WPF没有深入的了解,所以没法分析出究竟什么样的机制产生了这个差异。但有一点我比较清楚:WPF的消息与线程机制与winform不同,这给我刚开始从winform转WPF开发带来了不小的麻烦。于是大胆假设一番,从我较熟悉的地方入手。
6.这时候,搜寻资料就比较有目的性了:“WPF调用DLL产生溢出”,“WPF与Winform调用DLL“等关键词作为搜索目标。果然,在众多结果中找到了点击打开链接。
我WPF (.net4.0)下,调用C++的DLL的方法 ,这个DLL的方法是:
1 | //蜂吟 |
其作用是访问读卡器上的一个蜂吟器发声,我是把这段代码放到一个类AceReader里,
调用方式:AceReader.API_ControlBuzzer(0,0,10,1,buf)
现在的问题是调用完成时很正常,完成后,我在wpf点击其它任何按钮的动作或打开窗口等等commond动作时都报错:System.StackOverflowException
为此,我请一朋友用C++写了一个测试 a.DLL,里面有一个方法:
1 | [ ] |
这个方法的作用是弹出一个提示信息,没有访问硬件,这样就没有问题
其本上可以认为:访问硬件后,就会出现System.StackOverflowException
关于上面提到的C++dll : “mi.dll”
我在winForm (.net4.0) 下作同样的测试,都很正常。也就是winform上一切正常,没有问题
我在WPF(.net4.0) 一旦访问了”mi.dll”里的访问硬件的相关方法,访问完成后,再做其它动作时就会出现System.StackOverflowException
是什么原因?
这个描述与我遇到的问题完全一致,在问题解答中,我也找到了答案:
由于WPF和以前的Winform使用了不同的消息机制和线程机制,所以在你的dll中抛出的大量异常都被推到了Window的Frames中去了,导致溢出。对于这个问题,你只有尝试去从你的C++ Dll 入手去改了,在你的C++ dll中一定要把异常处理好,线程要做到和被PInvoke调用的代码独立。否则,Native代码被调用到了我们的主线程中,他甚至可以在主线程修改值,会导致我们的WPF 线程中Frame无法受到 WPF 的 Dispatcher 控制。
同时,提供了一个临时的解决办法:调把用代码放入另外一个线程
1 |
|
由于手头只有封装好的DLL,所以只好使用这个临时方法。
1 | private void button1_Click(object sender, RoutedEventArgs e) |
这段代码的问题相信大家一眼就能看出了,就是控件跨越了线程
这个问题相对来说就好解决了,可以参考一下点击打开链接,我这里为了省事,将代码这样写
1 |
|
终于,问题算是解决了
三、总结
该问题确实由DLL与WPF消息与线程共同引起的,在编写DLL时需要按照严格规范处理好异常。如果是引用他人DLL开发,可以将调用函数放在另一个线程中,避免和主线程冲突。同时,该问题也反映出我对于WPF的了解甚少,基础不扎实,还需要进一步学习,对于分析问题方面,思维应该更敏捷,眼光应该更敏锐。
DLL下载:http://115.com/file/dpzcx057 由于商业用途,未经公司许可,不能把DLL操作放出,只提供文章中例子的操作,望见谅。
WPF调用C/C++DLL出现异常:System.StackOverflowException
https://wurang.net/wpf_invoke_the_c_dll_stackoverflowexception/