一.问题的提出
微软资源管理器一般来说应该算是一个非常优秀的软件,但是其微软资源的图像浏览功能应该来说是有些问题的(可能是我的windows版本比较老(windows2000),且不说JPEG的BUG,就他采用的单线程读图应该就算是不合理之处,为什么这么说,我们可以做个试验,首先构造一个BMP图像,图像应尽可能大一点,我选择了6146*2048的24位色的图片,大约36M多,当点击此图片后,系统将非常缓慢的显示此图片的预览图,而且无法快速切换至其他文件(可能是我的机器太慢了),很明显微软在这里没有采用多线程处理,系统将把图像的所有数据读入内存后才进行处理显示,这就容易造成读取大图像文件的死机.因为在读取文件的过程中用户是无法中断的. 如何能快速切换文件呢? 一般对于一个需要较长时间处理数据的程序,用户可能希望在处理过程中提前中断当前的数据操作,我们有几种方式处理:
1.采用单线程处理方法:
这种方法一般采用的方法是在数据处理的过程中加入
MSG message;
if (::PeekMessage(&message, NULL, 0, 0, PM_REMOVE)) {
::TranslateMessage(&message);
::DispatchMessage(&message);
}
等语句,如果发生用户中断或其他消息,则系统执行用户中断或其他消息相应的消息处理程序,消息处理完成后,程序再返回原来数据处理过程继续执行相关的代码. 这种方式的优点就是编程简单,只要在程序中设置一个全局变量,在用户中断消息中改变这个变量,数据处理时判断如果变量改变则退出处理即可.在一般情况下这种办法还是比较有效的,但是这种方法也有些问题,比如在这种情况下,你的鼠标按住窗口不停的拖动,系统就不会运行数据处理直到你松开鼠标按钮.由于数据处理很容易被系统的消息所干扰,采用这种方法在实际程序的运行过程中会使人感觉程序不是很流畅.
2.采用多线程处理方法 这里我们可以将文件分为界面线程和工作线程,工作线程负责读取文件并发送读取进度和读取内容到界面线程,而界面线程负责界面的显示和用户切换文件后,结束工作线程.需要指出的是在目前的单CPU+WINDOWS的环境下采用多线程进行数据,并不能提高数据的处理速度(超线程CPU除外),他所能提高的是界面的反应速度,即界面的友好程度.当然这种友好程度也会被一些其他程序过多的占用CPU所干扰.
二.如何使用多线程
1.demo界面设置:
为了能很好的模拟测试多线程绘图显示程序,我们建立了一个简单的demo程序,
先使用mfc向导,建立一个对话框程序,并增加全局变量CDib m_dib,对话框上建立三
个按钮和一个进度条,按钮1的功能为将c:/test1.bmp读入m_dib,按钮2的功能为将
c:/test2.bmp读入m_dib,按钮3的功能为终止m_dib的读入(关于CDib将在以后详细
说明),进度条用于显示当前读入数据的进度,于是按钮中的代码如下:
void CDemoDlg::OnButton1()
{
m_dib.LoadBmp ("c://test1.bmp");
}
void CDemoDlg::OnButton2()
{
m_dib.LoadBmp ("c://test2.bmp");
}
void CDemoDlg::OnButton3()
{
m_dib.DoStop(); //停止CDib的读入操作
}
但是,m_dib读入功能为多线程读取函数那有可能在读取test1.bmp时,程序要求读取test2.bmp,
所以按钮中的代码修改如下:
void CDemoDlg::OnButton1()
{
m_dib.DoStop(); //这也可以直接加入LoadBmp中
m_dib.LoadBmp ("c://test1.bmp");
}
void CDemoDlg::OnButton2()
{
m_dib.DoStop();
m_dib.LoadBmp("c://test2.bmp");
}
当然我们还要显示m_Dib,为了显示m_Dib我们对OnPaint作如下修改:
void CDemoDlg::OnPaint()
{
CPaintDC dc(this);
CRect clientRc;
GetClientRect(&clientRc); //取得窗体大小
CDC memDC1; //我们将读入的图像置于memDC1中
CDC memDC2; //memDC2设置背景图像
CBitmap m_bitmap;
CBitmap * m_pOldBitmap;
CBitmap * m_pOldBitmapA;
memDC1.CreateCompatibleDC(&dc);
memDC2.CreateCompatibleDC(&dc);
m_bitmap.CreateCompatibleBitmap(&dc,clientRc.Width() , clientRc.Height());
m_pOldBitmapA = memDC2.SelectObject(&m_bitmap);
m_pOldBitmap = memDC1.SelectObject(&m_Dib); //m_Dib为全局变量
memDC2.BitBlt(0,0, clientRc.Width(), clientRc.Height(),
&memDC1,0,0, SRCCOPY); //将读入的图像复制到背景图中
dc.BitBlt(0,0, clientRc.Width(), clientRc.Height(),
&memDC2,0,0, SRCCOPY); //显示当前图像
memDC2.SelectObject(m_pOldBitmapA );
memDC1.SelectObject(m_pOldBitmap );
}
我们建立两个消息函数负责子线程向界面线程报告完成情况和状态,
ON_MESSAGE(WM_SETPOS, ON_WM_SETPOS) //负责子线程向主线程报告完成进度
ON_MESSAGE(WM_INTERRUPT, ON_WM_INTERRUPT) //负责子线程向主线程报告是否中断
在CDib中我们每次读入图像的一行,然后发送WM_SETPOS消息给界面线
程,界面则刷新当前图像和进度条,在ON_WM_SETPOS中,wParam为bmp总行
数,lParam为目前处理的行数,为了简化程序我们只处理自底向上(即bmp图
像高度>0)的图像.
void CDemoDlg::ON_WM_SETPOS(WPARAM wParam, LPARAM lParam)
{
CRect clientRc;
GetClientRect(&clientRc); //取得当前窗体大小
CProgressCtrl *cw;
cw=(class CProgressCtrl *)GetDlgItem(IDC_PROGRESS1);
cw->SetRange (0,100); //设置进度条
float l;
if (wParam!=0)
l=(float) lParam/(float)wParam*100;
else
l=0;
cw->SetPos ((int)l); //更新进度条
CRect rc;
rc.SetRect (0,wParam-lParam,clientRc.Width(),wParam-lParam+1);
InvalidateRect(&rc,false); //刷新当前行
}
至此我们的界面程序基本完成.
2.CDib设置:
下面我们进行CDib的构建,为了方便图像的显示处理我们继承CBitmap构建CDib,
为了解说方便,我们只处理24位色图,这里我们给出CDib基本结构,其他部分读者可以
自由扩充.
class CDib : public CBitmap
{
public:
CDib(); //构造函数
virtual ~CDib(); //析构函数
BYTE * m_lpBits; //数据指针,指向cbitmap的数据区
LPBITMAPINFOHEADER m_lpBmih;//图像头指针
HANDLE hMutex; //互斥变量
HANDLE hStopEvent; //线程读到此信号,立即退出现有的操作
CWnd * m_Wnd; //用于子线程发送消息界面指针
CWinThread *m_cAniThread; //多线程中,子线程对象
virtualvoid DoStop(); //用于界面线程中停止当前线程
BOOL LoadBmp(LPCTSTR lpszPathName, CDC* pDC = NULL);
//由bmp读入图像
static UINT ThreadProcRead(LPVOID parm);
//多线程函数读入图像
.
.
.
protected:
BOOL AttachDib(HGDIOBJ hObject);
//Attach Object
BOOL DeleteDib(); //delete object
void Initialize(); //设置图像头等操作
.
.
.
}