有時候,我們需要通過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有該書的電子版下載。