MFC多线程的创建
1.MFC多线程简介
MFC对多线程进行了一层简单的封装,在Visual C++中每个线程都是从CWinThread类继承而来的。每一个应用程序的执行都有一个主线程,这个主线程也是从CWinThread类继承而来的。可以利用CWinThread对象创建应用程序执行的其它线程。
MFC用CWinThread对象来表示所有线程。利用MFC可以创建两种线程,分别称之为工作者线程和用户界面线程。二者的主要区别在于工作者线程没有消息循环,而用户界面线程有自己的消息队列和消息循环。工作者线程没有消息机制,通常用来执行后台计算和维护任务,如冗长的计算过程,打印机的后台打印等。用户界面线程一般用于处理独立于其他线程执行之外的用户输入,响应用户及系统所产生的事件和消息等。但对于Win32的API编程而言,这两种线程是没有区别的,它们都只需线程的启动地址即可启动线程来执行任务。
2.MFC多线程基础
为了深入了解MFC下创建线程的方法,我们先深入学习一下CWinThread类。CWinThread类在MFC类结构中的位置如下图所示:
图1:CWinThread类在mfc类结构中的位置
详细的MFC类结构图请参考MSDN:
首先看一下类CWinThread的声明。类CWinThread的声明在afxwin.h中:
/
Class CWinThread :publicCCmdTarget
{
DECLARE_DYNAMIC(CWinThread)
public:
//构造函数
CWinThread();
//用来具体创建线程的函数
BOOL CreateThread(DWORDdwCreateFlags = 0,UINTnStackSize = 0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs= NULL);
// Attributes
//主窗口,(通常使用AfxGetApp()->m_pMainWnd可以得到)
CWnd* m_pMainWnd; // main window(usually same AfxGetApp()->m_pMainWnd)
//活动窗口,可能不是主窗口
CWnd* m_pActiveWnd; // active mainwindow (may not be m_pMainWnd)
BOOL m_bAutoDelete; // enables'delete this' after thread termination
// only valid while running
//该线程的句柄
HANDLE m_hThread; // thisthread's HANDLE
operator HANDLE() const;
//该线程的ID
DWORD m_nThreadID; // thisthread's ID
//线程优先级
int GetThreadPriority();
BOOL SetThreadPriority(int nPriority);
// Operations
//挂起线程
DWORD SuspendThread();
//启动线程
DWORD ResumeThread();
//发送线程消息
BOOL PostThreadMessage(UINT message, WPARAM wParam, LPARAM lParam);
// Overridables
// 线程初始化,每个应用程序都可以重载该函数
virtual BOOL InitInstance();
// running and idle processing
virtual int Run();
virtual BOOL PreTranslateMessage(MSG*pMsg);
virtual BOOL PumpMessage(); // low level message pump
virtual BOOL OnIdle(LONGlCount);// return TRUEif more idle processing
virtual BOOL IsIdleMessage(MSG*pMsg); // checks for special messages
// thread termination
virtual int ExitInstance(); //default will 'delete this'
// Advanced: exception handling
virtual LRESULT ProcessWndProcException(CException*e,constMSG*pMsg);
// Advanced: handling messages sent to message filter hook
virtual BOOL ProcessMessageFilter(intcode,LPMSGlpMsg);
// Advanced: virtual access to m_pMainWnd
virtual CWnd* GetMainWnd();
// Implementation
public:
virtual ~CWinThread();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext&dc)const;
#endif
void CommonConstruct();
virtual void Delete();
// 'delete this' only if m_bAutoDelete == TRUE
public:
// constructor used by implementation of AfxBeginThread
CWinThread(AFX_THREADPROCpfnThreadProc,LPVOIDpParam);
// valid after construction
LPVOID m_pThreadParams;// generic parameters passed to starting function
AFX_THREADPROC m_pfnThreadProc;
// set after OLE is initialized
void (AFXAPI*m_lpfnOleTermOrFreeLib)(BOOL,BOOL);
COleMessageFilter* m_pMessageFilter;
protected:
BOOL DispatchThreadMessageEx(MSG* msg); // helper
void DispatchThreadMessage(MSG* msg); // obsolete
};
/
有些函数是不是很眼熟呀,在前面的文章中已经介绍和使用过啦。MFC类就是这样的,它无非就是简单封装一些API函数,并添加一些自己的函数而构成的。不用MFC我们照样可以编写很优秀的程序,MFC的宗旨就是简化程序设计,让你可以很容易入门和简单的使用,也催生了大量的程序员。但对喜欢刨根问底的朋友却是一道很厚的墙。
下面几个函数是多线程编程中经常用到的几个全局函数:
//创建工作线程
CWinThread* AFXAPIAfxBeginThread(
AFX_THREADPROC pfnThreadProc,//线程函数
LPVOID pParam,//传给线程函数的参数
int nPriority =THREAD_PRIORITY_NORMAL,//线程的优先级
UINT nStackSize = 0,//堆栈大小
DWORD dwCreateFlags = 0,//创建起始状态标志
LPSECURITY_ATTRIBUTES lpSecurityAttrs= NULL//线程的安全属性
);
//创建用户界面线程
CWinThread* AFXAPIAfxBeginThread(
CRuntimeClass* pThreadClass,//从CWinThread派生的类的RUNTIME_CLASS
int nPriority =THREAD_PRIORITY_NORMAL,//线程的优先级
UINT nStackSize = 0,// 堆栈大小
DWORD dwCreateFlags = 0,// 创建起始状态标志
LPSECURITY_ATTRIBUTESlpSecurityAttrs =NULL//线程的安全属性
);
//获取线程对象
CWinThread* AFXAPIAfxGetThread();
//获取当前消息
MSG* AFXAPIAfxGetCurrentMessage();
//结束线程执行
void AFXAPIAfxEndThread(UINTnExitCode,BOOLbDelete =TRUE);
//初始化线程
void AFXAPIAfxInitThread();
//终止线程执行
void AFXAPIAfxTermThread(HINSTANCEhInstTerm =NULL);
仔细阅读以上类的说明能学到不少东西:
(1) CWinThead类通过CreateThread()成员函数来创建线程,这个函数的声明和Win32APICreateThread()的参数相似
(2)每个函数在运行后都有一个句柄和ID号。
(3)通过设置属性m_bAutoDelete,可决定线程在运行结束后线程对象是否自动删除,它的访问权限是public型的,可以直接进行设置。一般情况下,线程对象的生命周期和线程的生命周期一致。如果你想改变线程对象的生命周期,可设置该属性为FALSE。
(4)MFC下的多线程仍然支持线程的挂起和启动。
(5)具有PreTranslateMessage()、PumpMessage()等函数,供用户界面线程的消息机制使用。
在MFC中实际上是调用AfxBeginThread()函数来创建线程的。那么为什么不直接使用::CreateThread()或_beginthread()函数来创建线程呢?只要看一下CWinThread类的实现中的相关代码就明白了。在thrdcore.cpp文件中的相关代码如下:
==========================================================================================================
CWinThread* AFXAPI AfxGetThread()
{
// check for current thread in module thread state
AFX_MODULE_THREAD_STATE* pState= AfxGetModuleThreadState();
CWinThread* pThread= pState->m_pCurrentWinThread;
return pThread;
}
MSG* AFXAPI AfxGetCurrentMessage()
{
_AFX_THREAD_STATE* pState= AfxGetThreadState();
ASSERT(pState);
return &(pState->m_msgCur);
}
CWinThread* AFXAPI AfxBeginThread(AFX_THREADPROC pfnThreadProc, LPVOID pParam,int nPriority,UINT nStackSize, DWORD dwCreateFlags,
LPSECURITY_ATTRIBUTESlpSecurityAttrs)
{
#ifndef _MT
pfnThreadProc;
pParam;
nPriority;
nStackSize;
dwCreateFlags;
lpSecurityAttrs;
return NULL;
#else
ASSERT(pfnThreadProc!=NULL);
CWinThread* pThread=DEBUG_NEWCWinThread(pfnThreadProc,pParam);
ASSERT_VALID(pThread);
if (!pThread->CreateThread(dwCreateFlags|CREATE_SUSPENDED,nStackSize,
lpSecurityAttrs))
{
pThread->Delete();
return NULL;
}
VERIFY(pThread->SetThreadPriority(nPriority));
if (!(dwCreateFlags&CREATE_SUSPENDED))
VERIFY(pThread->ResumeThread() != (DWORD)-1);
return pThread;
#endif //!_MT)
}
CWinThread* AFXAPI AfxBeginThread(CRuntimeClass*pThreadClass,
int nPriority, UINT nStackSize,DWORD dwCreateFlags,
LPSECURITY_ATTRIBUTES lpSecurityAttrs)
{
#ifndef _MT
pThreadClass;
nPriority;
nStackSize;
dwCreateFlags;
lpSecurityAttrs;
return NULL;
#else
ASSERT(pThreadClass!=NULL);
ASSERT(pThreadClass->IsDerivedFrom(RUNTIME_CLASS(CWinThread)));
CWinThread* pThread= (CWinThread*)pThreadClass->CreateObject();
if (pThread ==NULL)
AfxThrowMemoryException();
ASSERT_VALID(pThread);
pThread->m_pThreadParams=NULL;
if (!pThread->CreateThread(dwCreateFlags|CREATE_SUSPENDED,nStackSize,
lpSecurityAttrs))
{
pThread->Delete();
return NULL;
}
VERIFY(pThread->SetThreadPriority(nPriority));
if (!(dwCreateFlags&CREATE_SUSPENDED))
{
ENSURE(pThread->ResumeThread() != (DWORD)-1);
}
return pThread;
#endif //!_MT
}
void AFXAPI AfxEndThread(UINTnExitCode,BOOLbDelete)
{
#ifndef _MT
nExitCode;
bDelete;
#else
// remove current CWinThread object from memory
AFX_MODULE_THREAD_STATE* pState= AfxGetModuleThreadState();
CWinThread* pThread= pState->m_pCurrentWinThread;
if (pThread !=NULL)
{
ASSERT_VALID(pThread);
ASSERT(pThread!=AfxGetApp());
// cleanup OLE if required
if (pThread->m_lpfnOleTermOrFreeLib !=NULL)
(*pThread->m_lpfnOleTermOrFreeLib)(TRUE,FALSE);
if (bDelete)
pThread->Delete();
pState->m_pCurrentWinThread=NULL;
}
// allow cleanup of any thread local objects
AfxTermThread();
// allow C-runtime to cleanup, and exit the thread
_endthreadex(nExitCode);
#endif //!_MT
}
/
// Global functions forthread initialization and thread cleanup
LRESULT CALLBACK _AfxMsgFilterHook(intcode,WPARAMwParam,LPARAMlParam);
void AFXAPI AfxInitThread()
{
if (!afxContextIsDLL)
{
// set message filter proc
_AFX_THREAD_STATE* pThreadState= AfxGetThreadState();
ASSERT(pThreadState->m_hHookOldMsgFilter ==NULL);
pThreadState->m_hHookOldMsgFilter= ::SetWindowsHookEx(WH_MSGFILTER,
_AfxMsgFilterHook, NULL,::GetCurrentThreadId());
}
}
extern CThreadSlotData* _afxThreadData;
void AFXAPI AfxTermThread(HINSTANCEhInstTerm)
{
try
{
#ifdef _DEBUG
// check for missing AfxLockTempMap calls
if (AfxGetModuleThreadState()->m_nTempMapLock != 0)
{
TRACE(traceAppMsg,0,"Warning: Temp map lock count non-zero(%ld).\n",
AfxGetModuleThreadState()->m_nTempMapLock);
}
#endif
AfxLockTempMaps();
AfxUnlockTempMaps(-1);
}
catch( CException*e )
{
e->Delete();
}
try
{
// cleanup thread local tooltip window
if (hInstTerm ==NULL)
{
AFX_MODULE_THREAD_STATE*pModuleThreadState=AfxGetModuleThreadState();
if ((pModuleThreadState!=NULL) &&
(pModuleThreadState->m_pToolTip!=NULL))
{
pModuleThreadState->m_pToolTip->DestroyWindow();
delete pModuleThreadState->m_pToolTip;
pModuleThreadState->m_pToolTip=NULL;
}
}
}
catch( CException*e )
{
e->Delete();
}
try
{
// cleanup the rest of the thread local data
if (_afxThreadData!=NULL)
_afxThreadData->DeleteValues(hInstTerm,FALSE);
}
catch( CException*e )
{
e->Delete();
}
}
/
// CWinThread construction
CWinThread::CWinThread(AFX_THREADPROCpfnThreadProc,LPVOIDpParam)
{
m_pfnThreadProc = pfnThreadProc;
m_pThreadParams = pParam;
CommonConstruct();
}
CWinThread::CWinThread()
{
m_pThreadParams = NULL;
m_pfnThreadProc = NULL;
CommonConstruct();
}
void CWinThread::CommonConstruct()
{
m_pMainWnd = NULL;
m_pActiveWnd = NULL;
// no HTHREAD until it is created
m_hThread = NULL;
m_nThreadID = 0;
_AFX_THREAD_STATE* pState= AfxGetThreadState();
// initialize message pump
#ifdef _DEBUG
pState->m_nDisablePumpCount= 0;
#endif
pState->m_msgCur.message =WM_NULL;
pState->m_nMsgLast=WM_NULL;
::GetCursorPos(&(pState->m_ptCursorLast));
// most threads are deleted when not needed
m_bAutoDelete = TRUE;
// initialize OLE state
m_pMessageFilter = NULL;
m_lpfnOleTermOrFreeLib = NULL;
}
CWinThread::~CWinThread()
{
// free thread object
if (m_hThread !=NULL)
CloseHandle(m_hThread);
// cleanup module state
AFX_MODULE_THREAD_STATE* pState= AfxGetModuleThreadState();
if (pState->m_pCurrentWinThread ==this)
pState->m_pCurrentWinThread=NULL;
}
BOOL CWinThread::CreateThread(DWORDdwCreateFlags,UINTnStackSize,
LPSECURITY_ATTRIBUTES lpSecurityAttrs)
{
#ifndef _MT
dwCreateFlags;
nStackSize;
lpSecurityAttrs;
return FALSE;
#else
ENSURE(m_hThread==NULL); // already created?
// setup startup structure for thread initialization
_AFX_THREAD_STARTUP startup;memset(&startup,0,sizeof(startup));
startup.pThreadState=AfxGetThreadState();
startup.pThread=this;
startup.hEvent= ::CreateEvent(NULL,TRUE,FALSE,NULL);
startup.hEvent2= ::CreateEvent(NULL,TRUE,FALSE,NULL);
startup.dwCreateFlags=dwCreateFlags;
if (startup.hEvent ==NULL||startup.hEvent2==NULL)
{
TRACE(traceAppMsg,0,"Warning: CreateEvent failed inCWinThread::CreateThread.\n");
if (startup.hEvent !=NULL)
::CloseHandle(startup.hEvent);
if (startup.hEvent2 !=NULL)
::CloseHandle(startup.hEvent2);
return FALSE;
}
// create the thread (it may or may not start to run)
m_hThread = (HANDLE)(ULONG_PTR)_beginthreadex(lpSecurityAttrs,nStackSize,
&_AfxThreadEntry, &startup,dwCreateFlags |CREATE_SUSPENDED,(UINT*)&m_nThreadID);
if (m_hThread ==NULL)
{
::CloseHandle(startup.hEvent);
::CloseHandle(startup.hEvent2);
return FALSE;
}
// start the thread just for MFC initialization
VERIFY(ResumeThread()!= (DWORD)-1);
VERIFY(::WaitForSingleObject(startup.hEvent,INFINITE) ==WAIT_OBJECT_0);
::CloseHandle(startup.hEvent);
// if created suspended, suspend it until resume threadwakes it up
if (dwCreateFlags&CREATE_SUSPENDED)
VERIFY(::SuspendThread(m_hThread) != (DWORD)-1);
// if error during startup, shut things down
if (startup.bError)
{
VERIFY(::WaitForSingleObject(m_hThread,INFINITE)==WAIT_OBJECT_0);
::CloseHandle(m_hThread);
m_hThread = NULL;
::CloseHandle(startup.hEvent2);
return FALSE;
}
// allow thread to continue, once resumed (it may alreadybe resumed)
VERIFY(::SetEvent(startup.hEvent2));
return TRUE;
#endif //!_MT
}
void CWinThread::Delete()
{
// delete thread if it is auto-deleting
if (m_bAutoDelete)
delete this;
}
/
在创建一个新的线程时,不必直接创建线程对象,因为线程对象是由全局函数AxCreateThread()自动产生的。只要首先定义一个CWinThread类指针,然后调用全局函数AxCreateThread()来产生一个新的线程对象,并调用线程类的CreateThread()成员函数来具体创建线程,最后将新的线程对象的指针返回。应该将其存在CWinThread变量中,以便能够进一步控制线程。
AxCreateThread()函数和CWinThread:: CreateThread()函数并不是简单地对_beginthreadex()函数进行了一下封装,它还做了一些应用程序框架所需的内部数据的初始化工作,并保证使用正确的C运行库版本,因为在两个函数一开始就要检查环境参数_MT是否已经定义。而且在创建线程过程中还进行了很多检查和测试,保证能够正确产生新的线程,同时在线程创建失败时正确地释放掉已经分配的资源。
注意事项:
(1)一般情况下,推荐使用AfxBeginThread()来一次性地创建并启动一个线程,但是也可以通过两步法来创建线程:首先创建CWinThread类的一个对象,然后调用该对象的成员函数CreateThread()来启动该线程。
(2)MFC支持两类多线程,即工作线程和用户界面线程。用户界面线程经常重载InitInstance()和ExitInstance()函数,用以控制用户界面线程实例的初始化和必要的清理工作。工作者线程一般不使用。
3.线程函数
(1)工作线程的线程函数:从AfxBeginThread()函数的参数可以看出:
AFX_THREADPROC pfnThreadProc,//线程函数
LPVOID pParam, //传给线程函数的参数
其中AFX_THREADPROC为一个宏,其定义如下:
typedef UINT(AFX_CDECL *AFX_THREADPROC)(LPVOID);
从以上语句,可以得到工作线程的线程函数的形式为:
UINT ThreadFunc(LPVOID pParm);
ThreadFunc()函数应返回一个UINT类型的值,用以指明该函数结束的原因。一般情况下,返回0表明执行成功。
pParam:传递给线程函数的一个32位参数,执行函数将用某种方式解释该值。它可以是数值,或是指向一个结构的指针,甚至可以被忽略。
(2)用户界面线程的线程函数:从AfxBeginThread()函数的参数可以看出:
CRuntimeClass* pThreadClass,//从CWinThread派生的类的RUNTIME_CLASS
即pThreadClass 是指向 CWinThread 的一个导出类的运行时类对象的指针,该导出类定义了被创建的用户界面线程的启动、退出等。
有了上面的基础,下面来学习线程的创建。
4.工作线程的创建
工作线程没有消息机制,经常用来完成一些后台工作,如计算、打印、等待、循环等,这样用户就不必因为计算机在从事繁杂而耗时的工作而等待。创建一个工作线程相对比较简单。
我们用一个实例来演示工作线程的创建。查找N(很大)以内的素数,为了演示效果,N的取值很大(比如:10000等),并在主界面实时动态显示处理结果,并显示处理进度等。
先说下素数的定义:素数又称质数,指在一个大于1的中,除了1和此自身外,不能被其他自然数的数。
一个数 n 如果是素数,那么它的所有的因子不超过sqrt(n)(即n的平方),那么我们可以用这个性质用判断一个数是否是素数。在此不讨论该算法的复杂度,只是演示效果,望大牛们不要拍砖哈。
主要程序:
//.h文件
UINT ThreadFunc(LPVOID pParm);//线程函数的定义
bool IsPrime(UINT Number ); //素数判断
struct threadInfo
{
unsigned int nRange;//范围
HWND hWnd;//主窗口句柄,用于消息的发送
};
private:
CWinThread *m_pThread;
//.cpp文件
//开始
void CAfxBeginThread1Dlg::OnBnClickedButtonCal()
{
// TODO: 在此添加控件通知处理程序代码
m_nNum = 1;
m_List.ResetContent();
UpdateData(TRUE);
m_stTip.SetWindowText("");
if(m_nRange<2)
{
AfxMessageBox("查找范围必须是大于的整数!",MB_OK|MB_ICONERROR);
return;
}
GetDlgItem(IDC_BUTTON_CAL)->EnableWindow(FALSE);
m_Progress.SetPos(0);
m_Progress.SetRange32(0,m_nRange);
m_Info.nRange =m_nRange;
m_Info.hWnd =m_hWnd;
m_pThread = AfxBeginThread(ThreadFunc,&m_Info);
if (m_pThread ==NULL)
{
AfxMessageBox("启动失败!",MB_OK|MB_ICONERROR);
return;
}
}
UINT ThreadFunc(LPVOID pParm)
{
threadInfo *pInfo=(threadInfo*)pParm;
bool bResult = false;
for (inti =2;i<=pInfo->nRange;i++)
{
bResult = IsPrime(i);
::SendMessage(pInfo->hWnd,WM_INFO,bResult,i);
//Sleep(1000);
}
//结束
::SendMessage(pInfo->hWnd,WM_INFO,0,-1);
return 0;
}
//素数判断
bool IsPrime(UINT nNumber )
{
//定理:如果n不是素数, 则n有满足<d<=sqrt(n)的一个因子d.if(nNumber < 2)returnfalse;
if(nNumber == 2)returntrue;
for (inti = 3;i*i<=nNumber;i += 2)
{
if(nNumber%i == 0)
return false;
}
return true;
}
//显示消息处理函数
LRESULT CAfxBeginThread1Dlg::OnMyInfo(WPARAMwParam,LPARAMlParam)
{
if (wParam == 0&&lParam == -1)
{
//结束m_stTip.SetWindowText("完成");
GetDlgItem(IDC_BUTTON_CAL)->EnableWindow(TRUE);
}
else
{
//是素数m_Progress.SetPos(lParam);
CString str;
str.Format("%d",lParam);
m_stTip.SetWindowText(str);
if (wParam)
{
str.Format("%d个",m_nNum);
GetDlgItem(IDC_STATIC1)->SetWindowText(str);
str.Format("第%d个:%d",m_nNum++,lParam);
m_List.AddString(str);
}
}
return 0;
}
结果预览:
工程×××地址:
欢迎大家修改和指正。
注意事项:
(1)该方式的多线程和前面的_beginthreadex方式很想象,一般用于后台的工作执行。
(2)注意信息的实时显示。
(3)创建线程时多参数的传递,此处采用结构体的方式。
5.用户界面线程的创建
由于用户界面线程含有自己的消息循环,可以处理Windows消息,并可创建和管理诸如窗口和控件等用户界面元素。因此,这种线程较工作线程更为复杂。
创建用户界面线程的起点是从MFC的CWinThread类派生一个定制的线程类,而不是调用AfxBeginThead()函数。定制的线程类必须重载InitInstance()函数,该函数用来执行初始化任务,在创建线程时系统将调用InitInstance()函数。最好还要重载ExitInstane()函数,该函数是InitInstance()函数的对应,MFC在删除线程对象之前会调用ExitInstane()函数,以便线程能够在结束后清除自身。
用户界面线程的创建有两种方法,方法一是首先从CWinThread类派生一个类(必须要用宏DECLARE_DYNCREATE和IMPLEMENT_DYNCREATE对该类进行声明和实现),然后调用AfxBeginThead()创建CWinThread派生类的对象进行初始化,启动线程运行。方法二是先通过构造函数创建类CWinThread的一个对象,然后由程序员调用函数::CreateThread来启动线程。通常CWinThread类的对象在该线程的生存期结束时将自动终止,如果程序员希望自己来控制,则需要将m_bAutoDelete设为FALSE。这样在线程终止之后,CWinThread类对象仍然存在,此时需要手动删除CWinThread对象。
5.1用户界面线程实例1
该实例主要演示创建用户界面线程的第一种方法,先从CWinThread类派生一个类,然后调用AfxBeginThead()创建CWinThread派生类的对象进行初始化,启动线程运行。该实例主要演示文件的移动,并实施显示操作的进度。设计到两个线程:文件的读写采用工作线程,进度显示采用用户界面线程。
主要程序:
//.h文件
define STEPLEN 1024*64 //文件一次读写的进步k
UINT ThreadFunc(LPVOID pParm);//线程函数的定义:文件复制线程
CWinThread *pUIThread;//界面线程,用于进度的显示
CWinThread *pThread; //工作线程,用于文件的复制
CString m_szSrcPath; //源文件路径
CString m_szDesPath; //目标文件路径
//.CPP文件
//文件复制模块:
void CAfxBeginThead2Dlg::OnBnClickedButtonCopy()
{
// TODO: 在此添加控件通知处理程序代码
if (m_szDesPath.IsEmpty() ||m_szSrcPath.IsEmpty())
{
AfxMessageBox("源文件路径和目标文件路径不能为空!",MB_OK|MB_ICONERROR);
return;
}
//
GetDlgItem(IDC_BUTTON_COPY)->EnableWindow(FALSE);
//创建用户界面线程,用于进度的显示
pUIThread=AfxBeginThread(RUNTIME_CLASS(CcbCopyFile));
if (pUIThread ==NULL)
{
AfxMessageBox("用户界面线程启动失败!",MB_OK|MB_ICONERROR);
GetDlgItem(IDC_BUTTON_COPY)->EnableWindow(TRUE);
return;
}
//传递参数
pUIThread->PostThreadMessage(WM_THREADINFO,0,(LPARAM)m_szSrcPath.GetBuffer(0));
pUIThread->PostThreadMessage(WM_THREADINFO,1,(LPARAM)m_szDesPath.GetBuffer(0));
//创建工作线程,用于文件的复制
pThread=AfxBeginThread(ThreadFunc,this);
if (pThread ==NULL)
{
AfxMessageBox("工作线程启动失败!",MB_OK|MB_ICONERROR);
GetDlgItem(IDC_BUTTON_COPY)->EnableWindow(TRUE);
return;
}
SetTimer(1,1000,NULL);//速度统计
SetTimer(2,100,NULL);//操作计时
m_stTip.SetWindowText("进行中...");
pUIThread->PostThreadMessage(WM_THREADINFO,2,1);//启动
}
//文件复制线程
UINT ThreadFunc(LPVOID pParm)
{
CAfxBeginThead2Dlg*pInfo=(CAfxBeginThead2Dlg*)pParm;
//打开源文件,得到文件的大小,并一块一块的读取
CFile ReadFile;
BOOL bOpen = ReadFile.Open(pInfo->m_szSrcPath,CFile::modeRead);
if(!bOpen)
{
AfxMessageBox(pInfo->m_szSrcPath +_T(" :文件打开失败!"),MB_ICONERROR|MB_OK);
pInfo->m_stTip.SetWindowText("复制【失败】!");
pInfo->GetDlgItem(IDC_BUTTON_COPY)->EnableWindow(TRUE);
return 1;
}
//得到文件的大小,用于计算进度
DWORD dwTotalSize= ReadFile.GetLength();
DWORD dwCompleteSize= 0;//已完成的大小
//计算文件去读的步长
DWORD dwStep = STEPLEN > dwTotalSize? dwTotalSize : STEPLEN;
//数据缓冲区
char *pBuf =newchar[dwStep+1];
memset(pBuf,0x00,dwStep+1);
DWORD dwRead = dwStep;
//创建目标文件,若目标文件存在则清空
CFile WriteFile;
bOpen = WriteFile.Open( pInfo->m_szDesPath,CFile::modeCreate |CFile::modeWrite);
if(!bOpen)
{
AfxMessageBox(pInfo->m_szDesPath +_T(" :文件打开失败!"),MB_ICONERROR|MB_OK);
pInfo->m_stTip.SetWindowText("复制【失败】!");
pInfo->GetDlgItem(IDC_BUTTON_COPY)->EnableWindow(TRUE);
return 2;
}
//文件的复制:从源文件去读取数据,并写入到目标文件中
while( (dwRead =ReadFile.Read(pBuf,dwStep))> 0 )
{
//读取源文件,一次一块//将读取的数据写入目标文件中
WriteFile.Write(pBuf,dwRead);
dwCompleteSize += dwRead;
pInfo->m_nSpeed+=dwRead;
//更新进度
pInfo->pUIThread->PostThreadMessage(WM_THREADINFO,3, (LPARAM)int((dwCompleteSize*1.0/dwTotalSize)*100));
}
//完成
delete pBuf;
//关闭文件
ReadFile.Close();
WriteFile.Close();
//发送结束消息,用于关闭进度显示模块
pInfo->pUIThread->PostThreadMessage(WM_THREADINFO,10, 1);
pInfo->KillTimer(1);
pInfo->KillTimer(2);
pInfo->m_stTip.SetWindowText("复制完成!");
//使能复制按钮,以便可以继续进行
pInfo->GetDlgItem(IDC_BUTTON_COPY)->EnableWindow(TRUE);
return 0;
}
//用户界面线程类:派生于CWinThread类
//文件复制模块:头文件
class CcbCopyFile : publicCWinThread
{
DECLARE_DYNCREATE(CcbCopyFile)
protected:
CcbCopyFile(); // 动态创建所使用的受保护的构造函数
virtual ~CcbCopyFile();
CString m_szSrcPath,m_szDesPath;
public:
CCopyFileDlg *m_pProgressDlg;//进度界面
virtual BOOL InitInstance();
virtual int ExitInstance();
afx_msg void OnThreadInfo(WPARAMwParam,LPARAMlParam);
protected:
DECLARE_MESSAGE_MAP()
};
===================================================================
// cbCopyFile.cpp : 实现文件
//
#include "stdafx.h"
#include "AfxBeginThead2.h"
#include "cbCopyFile.h"
// CcbCopyFile
IMPLEMENT_DYNCREATE(CcbCopyFile,CWinThread)
CcbCopyFile::CcbCopyFile()
{
m_pProgressDlg = NULL;
}
CcbCopyFile::~CcbCopyFile()
{
}
BOOL CcbCopyFile::InitInstance()
{
// TODO: 在此执行任意逐线程初始化
return TRUE;
}
int CcbCopyFile::ExitInstance()
{
// TODO: 在此执行任意逐线程清理
return CWinThread::ExitInstance();
}
BEGIN_MESSAGE_MAP(CcbCopyFile,CWinThread)
ON_THREAD_MESSAGE(WM_THREADINFO,&CcbCopyFile::OnThreadInfo)
END_MESSAGE_MAP()
// CcbCopyFile 消息处理程序
//显示消息处理函数
void CcbCopyFile::OnThreadInfo(WPARAMwParam,LPARAMlParam)
{
if (wParam == 0)
{
//源文件路径参数m_szSrcPath.Format("%s",lParam);
//AfxMessageBox(m_szSrcPath);
}
else if (wParam == 1)
{
//目标文件路径参数m_szDesPath.Format("%s",lParam);
//AfxMessageBox(m_szDesPath);
}
else if (wParam == 2)
{
//启动m_pProgressDlg = newCCopyFileDlg;
m_pProgressDlg->Create(IDD_DIALOG1);
m_pProgressDlg->m_szSrcPath=m_szSrcPath;
m_pProgressDlg->m_szDesPath=m_szDesPath;
m_pProgressDlg->UpdateData(FALSE);
m_pProgressDlg->ShowWindow(TRUE);
}
else if (wParam == 3)
{
//进度m_pProgressDlg->m_Progress.SetPos(lParam);
}
else if (wParam == 4)
{
//速度m_pProgressDlg->UpdateSpeed(lParam);
}
else if (wParam == 5)
{
//时间float *p = (float *)lParam;
m_pProgressDlg->UpdateTime(*p);
}
else
{
//完成m_pProgressDlg->OnCancel();
}
//return 0;
}
结果预览:
工程×××地址:
欢迎大家修改和指正。
注意事项:
(1)该实例源码比较多,所以上面只贴出了重要的部分,建议下载整个工程源码下来学习和研究(地址见上)。
(2)该实例主要演示了文件复制,采用了工作线程和用户界面线程相配合。提供了实时的进度显示、实时复制速度显示和时间显示。功能还是比较实用,只要稍加修改做成一个复制模块(dll)用于自己的程序中,还是非常实用。对不喜欢采用CopyFile/CopyFileEx函数的朋友是一个不错的选择。
(3)注意在工作线程中,响应消息的方式,消息映射和平时的不一样,主要采用ON_THREAD_MESSAGE,而不是ON _MESSAGE。
(4)注意工作线程创建中,传递给线程函数的参数为this,即 pThread=AfxBeginThread(ThreadFunc,this);//即传递的是对象的地址。这对于要传递很多参数的是一个不错的选择。
消息响应函数的格式为:void OnThreadInfo(WPARAMwParam,LPARAM lParam);
注意返回类型必须为void。
(5)该实例还有一个不完善的地方,就是复制过程中按“取消”的处理,该实例中还没有实现,望有心的朋友可以实现它,大家多多思考和动手,对你一定有很好的帮助的。
(6)用户界面线程和程序的主线程很相像,能处理各种消息。
5.2用户界面线程实例2
该实例主要演示创建用户界面线程的第二种方法,先从CWinThread类派生一个类,然后调用CreateThread创建CWinThread派生类的对象进行初始化,启动线程运行。该实例主要演示手气测试,设计到两个用户界面线程:数字输入线程和结果判断线程。
主要程序:
//主线程
CInputThread *m_pInputThread;//数字输入线程
CResultThread *m_pResultThread;//结果判断线程
//测试
void CAfxBeginThead3Dlg::OnBnClickedButtonTest()
{
// TODO: 在此添加控件通知处理程序代码
m_pInputThread = newCInputThread;
m_pInputThread->CreateThread(CREATE_SUSPENDED);
m_pResultThread = newCResultThread;
m_pResultThread->CreateThread(CREATE_SUSPENDED);
m_pResultThread->m_hWnd=m_hWnd;
m_pInputThread->m_pResultThread=m_pResultThread;
m_pInputThread->ResumeThread();//运行
m_pResultThread->ResumeThread();
m_nTimes++;
// UpdateInfo();
}
LRESULT CAfxBeginThead3Dlg::OnMessage(WPARAMwParam,LPARAMlParam)
{
if(lParam == 1)
{
//成功一次m_nSucess++;
}
UpdateInfo();
if(wParam == 1)
{
//再测一次OnBnClickedButtonTest();
}
return 0;
}
// 更新信息
void CAfxBeginThead3Dlg::UpdateInfo(void)
{
CString str;
str.Format("统计:%d/%d",m_nSucess,m_nTimes);
GetDlgItem(IDC_STATIC1)->SetWindowText(str);
}
//数字输入线程
CResultThread *m_pResultThread;//结果判断线程
CInputDlg m_InputDlg;//输入对话框
BOOL CInputThread::InitInstance()
{
// TODO: 在此执行任意逐线程初始化
m_InputDlg.DoModal();
return TRUE;
}
int CInputThread::Run()
{
// TODO: 在此添加专用代码和/或调用基类
m_pResultThread->PostThreadMessage(WM_THREADMESSAGE,0,m_InputDlg.m_nInput);
return CWinThread::Run();
}
//结果判断线程
#define WM_THREADMESSAGE WM_USER+100 //输入数字线程发送的消息,用于结果判断
#define WM_MESSAGEWM_USER+200//向主线程发送消息,用于返回相关信息
BEGIN_MESSAGE_MAP(CResultThread,CWinThread)
ON_THREAD_MESSAGE(WM_THREADMESSAGE,&CResultThread::OnThreadMessage)
END_MESSAGE_MAP()
//显示消息处理函数
void CResultThread::OnThreadMessage(WPARAMwParam,LPARAMlParam)
{
if (wParam == 0)
{
bool bSucess = false;
CString str;
int nLuckNum = GetLuckNum();
if (lParam ==nLuckNum)
{
bSucess = true;
str.Format("恭喜,手气不错哦!\n=============\n你测试的数字为【%d】\n当前的幸运数字为【%d】\n再测一次吧?",lParam,nLuckNum);
}
else
{
bSucess = false;
str.Format("很遗憾,差一点点哦!\n=============\n你测试的数字为【%d】\n当前的幸运数字为【%d】\n再测一次吧?",lParam,nLuckNum);
}
if(AfxMessageBox(str,MB_YESNO|MB_DEFBUTTON1|MB_ICONINFORMATION)==IDYES)
{
::SendMessage(m_hWnd,WM_MESSAGE,1,bSucess);//发送消息
}
else
{
::SendMessage(m_hWnd,WM_MESSAGE,0,bSucess);//发送消息
}
}
else
{
;
}
}
// 得到幸运数字
int CResultThread::GetLuckNum(void)
{
srand((unsigned)time(NULL));
return rand()%10;
}
结果预览:
工程×××地址:
欢迎大家修改和指正。
注意事项:
(1)从CWinThread类派生的类的构造函数和解析函数默认是protected,要采用第二种方法则要修改为public,否则将出错。
(2)注意线程参数的传递方法,和前面不一样的。即创建线程时先设置为CREATE_SUSPENDED(挂起),然后设置相关参数,然后再启动线程,具体可以参考程序。
m_pResultThread= new CResultThread;
m_pResultThread->CreateThread(CREATE_SUSPENDED);//创建线程时先挂起
m_pResultThread->m_hWnd =m_hWnd;//然后相关设置参数
m_pResultThread->ResumeThread();//再运行
(3)注意CWinThread派生类中Run()函数的重载。仔细想想下面的程序的执行原理,为什么对话框结束后就会执行此处。如果将对话框设置为非模式对话框,还会正常吗?
int CInputThread::Run()
{
// TODO: 在此添加专用代码和/或调用基类
m_pResultThread->PostThreadMessage(WM_THREADMESSAGE,0,m_InputDlg.m_nInput);
return CWinThread::Run();
}
===============================================================
转载请标明出处,谢谢!
===============================================================