Web版PACS开发纪要一:关闭动态库中创建的线程

最近的项目是关于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)中创建的功能动态库。

三、问题分析

演示工程的对话框界面如下左图所示:

Web版PACS开发纪要一:关闭动态库中创建的线程_第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应用程序。

Web版PACS开发纪要一:关闭动态库中创建的线程_第2张图片

单击“开始监测”后,加载了功能动态库FoldWatcher20130525.dll,如下图深色条纹所示:

Web版PACS开发纪要一:关闭动态库中创建的线程_第3张图片

在ProcessExplorer工具的进程窗口(Process)中,单击打开FoldWatcherMain.exe应用程序,选择Thread选项卡,可以看到当先运行的线程

Web版PACS开发纪要一:关闭动态库中创建的线程_第4张图片

TID=6260,是调用动态库函数的线程;TID=5656的是MFC主线程;TID=5944的是动态库函数中创建的监控文件夹的线程,该线程与完成端口绑定。手动向监控的文件夹中拷贝bmp文件,可以看到5944线程开始运行,CPU利用提高,证明正在转移bmp文件。

单击“停止监测”后,看到TID=6260的动态库调用线程关闭。

Web版PACS开发纪要一:关闭动态库中创建的线程_第5张图片

但是文件夹监控子线程TID=5944依然存在。

【分析原因】:在MFC主进程中加载了功能动态库,然后创建了一个工作线程调用动态库中的函数,然而该库函数中又创建了一个子线程。该子线程与MFC中创建的线程是否有关系?是否是父子关系?是否关闭了MFC中的工作线程,库函数创建的子线程也同样会关闭呢?答案是否定的。一旦工作线程中创建完成“子线程”后,两者就没有直接的关联关系了,也就是说该“子线程”不隶属于工作线程。因此上面的尝试结果失败的。那么是否有方法来关闭工作线程中创建的“子线程”呢?

【尝试二】

既然工作线程是调用的动态库中的函数,“子线程”是利用动态库函数来创建的,那么是否卸载了动态库,该线程就会停止呢?经过测试,发现卸载动态库后,由动态库创建的“子线程”并未关闭。

【尝试三】

将动态库中创建的线程句柄导出,然后在MFC对话框程序中进行关闭。由于句柄属于内核对象,本人涉猎有限,未找到导出句柄的有效方法,此尝试失败。

【尝试四】

再次分析原本的动态库,发现问题主要出在文件夹监控函数WatchDirectory中存在while(1);语句,导致程序无法退出。那么是否可以将动态库的函数进行重新构造,增加StartWatch()、StopWatch()函数来控制线程的运行和关闭呢?答案是肯定的。(具体的修改代码见博文后的链接2)此时单击“开始监测”,程序运行正常,依然能够实时监控文件夹,转移bmp文件。ProcessExplorer中的观察结果如下:

Web版PACS开发纪要一:关闭动态库中创建的线程_第6张图片

当单击“停止监测”按钮后,工作线程终止,结果图如下:

Web版PACS开发纪要一:关闭动态库中创建的线程_第7张图片

至此完成了将文件夹监控功能动态库添加到基于MFC对话框的工程中的任务。 

五、总结:

整个问题的解决耗费了近一天的时间,究其原因主要有两个

1)对问题的“病灶”诊断不清,犹如解决方案中的尝试一所示,并未发现问题的根本在于功能动态库中存在着while(1);这样的阻塞函数,简单的通过工作线程并不能解决while(1);带来的阻塞;

2)对动态库与线程的关系、动态库导出函数和变量,以及线程之间传递句柄基础知识掌握不牢固,尝试二和尝试三浪费了不少时间,无功而返。对此有待进一步的补充学习,希望后期能够将尝试二、尝试三顺利完成,使其达到同样的目的。



尝试一的工程代码:

http://pan.baidu.com/share/link?shareid=468593&uk=1226245216

尝试四的工程代码:

http://pan.baidu.com/share/link?shareid=468626&uk=1226245216



Author:zssure

Date    :2013-05-25

E-mail :[email protected]






你可能感兴趣的:(多线程,C++,c,DCMTK,CxImage)