windows核心編程課程實踐—多執行緒文件搜索器(MFC介面)
課上完了連老師見都沒見一面QAQ….記錄一下該小項目
效果如下:
1、實現文件搜索功能,並封裝為類
1)首先是文件搜索類Rapidfinder的構造函數和析構函數和文件資訊初始化函數和文件路徑規格化函數;重新初始化文件函數
CRapidFinder::CRapidFinder(HWND MainHwnd, CString MatchName, CString MatchDir)
{
m_hThrds = NULL;
InitializeCriticalSection(&m_gCriticalSection);
ThreadSet();
FinderSet(MainHwnd, MatchName, MatchDir);
}
CRapidFinder::~CRapidFinder()
{
DeleteCriticalSection(&m_gCriticalSection);
if (m_hExitEvent)CloseHandle(m_hExitEvent);
}
//初始化搜索文件資訊
void CRapidFinder::FinderSet(HWND MainHwnd, CString MatchName, CString MatchDir)
{
FinderReset();
m_MainhWnd = MainHwnd;
if (!MatchName.IsEmpty())
{
MatchName.MakeUpper();
MatchDir.MakeUpper();//makerupper()將CString字元轉化為一個大寫的字元串。
m_strFileName = MatchName;
m_Option |= OP_FILENAME;//按位或後賦值,OP_FILENAME為0x01
}
m_strFileDir = MatchDir;
m_hExitEvent = CreateEvent(NULL, TRUE, FALSE, L"RAPIDFINDER");//創建事件,手動設置訊號,初始化為未激發
CreatePathList();//規格化文件路徑
}
//初始化創建執行緒的資訊
void CRapidFinder::ThreadSet(LONG MaxThreadCount, int priority)
{
m_Priority = priority;
m_ActiveCount = m_MaxThreadCount = MaxThreadCount;
if (m_hThrds)delete[]m_hThrds;//釋放執行緒句柄數組
m_hThrds = new HANDLE[MaxThreadCount];
}
//重新初始化文件
void CRapidFinder::FinderReset()
{
m_lpText = NULL;
m_NextVal = NULL;
m_Option = 0;
m_strFileName = "";
m_strFileDir = "";
m_DirList.RemoveAll();
ResetEvent(m_hExitEvent);//重置為未激發態
m_ExitCode = ERR;
}
//規格化文件路徑
void CRapidFinder::CreatePathList()
{
Trim(m_strFileDir);
if (m_strFileDir.IsEmpty())
{
SetErrNo(0); return;
}
int np = 0, op = 0;
CString str;
//當有多個路徑同時選擇時(即用;分隔)
while ((np = m_strFileDir.Find(';', op)) != -1)
{
str = Trim(m_strFileDir.Mid(op, np - op));
str.TrimRight('\\');//消除「\\」
if (!str.IsEmpty())m_DirList.AddTail((LPCTSTR)str);
op = np + 1;
}
str = Trim(m_strFileDir.Mid(op, m_strFileDir.GetLength() - op));
str.TrimRight('\\');
if (!str.IsEmpty())m_DirList.AddTail((LPCTSTR)str);
}
構造函數用來初始化臨界區,初始化執行緒資訊(執行緒優先順序、最大執行緒數、執行緒句柄數組),初始化搜索文件資訊(目錄,文件名);
析構函數退出臨界區,關閉事件句柄;
2)文件搜索函數的第二部分就是:判斷搜索方式的MatchProc函數(是按文件名還是特定字元串);查詢指定文件里是否包含特定字元的FindTextFromFile()函數;預先處理特定字元串的CalNextPos()函數
//判斷搜索文件時的方式,是按文件名還是包含字元
BOOL __fastcall CRapidFinder::MatchProc(CString& findpath)
{
CString fname(findpath);
int pos = fname.ReverseFind('\\');
fname.MakeUpper();
if ((m_Option & OP_FILENAME) && (fname.Find(m_strFileName, pos + 1) == -1))return false;
if ((m_Option & OP_FILETEXT) && !FindTextFromFile(findpath))return false;
return true;
}
//查詢文件里是否包含我們指定的字元
BOOL __fastcall CRapidFinder::FindTextFromFile(CString& findpath)
{
CFile file;
if (NULL == file.Open(findpath.GetBuffer(0), CFile::modeRead | CFile::typeBinary))return false;
BYTE* Buffer = new BYTE[512];
int nRead;
if (!(nRead = file.Read(Buffer, 512))) { file.Close(); return false; }//如果讀回位元組數為0,則關閉Cfile
int i = 0, j = 0;
while (j < m_TextSize)
if (j == -1 || Buffer[i] == m_lpText[j])//判斷是否包含我們指定的字元
{
if (++i == nRead)//如果讀回位元組數為1,則只需要一輪循環集合
{
/*PeekAndPump();*/
if (!(nRead = file.Read(Buffer, 512))) { file.Close(); return false; }
i = 0;
}
j++;
}
else j = m_NextVal[j];
file.Close();
return true;
}
//決定是否從下一位字元再開始匹配。比如在「aab」中搜索「ab」,當在「aab」中有重複的字元時給每一位字元設置一個是否進行到下一位匹配的標誌
int* CRapidFinder::CalNextPos()
{
int j = 0, k = -1;
m_NextVal[0] = -1;
while (j < m_TextSize - 1)
if ((k == -1) || (m_lpText[j] == m_lpText[k]))
{
j++; k++;
if (m_lpText[j] != m_lpText[k])m_NextVal[j] = k;
else m_NextVal[j] = m_NextVal[k];
}
else k = m_NextVal[k];
return m_NextVal;
}
3)第三部分就是多執行緒的處理,包括主執行緒函數MainThreadProc()、子執行緒函數ThreadProc()、啟動子執行緒函數StartFinder()、暫停函數PauseFinder()、停止函數StopFinder()、暫停後繼續函數ResumeFinder();
//主執行緒函數,為每個執行緒分配搜索任務
DWORD WINAPI CRapidFinder::MainThreadProc(LPVOID lpParam)
{
//新建一個finder對象來存儲文件資訊
CRapidFinder* finder = (CRapidFinder*)lpParam;
resume:
//Reset,重新開始
ResetEvent(finder->m_hExitEvent);//重置m_hExitEvent,使之處於未激發態
finder->m_ExitCode = ERR;
finder->m_ActiveCount = finder->m_MaxThreadCount;
PostMessage(finder->m_MainhWnd, WM_THREADCOUNT, (WPARAM)(finder->m_ActiveCount), NULL);
//啟動子執行緒
for (int i = 0; i < finder->m_MaxThreadCount; i++)
finder->m_hThrds[i] = StartThread(ThreadProc, lpParam);
WaitForMultipleObjects(finder->m_MaxThreadCount, finder->m_hThrds, TRUE, INFINITE);//等待所有執行緒返回
for (int i = 0; i < finder->m_MaxThreadCount; i++)
CloseHandle(finder->m_hThrds[i]); //關閉所有執行緒句柄
//查看執行緒退出原因
switch (finder->m_ExitCode)
{
case PAUSE:SendMessage(finder->m_MainhWnd, WM_THREADPAUSE, NULL, NULL);
ResetEvent(finder->m_hExitEvent);//重置為未激發態,為暫停後重新開始做準備
//等待繼續查找
WaitForSingleObject(finder->m_hExitEvent, INFINITE);
goto resume;
//下面兩個類似,一個是直接退出,一個是執行完退出
case EXIT:SendMessage(finder->m_MainhWnd, WM_THREADEXIT, EXIT, NULL);
finder->FinderReset();
break;;
case STOP:SendMessage(finder->m_MainhWnd, WM_THREADEXIT, STOP, NULL);
finder->FinderReset();
break;
default:finder->SetErrNo(1); return 0;
}
return 1;
}
//啟動子執行緒的函數
HANDLE CRapidFinder::StartThread(LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParam)
{
DWORD ThreadID;//執行緒id
CRapidFinder* finder = (CRapidFinder*)lpParam;//文件搜索類對象
HANDLE htmp = CreateThread(NULL, 0, lpStartAddress, lpParam, CREATE_SUSPENDED, &ThreadID);
BOOL re = SetThreadPriority(htmp, finder->m_Priority);
ASSERT(re);
ResumeThread(htmp);
return htmp;
}
上述兩個函數相互配合,創建指定數目的子執行緒(在StartFinder()函數初始化子執行緒資訊,創建子執行緒);主執行緒使用WaitForMultipleObjects函數等待所有子執行緒返回,在全部返回後關閉所有句柄;
並且主執行緒還監聽和處理子執行緒的狀態,根據m_ExitCode判斷子執行緒是處於暫停、停止還是暫停後繼續的狀態(即文件搜索中的暫停、停止、暫停後繼續的功能),然後通過設置m_hExitEvent事件對象的激髮狀態來控制子執行緒的工作;
2、文件搜索中的開始、暫停、暫停後繼續、停止函數
//開始查找
BOOL CRapidFinder::StartFinder()
{
if (m_DirList.IsEmpty()) { SetErrNo(0); return FALSE; }
PostMessage(m_MainhWnd, WM_THREADCOUNT, (WPARAM)m_ActiveCount, NULL);
DWORD ThreadID;
//創建主執行緒
HANDLE hMainThread = CreateThread(NULL, 0, MainThreadProc, (LPVOID)this, CREATE_SUSPENDED, &ThreadID); //執行緒是在掛起狀態下創建的,並且在調用ResumeThread函數之前不會運行 。
ASSERT(hMainThread);
BOOL re = SetThreadPriority(hMainThread, m_Priority);//調整優先順序
ASSERT(re);//作用是如果它的條件返回錯誤,則終止程式執行
ResumeThread(hMainThread);
CloseHandle(hMainThread);
return TRUE;
}
//暫停查找
void CRapidFinder::PauseFinder()
{
if (m_ExitCode == PAUSE)return;
m_ExitCode = PAUSE;
SetEvent(m_hExitEvent);//發出m_hExitEvent訊號
Sleep(40);
}
//繼續查找
void CRapidFinder::ResumeFinder()
{
SetEvent(m_hExitEvent);
}
//停止查找
void CRapidFinder::StopFinder()
{
if (m_ExitCode == STOP)return;
if (m_ExitCode == PAUSE)
{
ResumeFinder();//重新開始
Sleep(40);//延時
}
m_ExitCode = STOP;
SetEvent(m_hExitEvent);
}
在StartFinder()中創建主執行緒;暫停、暫停後繼續、停止都是通過改變m_hExitEvent的激髮狀態來實現
因為創建m_hExitEvent事件時:CreateEvent(NULL, TRUE, FALSE, L"RAPIDFINDER");是手動重置激髮狀態,所以在主函數的個狀態的處理邏輯里每次都需要先用resetEvent()重置激發態
2、MFC介面
主要用到了mfc的按鈕控制項、Edit控制項和list控制項
主要介面布局如上
相關成員的定義:
事件處理程式(函數)的實現
1)MFC類的初始化,對相關成員變數賦初值;以及對話框介面的初始化(設置菜單,圖標),另外還使用到了CListCtrl控制項來顯示搜索到的文件資訊
CFinderDemoDlg::CFinderDemoDlg(CWnd* pParent /=NULL/)
: CDialog(CFinderDemoDlg::IDD, pParent)
{
//{{AFX_DATA_INIT(CFinderDemoDlg)
m_ActiveCount = _T("0");
m_folder = _T("");//C:\;D:\;E:\;F:");
m_count = _T("0");
m_findfolder = _T("");
m_text = _T("");
m_filename = _T("");
m_threadcount = 10;
m_priority = 0;
//}}AFX_DATA_INIT
// Note that LoadIcon does not require a subsequent DestroyIcon in Win32
m_folder=GetAllDriverList();
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);//圖標
}
//初始化對話框
BOOL CFinderDemoDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// IDM_ABOUTBOX must be in the system command range.
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
m_ListCtrl.SetExtendedStyle(LVS_EX_FULLROWSELECT|LVS_EX_TRACKSELECT|LVS_EX_FLATSB|LVS_EX_UNDERLINEHOT|LVS_EX_GRIDLINES);//具體參考//docs.microsoft.com/zh-cn/windows/win32/Controls/extended-list-view-styles
m_ListCtrl.InsertColumn(0,T("文件名"),LVCFMT_IMAGE|LVCFMT_LEFT,80,80);//插入一列
m_ListCtrl.InsertColumn(1,T("路徑"),LVCFMT_LEFT,180);
m_ListCtrl.InsertColumn(2,T("大小"),LVCFMT_LEFT,80);
m_ListCtrl.InsertColumn(3,T("類型"),LVCFMT_LEFT,80);
m_ListCtrl.SetHoverTime(500);//設置列表視圖控制項的當前逗留時間
finder.ThreadSet(30);
m_ListCtrl.SetRedraw();//數據更新時閃爍
UIControl(false);
CMenu* pSysMenu = GetSystemMenu(FALSE);// 返回當前使用窗口菜單的拷貝的句柄。該拷貝初始時與窗口菜單相同,但可以被修改。
if (pSysMenu != NULL)
{
CString strAboutMenu;
strAboutMenu.LoadString(IDS_ABOUTBOX);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);//畫一條水平區分線
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);//將IDM_ABOUTBOX添加到菜單中
}
}
//設置圖標
SetIcon(m_hIcon, TRUE); // 設置大圖標
SetIcon(m_hIcon, FALSE); // 設置小圖標
// TODO: Add extra initialization here
return TRUE; // return TRUE unless you set the focus to a control
}
2)數據交換函數和消息響應函數
將控制項的值傳給類中的將變數的值顯示到控制項上;以及對相關消息的響應函數(如開始、暫停、顯示「正在查找的文件」的函數…)
void CFinderDemoDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CFinderDemoDlg)
//將控制項的值傳遞給指定變數假設為類型1,將變數的值傳遞給控制項假設為類型2
DDX_Control(pDX, IDC_LIST, m_ListCtrl);//類型2
DDX_Control(pDX, IDC_STOP, m_stop);//將IDC_STOP窗口的值傳遞給m_stop
DDX_Control(pDX, IDC_PAUSE, m_pause);//類型1
DDX_Control(pDX, IDC_START, m_start);//類型1
DDX_Text(pDX, IDC_ACTIVE_THREAD, m_ActiveCount);//將m_ActiveCount的值顯示到IDC_ACTIVE_THREAD窗口
DDX_Text(pDX, IDC_FOLDER, m_folder);//類型1
DDX_Text(pDX, IDC_COUNT, m_count);//類型2
DDX_Text(pDX, IDC_FINDING_FOLDER, m_findfolder);//將m_findfolder的值顯示到IDC_FINDING_FOLDER窗口
DDX_Text(pDX, IDC_INCLUDE_TEXT, m_text);//類型1
DDX_Text(pDX, IDC_FILENAME, m_filename);//類型1
DDX_Text(pDX, IDC_THREAD_NO, m_threadcount);//類型1
//DDX_Text(pDX, IDC_PRIORITY, m_priority);
//}}AFX_DATA_MAP
}
BEGIN_MESSAGE_MAP(CFinderDemoDlg, CDialog)//添加消息響應函數,為每個消息處理函數加入一個入口。
ON_WM_SYSCOMMAND()//系統消息
ON_WM_PAINT()//繪圖消息
ON_WM_QUERYDRAGICON()//查詢icon消息
ON_BN_CLICKED(IDC_STOP, OnStop)//停止
ON_BN_CLICKED(IDC_START, OnStart)//開始
ON_BN_CLICKED(IDC_PAUSE, OnPause)//暫停
ON_BN_CLICKED(IDC_BROWSE, OnBrowse)//選擇文件
ON_EN_CHANGE(IDC_THREAD_NO, OnChangeThreadNo)//設置執行緒數
ON_MESSAGE(WM_THREADEXIT, OnFindExit)//查找時停止
ON_MESSAGE(WM_THREADCOUNT, OnFindThreadCount)
ON_MESSAGE(WM_FINDERITEM , OnFindItem)
ON_MESSAGE(WM_THREADPAUSE, OnFindPause)//查找時暫停
ON_MESSAGE(WM_FINDERFOLDER, OnFindingFolder)//正在查找
ON_EN_CHANGE(IDC_FOLDER, &CFinderDemoDlg::OnEnChangeFolder)
END_MESSAGE_MAP()
3)文件相關函數,OnBrowse()選擇在哪個路徑進行查找文件;修改查找文件的執行緒數的OnChangeThreadNo()函數;
點擊選擇文件按鈕,觸發OnBrowse事件處理函數,打開選擇文件路徑的面板;在mfc介面上設置執行緒數目,通過值交換函數,將控制項上的值賦給相關類的成員變數,再通過OnChangeThreadNo()函數修改創建執行緒是默認設置的執行緒數目為新的值
//選擇查找文件路徑
void CFinderDemoDlg::OnBrowse()
{
// TODO: Add your control notification handler code here
BROWSEINFO bi;
char dispname[MAX_PATH], path[MAX_PATH];
ITEMIDLIST* pidl;
//
bi.hwndOwner = m_hWnd;
bi.pidlRoot = 0;
bi.pszDisplayName = dispname;
bi.lpszTitle = "請選擇查找目錄:";
bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_EDITBOX | BIF_DONTGOBELOWDOMAIN;
bi.lpfn = 0;
bi.lParam = 0;
bi.iImage = 0;
if (pidl = SHBrowseForFolder(&bi))
{
SHGetPathFromIDList(pidl, path);
m_folder = CString(path);
if (m_folder.IsEmpty())m_folder = GetAllDriverList();
UpdateData(false);
}
}
//當用戶未指定查找路徑時的默認路徑
CString CFinderDemoDlg::GetAllDriverList()
{
CString tmp = _T("D:"), Dir;//D:
for (int i = 1; i <= 25; i++)
{
Dir = CString("D:" + i) + _T(":");
if (GetDriveType(Dir.GetBuffer(0)) == DRIVE_NO_ROOT_DIR)continue;//判斷該路徑是否是有效的
tmp += ";" + Dir;
}
return tmp;
}
//修改默認的執行緒數為我們MFC介面選擇的執行緒數
void CFinderDemoDlg::OnChangeThreadNo()
{
int count=GetDlgItemInt(IDC_THREAD_NO);
if(count<1||count>99){count=10;SetDlgItemInt(IDC_THREAD_NO,count);}
//finder.ThreadSet(count,finder.GetThreadPrioriy());
finder.ThreadSet(count, m_priority);
}
4)開始、暫停、停止查找文件的函數
//開始
void CFinderDemoDlg::OnStart()
{
// TODO: Add your control notification handler code here
// ::SendMessage(GetSafeHwnd(),WM_THREADCOUNT,(WPARAM)100,NULL);
UpdateData(true);//刷新控制項的值到變數
m_count = _T("0");
m_ActiveCount = _T("0");
count = 0;
m_imglist.DeleteImageList();
m_imglist.Create(16, 16, ILC_MASK | ILC_COLORDDB, 1, 100);//新建一個影像列表,然後用add添加圖標(這裡在OnFindItem函數使用到了)
m_ListCtrl.SetImageList(&m_imglist, LVSIL_SMALL);//再將該列表中的影像綁定到m_ListCtrl列表控制項上
m_ListCtrl.DeleteAllItems();
UpdateData(false);//將變數刷新到控制項進行顯示
finder.FinderSet(GetSafeHwnd(), m_filename, m_folder);
finder.FindWithText(m_text, m_text.GetLength());
finder.StartFinder();
UIControl(true);//開始後就調用UIControl進位相關控制項接收滑鼠或鍵盤消息
}
//暫停
void CFinderDemoDlg::OnPause()
{
// TODO: Add your control notification handler code here
static BOOL ispause = true;
if (ispause)
{
m_pause.SetWindowText("繼續");//如果是暫停狀態就將按鈕修改為「繼續」
finder.PauseFinder();
ispause = false;
}
else
{
m_pause.SetWindowText("暫停");
finder.ResumeFinder();
ispause = true;
}
}
//停止
void CFinderDemoDlg::OnStop()
{
// TODO: Add your control notification handler code here
finder.StopFinder();
}
//彈出框
LRESULT CFinderDemoDlg::OnFindExit(WPARAM wparam, LPARAM lparam)
{
m_pause.SetWindowText("暫停");
UIControl(false);
if (wparam == 1)AfxMessageBox("停止查找!");//:AfxMessageBox比MessageBox簡單一些,因為它是一個全局函數所以不需要對應的一個窗口類,但是不能控制消息框標題
else AfxMessageBox("查找結束!");
m_ListCtrl.RedrawItems(0, count - 1);//暫停時只顯示現在查到的
return 0;
}
LRESULT CFinderDemoDlg::OnFindPause(WPARAM wparam, LPARAM lparam)
{
AfxMessageBox("用戶暫停!");
return 0;
}
5)介面更新函數,如更新當前活動執行緒數,查找到符合的文件數目,開始、暫停、停止的按鈕狀態,顯示正在查找的文件,顯示已經找到的符合的文件
LRESULT CFinderDemoDlg::OnFindExit(WPARAM wparam,LPARAM lparam)
{
m_pause.SetWindowText("暫停");
UIControl(false);
if(wparam==1)AfxMessageBox("停止查找!");//:AfxMessageBox比MessageBox簡單一些,因為它是一個全局函數所以不需要對應的一個窗口類,但是不能控制消息框標題
else AfxMessageBox("查找結束!");
m_ListCtrl.RedrawItems(0,count-1);//暫停時只顯示現在查到的
return 0;
}
LRESULT CFinderDemoDlg::OnFindPause(WPARAM wparam,LPARAM lparam)
{
AfxMessageBox("用戶暫停!");
return 0;
}
//查找到的文件的文件資訊
LRESULT CFinderDemoDlg::OnFindItem(WPARAM wparam,LPARAM lparam)
{
m_count.Format("%d",++count);
UpdateData(false);
CString pathname=*((CString *)wparam);
CFileStatus Status;
CFile::GetStatus(pathname,Status);
CString Unit="Byte";
float flen=(float)Status.m_size;
if(flen>1024)
{
flen/=1024;
if(flen<1024){Unit="KB";}
else{flen/=1024;Unit="MB";}
}
CString Size;
Size.Format("%1.2f",flen);
int pos=pathname.ReverseFind('');
SHFILEINFO sfi;
if (::SHGetFileInfo (pathname, FILE_ATTRIBUTE_NORMAL, &sfi, sizeof(SHFILEINFO),SHGFI_USEFILEATTRIBUTES | SHGFI_DISPLAYNAME | SHGFI_TYPENAME |SHGFI_ICON|SHGFI_SMALLICON ))
{
//更新mfc介面的list框顯示
m_imglist.Add(sfi.hIcon);
m_ListCtrl.InsertItem(count-1,sfi.szDisplayName,count-1);
m_ListCtrl.SetItemText(count-1,1,pathname.Mid(0,pos));
m_ListCtrl.SetItemText(count-1,2,(Size+Unit));
m_ListCtrl.SetItemText(count-1,3,sfi.szTypeName);
}
m_ListCtrl.Update(count-1);
PeekAndPump();
return 0;
}
//在MFC介面顯示當前活動的執行緒數
LRESULT CFinderDemoDlg::OnFindThreadCount(WPARAM wparam,LPARAM lparam)
{
m_ActiveCount.Format("%ld",LONG(wparam));
UpdateData(FALSE);
return 0;
}
//在mfc介面顯示當前正在查詢的文件路徑
LRESULT CFinderDemoDlg::OnFindingFolder(WPARAM wparam,LPARAM lparam)
{
m_findfolder=*((CString *)wparam);
UpdateData(false);
return 0;
}
/控制相關控制項的狀態
void CFinderDemoDlg::UIControl(BOOL bOp)//start with true;
{
m_start.EnableWindow(!bOp);
m_stop.EnableWindow(bOp);
m_pause.EnableWindow(bOp);
GetDlgItem(IDC_THREAD_NO)->EnableWindow(!bOp);//EnableWindow函數允許/禁止指定的窗口或控制項接受滑鼠和鍵盤的輸入
GetDlgItem(IDC_SPIN_THREAD_NO)->EnableWindow(!bOp);
}