最近的项目是关于B/S架构下的Web版PACS开发,为了缩短研发周期,采用了该领域主流的两大开源库:DCMTK和CxImage。但是由于项目初期对CxImage开源库的编译问题,导致该开源库在MFC下无法正常运行,因此决定将功能函数在控制台下完成,然后封装成动态链接库(XXX.dll),并加载到MFC工程中。下面是对“急救车上的多种医疗设备数据采集客户端”开发中遇到的问题进行的简略记录,主要分为以下几个部分:
1)问题的背景
2)演示工程构建
3)问题分析
4)解决方案
5)总结
一、问题的背景
由于牵扯到过多的医学方面的专业知识,具体背景就不细说了,简而言之,就是一句话——将控制台下完成的功能函数封装成动态库来应用到MFC工程中,完成期望的功能。
二、演示工程构建
原始的项目中运用了DCMTK和CxImage开源库中的大量的函数,来实现医学图像的解析和传送。如果直接以原工程作为演示工程,过于繁琐,各个库不能独立运行给阅读者带来不便。因此下面搭建了一个简单的测试工程。该工程分为两部分:
1)功能实现部分:FoldWatch20130525,其实现的主要功能与原工程类似(只是省略了对医学图像处理的部分),即监控配置文件foldwatch.ini中给定的源文件夹([Directory]节中指定的值),将其下的所有bmp图像转存到目标文件夹中([DestinationDirectory]节中指定的值)。演示工程中利用了完成端口实现该功能【注】:该转移bmp文件的功能是可以直接添加到2)中的基于MFC对话框的工程中的,此处为了演示开发项目中遇到的问题,模拟DCMTK+CxImage开源库的状态,假定该功能不能在MFC工程下直接运行,需要将其编译成动态库形式方可。2)调用显示部分:FoldWatchMain,一个简单的基于对话框的MFC工程。通过按钮来调用1)中创建的功能动态库。
三、问题分析
演示工程的对话框界面如下左图所示:
单击“开始监测”按钮,进入到响应函数中加载动态库中,并调用FoldWatch函数。随后界面处于未响应状态(如上图右)。如果工程出现这种情况,必然会影响到用户体验(在医疗领域就不单单是影响用户体验这么简单了,时间就是生命!!!!!)
【分析原因】发现在文件转移工程动态库中,有一个循环等待的过程while(1);其目的就是为了能够实时持续的监控源文件夹。那么是否可以在主体工程中创建一个线程,然后让该线程调用动态库中的功能函数来实现文件夹的监控,待需要停止时,直接关闭主体进程中的工作线程就可以了。于是进行了以下尝试:
四、解决方案
【尝试一】
在“开始监测”按钮响应函数中利用CreateThread创建一个工作线程,然后调用dll中的工作函数FoldWatch。修改代码如下(完整的工程代码见博文后的链接1):
hInst=LoadLibrary(_T("FoldWatcher20130525.dll")); if(hInst) { typedef int (*pFunc)(); pFunc fun=NULL; fun=(pFunc)GetProcAddress(hInst,_T("FoldWatch")); if(fun) { //直接调用动态库中的函数 //(*fun)(); //进行工作线程创建,然后在线程中调用动态库的函数 hThread=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)fun,NULL,0,NULL); } }
如此,创建线程后直接将控制权返回给MFC对话框,解决了对话框无响应的问题。且能够顺利的完成文件夹监控任务。但是无意间又发现了一个问题:单击“停止监测”后,依然能够实现bmp文件转移,难道文件夹监控线程并未停止?为了进一步确认监控线程是否停止,利用微软提供的ProcessExplorer工具来进行观察。
首先运行MFC对话框主程序,可以在ProcessExplorer中看到FoldWatcherMain.exe应用程序。
单击“开始监测”后,加载了功能动态库FoldWatcher20130525.dll,如下图深色条纹所示:
在ProcessExplorer工具的进程窗口(Process)中,单击打开FoldWatcherMain.exe应用程序,选择Thread选项卡,可以看到当先运行的线程
TID=6260,是调用动态库函数的线程;TID=5656的是MFC主线程;TID=5944的是动态库函数中创建的监控文件夹的线程,该线程与完成端口绑定。手动向监控的文件夹中拷贝bmp文件,可以看到5944线程开始运行,CPU利用提高,证明正在转移bmp文件。
单击“停止监测”后,看到TID=6260的动态库调用线程关闭。
但是文件夹监控子线程TID=5944依然存在。
【分析原因】:在MFC主进程中加载了功能动态库,然后创建了一个工作线程调用动态库中的函数,然而该库函数中又创建了一个子线程。该子线程与MFC中创建的线程是否有关系?是否是父子关系?是否关闭了MFC中的工作线程,库函数创建的子线程也同样会关闭呢?答案是否定的。一旦工作线程中创建完成“子线程”后,两者就没有直接的关联关系了,也就是说该“子线程”不隶属于工作线程。因此上面的尝试结果失败的。那么是否有方法来关闭工作线程中创建的“子线程”呢?
【尝试二】
既然工作线程是调用的动态库中的函数,“子线程”是利用动态库函数来创建的,那么是否卸载了动态库,该线程就会停止呢?经过测试,发现卸载动态库后,由动态库创建的“子线程”并未关闭。
【尝试三】
将动态库中创建的线程句柄导出,然后在MFC对话框程序中进行关闭。由于句柄属于内核对象,本人涉猎有限,未找到导出句柄的有效方法,此尝试失败。
【尝试四】
再次分析原本的动态库,发现问题主要出在文件夹监控函数WatchDirectory中存在while(1);语句,导致程序无法退出。那么是否可以将动态库的函数进行重新构造,增加StartWatch()、StopWatch()函数来控制线程的运行和关闭呢?答案是肯定的。(具体的修改代码见博文后的链接2)此时单击“开始监测”,程序运行正常,依然能够实时监控文件夹,转移bmp文件。ProcessExplorer中的观察结果如下:
当单击“停止监测”按钮后,工作线程终止,结果图如下:
至此完成了将文件夹监控功能动态库添加到基于MFC对话框的工程中的任务。
五、总结:
整个问题的解决耗费了近一天的时间,究其原因主要有两个:
1)对问题的“病灶”诊断不清,犹如解决方案中的尝试一所示,并未发现问题的根本在于功能动态库中存在着while(1);这样的阻塞函数,简单的通过工作线程并不能解决while(1);带来的阻塞;
2)对动态库与线程的关系、动态库导出函数和变量,以及线程之间传递句柄基础知识掌握不牢固,尝试二和尝试三浪费了不少时间,无功而返。对此有待进一步的补充学习,希望后期能够将尝试二、尝试三顺利完成,使其达到同样的目的。
尝试一的工程代码:尝试四的工程代码:
Author:zssure
Date :2013-05-25
E-mail :zssure@163.com