有时候,我们需要通过INTERNET下载文件,在文件下载的过程中,我们还需要处理其他事情,为此,我们需要把文件下载的工作放到一个线程中来实现。为了体现面向对象程序设计的封装性,我们最好把线程封装为一个类,以后我们每需要一个这样的线程,我们只要实例化该类的一个对象就可以了。如果自己实现这样的类,应该有一定的难度,所幸的事,MFC为我们提供了这样的一个基类,CWinThread类,我们只要以它为基类派生一个符合我们需要的子类就可以了。
现在,我有几个文件要下载,要下载的文件保存在一个链表中,下载的过程中,要向界面显示错误或下载进度等一些信息。我的程序是一个向导程序,向导的CPropertySheet类的子类CWizardSheet管理着各个属性页,因此,我将保存着将要下载的文件信息的链表保存为CWizardSheet类的成员,要下载的时候,我将一个CWizardSheet的指针传给线程,线程读取它的链表成员,顺序下载其中的文件,下载的过程中,将发一些消息来向主窗口通知主窗口文件下载的状态信息。
线程类定义如下:
#if !defined(AFX_CDownloadThread_H__1238AC8E_F397_4E8C_AC43_50170B789119__INCLUDED_)
#define AFX_CDownloadThread_H__1238AC8E_F397_4E8C_AC43_50170B789119__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
// CDownloadThread.h : header file
//
#include "common.h"
class CWizardSheet;
/////////////////////////////////////////////////////////////////////////////
// CDownloadThread thread
class CDownloadThread : public CWinThread
{
DECLARE_DYNCREATE(CDownloadThread)
private:
CDownloadThread(); // 定义为私有的是为了防止外部定义线程数组和调用该无参构造函数
public:
//传给构造函数一个父窗口的指针
CDownloadThread(CWizardSheet* pWizSheet, AFX_THREADPROC pfnThreadProc);
// Attributes
public:
// Operations
public:
static UINT ThreadFunc(LPVOID param); //线程函数,将调用下面的函数来下载文件
void GetFile(NewModuleInfo& newModuleInfo); //真正实现文件下载的函数
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CDownloadThread)
public:
virtual BOOL InitInstance();
virtual int ExitInstance();
//}}AFX_VIRTUAL
// Implementation
public:
virtual ~CDownloadThread();
// Generated message map functions
//{{AFX_MSG(CDownloadThread)
// NOTE - the ClassWizard will add and remove member functions here.
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
private:
CWizardSheet* m_pWizSheet; //保存父窗口指针用
CString m_saveDir; //文件保存路径
};
/////////////////////////////////////////////////////////////////////////////
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
#endif // !defined(AFX_CDownloadThread_H__1238AC8E_F397_4E8C_AC43_50170B789119__INCLUDED_)
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
下载线程的实现如下:
// CDownloadThread.cpp : implementation file
//
#include "stdafx.h"
#include <afxinet.h>
#include "Wiz2.h"
#include "DownloadThread.h"
#include "WizardSheet.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
//extern WM_MSG_PROGRS = WM_USER+100;
/////////////////////////////////////////////////////////////////////////////
// CDownloadThread
IMPLEMENT_DYNCREATE(CDownloadThread, CWinThread)
CDownloadThread::CDownloadThread()
{
}
CDownloadThread::CDownloadThread(
CWizardSheet* pWizSheet,
AFX_THREADPROC pfnThreadProc):CWinThread(pfnThreadProc, NULL)
{
m_pWizSheet = pWizSheet; //保存父窗口指针
m_saveDir = pWizSheet->GetSaveDir();//获得文件要保存的路径
m_bAutoDelete = TRUE;//线程结束将自动清除
m_pThreadParams = this; //线程参数设为自己
}
CDownloadThread::~CDownloadThread()
{
}
BOOL CDownloadThread::InitInstance()
{
// TODO: perform and per-thread initialization here
return TRUE;
}
int CDownloadThread::ExitInstance()
{
// TODO: perform any per-thread cleanup here
return CWinThread::ExitInstance();
}
BEGIN_MESSAGE_MAP(CDownloadThread, CWinThread)
//{{AFX_MSG_MAP(CDownloadThread)
// NOTE - the ClassWizard will add and remove mapping macros here.
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CDownloadThread message handlers
UINT CDownloadThread::ThreadFunc(LPVOID param)
{
CDownloadThread* pthread = (CDownloadThread*)param;
NewModuleInfoList list = pthread->m_pWizSheet->GetNewModuleInfoList();//获得要下载的文件列表
HWND hWnd = AfxGetApp()->GetMainWnd()->m_hWnd;
NewModuleInfoList::iterator item;
WaitForSingleObject(pthread->m_pWizSheet->m_hNewModulesListMutex, INFINITE);
//顺序下载各个文件
for (item=list.begin(); item!=list.end(); item++)
{
if (item->isFullFile==FALSE && item->isSelected==TRUE)
pthread->GetFile(*item);
}
SendMessage(hWnd, WM_MSG_DOWNMODULE, NOTIFY_MSG, DOWNLOAD_FINISH);//发下载完成消息,
ReleaseMutex(pthread->m_pWizSheet->m_hNewModulesListMutex);
return 0;
}
//This is the real thread function, it will open the url the
//caller supply, download the file specify by the url to the
//specify dir, and send the statistics messge to the UI to
//tell the user the progress
void CDownloadThread::GetFile(NewModuleInfo& newModuleInfo)
{
HWND hWnd = AfxGetApp()->GetMainWnd()->m_hWnd;
CInternetSession session;
CStdioFile* pFile = NULL;
CStdioFile localFile;
session.SetOption(INTERNET_OPTION_CONNECT_RETRIES, 10);
try
{
pFile = session.OpenURL(newModuleInfo.URL, //打开URL
1,
INTERNET_FLAG_TRANSFER_BINARY
| INTERNET_FLAG_RELOAD
| INTERNET_FLAG_DONT_CACHE
| INTERNET_FLAG_PASSIVE);
if (pFile == NULL)
{
//send connect failed message, notify the user an error encounter
ReleaseMutex(m_pWizSheet->m_hNewModulesListMutex);
SendMessage(hWnd, WM_MSG_DOWNMODULE, NOTIFY_MSG, CONNECT_FAILED);
return;
}
CString strLen;
DWORD len = 0;
if (newModuleInfo.serverType == 0)//如果是HTTP,则得到文件长度
{
if (((CHttpFile*)pFile)->QueryInfo(HTTP_QUERY_CONTENT_LENGTH,strLen) != 0)
{
len = atol(strLen);
}
}
else if (newModuleInfo.serverType == 1)
{
len = pFile->GetLength(); //如果是FTP,则得到文件长度,但实际调试发现FTP文件无法这样得到长度
}
if (len < newModuleInfo.info.length)
{
len = newModuleInfo.info.length;
}
//Open a local file to store the internet file打开本地文件用于保存
CString fileName = m_saveDir + "//" +newModuleInfo.info.fileName;
BOOL bIsOK = localFile.Open(fileName,
CFile::modeCreate | CFile::modeWrite
| CFile::shareDenyWrite| CFile::typeBinary);
if (bIsOK == FALSE)
{
//Send open file failed message, notify the user an error encounter
ReleaseMutex(m_pWizSheet->m_hNewModulesListMutex);
SendMessage(hWnd, WM_MSG_DOWNMODULE, NOTIFY_MSG, OPENFILE_FAILED);
session.Close();
return;
}
localFile.Seek(0, CFile::begin);
//Read the internet file to buffer and write to local file
char buf[4096];
memset(buf, 0, 4096);
DWORD nTotal = 0;
double dper = 0;
int per = 0;
UINT ret = 0;
CTime startTime = CTime::GetCurrentTime();
CTimeSpan usedTime;
do{
ret = pFile->Read(buf, 4096);
localFile.Write(buf, ret); //保存文件
nTotal += ret;
usedTime = CTime::GetCurrentTime()-startTime;//计算用了多少时间
Statics* staticsPack = new Statics;
staticsPack->length = len;
staticsPack->dwTotal = nTotal;
staticsPack->usedTime = usedTime.GetTotalSeconds();
//send the statistics infomation to the UI to show the progress
SendMessage(hWnd, WM_MSG_DOWNMODULE, STATICS_MSG, (LPARAM)staticsPack);
if (len > 0)
{
dper = ((double)nTotal)/((double)len);
per = 100*dper;
if (per<5)
per = 5;
//Send the download percentage to UI to show the progress
SendMessage(hWnd, WM_MSG_DOWNMODULE, PROGRS_MSG, per);//发送下载进度给主窗口
}
}while (ret != 0);
//Do some cleaning
if (pFile != NULL)
{
pFile->Close();
delete pFile;
pFile = NULL;
}
if (localFile.m_hFile != CFile::hFileNull)
{
localFile.Close();
}
}
catch(CInternetException InetEx)
{
ReleaseMutex(m_pWizSheet->m_hNewModulesListMutex);
SendMessage(hWnd, WM_MSG_DOWNMODULE, NOTIFY_MSG, NETWORK_EXCEPTION);
pFile = NULL;
session.Close();
return;
}
catch(CFileException fileEx)
{
ReleaseMutex(m_pWizSheet->m_hNewModulesListMutex);
SendMessage(hWnd, WM_MSG_DOWNMODULE, NOTIFY_MSG, FILE_EXCEPTION);
if (pFile != NULL)
{
pFile->Close();
pFile = NULL;
}
session.Close();
return;
}
catch(...)
{
ReleaseMutex(m_pWizSheet->m_hNewModulesListMutex);
SendMessage(hWnd, WM_MSG_DOWNMODULE, NOTIFY_MSG, NETWORK_EXCEPTION);
pFile = NULL;
session.Close();
return;
}
newModuleInfo.isAvailable = TRUE;
//Send download finished msg to UI to tell the user
//that a module has been successfully downloaded.
CString* msg = new CString(newModuleInfo.info.fileName);
ReleaseMutex(m_pWizSheet->m_hNewModulesListMutex);
SendMessage(hWnd, WM_MSG_DOWNMODULE, MODULENAME_MSG, (LPARAM)msg);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
我可以在主窗口类中这样使用该线程类:
void CWizardSheet::DownloadModules()
{
if (!m_NewModuleInfoList.empty())//要下载的文件链表不为空
{
CDownloadThread* pThread =
new CDownloadThread(this, CDownloadThread::ThreadFunc);//生成一个线程类对象
pThread->CreateThread(); //启动线程
}
}
整个类实现也不难,你可以按你的需要,修改线程构造时接受的参数。
有的些细节是要注意的,从上面的代码看,要把一个类的成员函数做为参数,该成员必须是静态成员。从代码你也看出,这有一个技巧,我们把线程类本身的this指针做为参数传给线程,这样的做法的优点在于我们在线程函数ThreadFunc()中调用线程类的任何一个成员函数和成员变量,实现了良好的封装性。该类的实现是参考《WIN32多线程程序设计(侯捷 译)》286页(此书绝对值得收藏)的一个例子来实现的,本人的描述可能有不清楚的地方,不懂的要以参阅此书,在www.infoxa.com有该书的电子版下载。