不怕萬人阻擋,只怕自己投降
9.1 上機實驗目的
l 掌握命令解釋程式的設計方法。
l 學習Windows系統呼叫的使用,瞭解目錄操作、程序控制等相關知識。
l 培養C/C++語言程式設計技能,提高程式設計和文件編寫能力。
l 鍛鍊團隊成員的交流與合作能力。
9.2 上機實驗要求
本實驗要求實現一個簡單的命令解釋程式,其設計類似於MS-DOS的Command程式。具體要求如下:
(1) 參考Command命令解釋程式,採用控制檯命令列輸入方式,命令列提示符是當前目錄名與提示符“>”,在提示符後輸入命令。命令執行結束後,在控制檯繼續顯示提示符,等待輸入新的命令。
(2) 實現以下內部命令:
l cd <路徑名> 切換當前目錄。
l dir [<路徑名>] 顯示指定目錄下的檔案、子目錄及磁碟空間等相關資訊。
l tasklist 顯示系統當前程序資訊,包括程序識別符號pid、該程序包含的執行緒數、程序名等。
l taskkill <pid> 結束系統中正在執行的程序,須指定程序識別符號pid。
l history 顯示控制檯中曾經輸入過的命令。
l help 顯示本程式使用的幫助資訊。
l exit 退出控制檯,結束本命令解釋程式。
(3) 對前臺程序和後臺程序的操作
本實驗設計的命令解釋程式可以將程序放在前臺執行或者後臺執行。
啟動前臺程序的方法是在提示符下輸入命令列:
fp <可執行檔名(含路徑
啟動後臺程序的方法是在提示符下輸入命令列:
bg& <可執行檔名(含路徑)>
在前臺程序執行期間,解釋程式一直等待,直到前臺程序執行結束,才再顯示提示符;而在後臺程序執行期間,解釋程式不必等待,會立即顯示提示符,允許使用者輸入下一條命令。
(4) 命令解釋程式還需要捕獲Ctrl+C組合鍵的訊號來結束前臺正在執行的控制檯程序,並返回使用者輸入介面(顯示提示符),等待新命令輸入。本實驗程式利用系統自備功能,來實現此功能。(注:若前臺程序是圖形介面,則按Ctrl+C並不能使其結束,而是使本實驗的命令解釋程式結束。)
(5) 其他要求
該命令解釋程式應具有相應的出錯提示功能。
程式每次接收使用者輸入的一行命令,在使用者輸入回車鍵
若輸入命令時僅輸入回車鍵,則不作任何操作,重新顯示提示符,等待使用者輸入新的命令。
定義空格為分隔符,程式應能處理命令中出現的重複空格符。
9.3 相關基礎知識
9.3.1 命令解釋程式與作業系統核心的關係
命令解釋程式是使用者和系統核心之間的介面程式。對於Windows系統來說,由於已經提供了具有良好互動性的圖形使用者介面,傳統的控制檯命令解釋程式已經很少被廣大使用者所瞭解和使用。但是,對於某些應用,例如使用一條命令刪除所有副檔名為tmp的檔案,或者刪除某些具有特殊名字的病毒檔案,在圖形使用者介面下很難甚至不能完成。這需要通過Windows提供的Command命令介面來完成。Command程式是一個命令直譯器,它擁有自己的內部命令集,使用者和其他應用程式都可以通過對Command程式的呼叫完成與系統核心的互動。Command程式與核心的關係如圖9-1所示。
9.3.2 系統呼叫及Win32 API相關函式介紹作業系統所能完成的每一個特殊功能都有一個函式與其對應,即作業系統把它所能完成的功能以函式的形式提供給應用程式使用。應用程式對這些函式的呼叫叫系統呼叫。這些函式的集合就是Windows作業系統提供給應用程式的程式設計介面(Application Programming Interface),簡稱Windows API或Win32 API。所有在Win32平臺上執行的應用程式都可以呼叫這些函式。
使用Windows API,應用程式可以充分挖掘Windows的32位作業系統潛力。Microsoft的所有32位平臺都支援統一的API。
Windows的相關API的說明都可以在MSDN(Microsoft Developet Network)中查到,包括定義、使用方法等。下面簡單介紹本實驗中涉及的Windows API。
1.GetCurrentDirectory函式
功能:查詢當前程序的當前目錄,呼叫成功,返回裝載到lpBuffer的位元組數。失敗則返回0。
格式:
DWORD GetCurrentDirectory ( //DWORD就是unsigned long
DWORD nBufferLength, // 緩衝區的長度
LPTSTR lpBuffer // 指定一個預定義字串,用於裝載當前目錄
) //LPSTR:是指向字串的指標的型別名,即char *
【注】API中涉及的型別名,請參閱配套文件“Win32 Simple Data Types.doc”。
2.WaitForSingleObject函式
功能:等待一個事件訊號直至訊號出現或者超時。若等到訊號則返回WAIT_OBJECT_0(即0),若等待超過dwMiliseconds時間還是無訊號,則返回WAIT_TIMEOUT(即258)。若函式呼叫失敗,則返回WAIT_FAILED (即-1)。
格式:
DWORD WaitForSingleObject (
HANDLE hHandle, // 事件的控制代碼
DWORD dwMilliseconds // 最大等待時間,以ms計時。
)
3.SetCurrentDirectory函式
功能:設定當前目錄。返回非0表示成功,返回0表示失敗。
格式:
BOOL SetCurrentDirectory (
LPCTSTR lpPathName // 新設定的當前目錄路徑
)
4.FindFirstFile函式
功能:用於從一個資料夾(包括子資料夾)中查詢指定檔案,返回找到的檔案控制代碼。若呼叫失敗,則返回INVALID_HANDLE_VALUE (即-1)。
格式:
HANDLE FindFirstFile (
LPCTSTR lpFileName, // 檔名字串(可用萬用字元)
LPWIN32_FIND_DATA lpFindFileData // 指向一個用於保護檔案的結構體
)
【注】WIN32_FIND_DATA結構的說明請參看MSDN或本上機實驗指導的配套文件。
5.FindNextFile函式
功能:繼續查詢FindFirstFile函式搜尋後的檔案。它返回的檔案控制代碼可以作為引數用於FindNextFile函式。這樣就可方便地枚舉出與lpFileName引數指定的檔名相匹配的所有檔案。呼叫失敗,返回0。
格式:
HANDLE FindNextFile (
HANDLE hFindFile, // 前一個搜素到的檔案控制代碼
LPWIN32_FIND_DATA lpFindFileData // 指向一個用於保護檔案的結構體
)
6.GetVolumeInformation函式
功能:用於獲取磁碟相關資訊。執行成功返回非0;失敗,返回0。
格式:
BOOL GetVolumeInformation (
LPCTSTR lpRootPathName, // 磁碟驅動器程式碼字串(具體構成方法參看程式)
LPCTSTR lpVolumeNameBuffer, // 磁碟驅動器卷標名稱
DWORD nVolumeNameSize, // 磁碟驅動器卷標名稱長度
LPWORD lpVolumeSerialNumber, // 磁碟驅動器卷標序列號
LPWORD lpMaximunComponentLength, //系統允許的最大檔案長度
LPWORD lpFileSystemFlags, // 檔案系統標識
LPCTSTR lpFileSystemNameBuffer, // 檔案系統名稱
DWORD nFileSystemNameSize // 檔案系統名稱長度
)
7.GetDiskFreeSppaceEx函式
功能:獲取與一個磁碟的組織以及剩餘容量有關的資訊。呼叫失敗返回0。
格式:
HANDLE GetDiskFreeSppaceEx (
LPCTSTR lpRootPathName, // 不包括卷名的磁碟根路徑名
PULARGE_INTEGER lpFreeBytesAvailableToCaller, // 呼叫者可用的位元組數
PULARGE_INTEGER lpTotalNumberOfBytes, // 磁碟上的總位元組數
PULARGE_INTEGER lpTotalNumberOfFreeBytes // 磁碟上的可用位元組數
)
引數說明:lpRootPathName:根路徑名。例如形式為"C:\\"。使用NULL表示函式使用當前目錄所在的磁碟。
8.FileTimeToLocalFileTime函式
功能:將一個FILETIME結構轉換成本地時間。
格式:
BOOL FileTimeToLocalFileTime (
const FILETIME* lpFileTime, // 指向一個包含了UTC時間資訊的結構
LPFILETIME lpLocalFileTime // 用於裝載轉換過的本地時間的結構體
)
9.FileTimeToSystemTime函式
功能:根據一個FILETIME結構的內容,裝載一個SYSTENTIME結構。
格式:
BOOL FileTimeToSystemTime (
const FILETIME* lpFileTime, // 指向一個包含了檔案時間資訊的結構
LPFILETIME lpSystemTime // 用於裝載系統時間的結構體
)
10.CreateToolhelp32Snapshot函式
功能:為指定的程序、程序使用的堆(heap)、模組(module)、執行緒(thread)建立一個快照(snapshot)。快照建立成功則返回快照的控制代碼,失敗則返回INVALID_HANDL_VALUE。
格式:
HANDLE WINAPI CreateToolhelp32Snapshot (
DWORD dwFlags, // 指向快照中包含的系統內容
DWORD th32ProcessID // 指定將要快照的程序ID
)
11.Process32First函式
功能:是一個程序獲取函式,當使用CreateToolhelp32Snapshot()函式獲得當前執行程序的快照後,可以使用Process32First ( )函式獲得第一個程序的控制代碼。
格式:
BOOL WINAPI Process32First (
HANDLE hSnapshot, // 快照控制代碼
LPPROCESSENTRY32 lppe // 指向一個保護程序快照資訊的LPPROCESSENTRY32結構
)
12.Process32Next函式
功能:獲取快照中下一個程序資訊。
格式:
BOOL WINAPI Process32Next (
HANDLE hSnapshot, // 由Process32First或Process32Next函式獲得的快照控制代碼
LPPROCESSENTRY32 lppe // 指向一個保護程序快照資訊的LPPROCESSENTRY32結構
)
13.OpenProcess函式
功能:該函式開啟一個已經存在的程序物件,若成功,返回值是指定程序的開啟控制代碼。若失敗,則返回空值。
格式:
HANDLE OpenProcesst (
DWORD dwDesiredAccess, // 許可權標識(詳見MSDN)
BOOL bInheritHandle, // 指出返回的控制代碼是否能被當前程序建立的新程序繼承,
// TRUE表示可繼承,FALSE表示不能繼承。
DWORD dwProcessID // 程序ID
)
14.SetConsoleCtrlHandler函式
功能:新增或刪除一個事件鉤子(Handler)。
格式:
BOOL SetConsoleCtrlHandler (
PHANDLER_ROUTINE HandlerRoutine, // 回撥函式的指標
BOOL Add // 表示新增或刪除
)
15.CreateProcess函式
此函式已經在前面介紹,請參閱“上機實驗一”的1.2.2節,在此不再贅述。
16.GetExitCodeProcess函式
功能:獲取一個已中斷程序的退出程式碼。
格式:
BOOL GetExitCodeProcess (
HANDLE hProcess, // 程序控制代碼
LPDWORD lpExitCode // 指向接受退出碼的變數
)
17.TerminateProcess函式
功能:以給定的退出碼終止程序。
格式:
BOOL TerminateProcee (
HANDLE hProcess, // 程序控制代碼
UINT uExitCode // 程序的退出碼
)
9.4 實驗設計
本實驗在WindowsXP+VC++ 6.0環境下實現,利用Windows SDK提供的系統介面(API)完成程式的功能。因為VC++包含了Windows SDK所有工具和定義,所以安裝了VC++就不用再特意安裝SDK了。實驗中所用的API,是作業系統提供的。要使用這些API,需要一些標頭檔案,最常用的就是windows.h。一些特殊的API調研還需要其他的標頭檔案。
9.4.1 重要的資料結構
1.歷史命令迴圈陣列(佇列)
在history命令中,用陣列來存放輸入過的歷史命令。程式中假設該陣列的元素個數為20,陣列元素的結構定義如下:
typedef struct ENV_HISTORY {
int start; // 佇列的頭指標
int end; // 佇列的尾指標
char his_cmd[20][128]; // 佇列陣列(順序結構的佇列)
} ENV_HISTORY;
ENV_HISTORY envhis; // 定義佇列變數(為佇列分配記憶體空間)
2.檔案資訊連結串列
程式中,需要把dir命令取得的檔案資訊用連結串列儲存,輸出這些資訊時對連結串列遍歷。
連結串列結點的定義如下:
struct files_Content {
FILETIME time; // 檔案建立時間
char name[200]; // 檔名
int type; // type=1普通檔案, type=0目錄
int size; // 檔案大小
files_Content *next; // 構成連結串列的連結指標
} ;
9.4.2 程式實現
主程式的流程如圖9-2所示。
1.解析命令
解析命令就是分析輸入的命令列(input陣列),分離命令列中的命令和引數。命令和引數的分隔是由空格符完成的。將命令存入arg[0]指向的字串,將引數存入arg[1]指向的字串中。
2.命令處理
命令出路與執行命令的目的有關,其中系統呼叫是重要的組成部分。
void cd_cmd(char *route)
{
if (!SetCurrentDirectory(route))
{ // 設定當前目錄,若失敗則輸出出錯資訊
cout<<" SetCurrentDirectory failed ";
cout<<GetLastError()<<endl;
}
}
以上是cd命令的處理函式,涉及的Windows API為
SetCurrentDirectory( )函式,它的作用是設定當前目錄為指定路徑,若失敗則返回出錯資訊。其中出錯號從另一個API函式GetLastError( )獲得。
其餘命令處理函式結構類似,具體參見下面的原始碼。
9.5 源程式與執行結果
9.5.1 程式原始碼
1.WinShell.h
#define BUFSIZE MAX_PATH
#define HISNUM 20 //最多可以儲存20個歷史命令
char buf[BUFSIZE];
//儲存歷史命令的結構
typedef struct ENV_HISTORY {
int start; // 佇列的頭指標
int end; // 佇列的尾指標
char his_cmd[20][128]; // 佇列陣列(順序結構的佇列)
} ENV_HISTORY;
ENV_HISTORY envhis; // 定義佇列變數(為佇列分配記憶體空間)
//說明:因envhis是全域性變數(屬靜態變數),故其成員star,end有初值0
//儲存檔案或目錄相關資訊的結構
struct files_Content {
FILETIME time; // 檔案建立時間
char name[200]; // 檔名
int type; // type=1普通檔案, type=0目錄
int size; // 檔案大小
files_Content *next; // 構成連結串列的連結指標
} ;
2.WinShell.cpp
#define _Win32_WINNT 0x0501
#include <stdlib.h> //atoi()等
#include <iostream.h>
#include <windows.h> //DWORD;HANDLE...其中還有許多標頭檔案
#include <tlhelp32.h> //CreateToolhelp32Snapshot()
#include <string.h>
#include "WinShell.h"
// 以下兩個函式在主函式開頭聲明後放在主函式後面不行,故將它們移
// 至mian()的前面,原因可能是它們的引數型別分別是FILETIME和DWORD
// ***************** 時間處理函式 ******************
void ftime(FILETIME filetime)
{
SYSTEMTIME systemtime;
if (filetime.dwLowDateTime==-1) // Win32時間的低32位
cout<<"Never Expires\n";
else
{
//將UTC(Universal Time Coordinated)檔案時間轉換成本地檔案時間
if (FileTimeToLocalFileTime(&filetime,&filetime)!=0)
{
//將64位時間轉化成系統時間
if (FileTimeToSystemTime(&filetime,&systemtime)!=0)
{
//以一定能格式輸出時間
cout.fill('0'); //不足指定寬度是用0填充
cout<<dec<<systemtime.wYear<<'-';
cout.width(2);cout<<systemtime.wMonth<<'-'; //月份用2位顯示,下類似
cout.width(2);cout<<systemtime.wDay<<" ";
cout.width(2);cout<<systemtime.wHour<<':';
cout.width(2);cout<<systemtime.wMinute;
}
else
cout<<"FileTimeToSystemTime failed\n";
}
else
cout<<"FileTimeToLocalFileTime failed\n";
}
cout.fill(' '); //恢復空格填充
}
// ************ 回撥函式 ************
BOOL WINAPI ConsoleHandler(DWORD CEvent)
{ // 此函式不做什麼,由系統處理事件,包括按下Ctrl+C等
switch(CEvent)
{
case CTRL_C_EVENT:
break;
case CTRL_BREAK_EVENT:
break;
case CTRL_CLOSE_EVENT:
break;
case CTRL_LOGOFF_EVENT:
break;
case CTRL_SHUTDOWN_EVENT:
break;
}
return TRUE;
}
// **************** 主函式 ****************
void main()
{
//宣告程式中用到的函式
void cd_cmd(char *dir); // cd命令處理函式
void dir_cmd(char *dir); // dir命令處理函式
void GetProcessList(); // 獲得系統當前程序列表
void history_cmd(); // 獲得最近輸入的命令
void add_history(char *); // 將輸入命令列新增到命令歷史中
HANDLE process(int,char[]); // 建立程序
BOOL killProcess(char *); // kill程序
void help(); // 顯示幫助資訊
char c,*input,*arg[2],path[BUFSIZE];
int input_len=0,is_bg=0,i,j,k;
HANDLE hprocess; // 程序執行結束,返回程序控制代碼
DWORD dwRet;
while (true)//顯示提示符,等待使用者輸入命令是個無限迴圈過程
{
// 將指向輸入命令的指標陣列初始化
for (i=0;i<2;i++)
arg[i]=NULL;
// 獲得當前目錄並存入path中,BUFSIZE是最多能夠儲存的路徑名長度
dwRet=GetCurrentDirectory(BUFSIZE,path);//返回目錄資料實際長度存於dwRet
if (dwRet==0) // 返回當前目錄失敗,輸出出錯資訊
cout<<"GetCurrentDirectory failed "<<GetLastError()<<endl;
else if (dwRet>BUFSIZE)// BUFSIZE長度小於返回目錄資料的長度,輸出出錯資訊
cout<<"GetCurrentDirectory failed (buffer too small; need "<<dwRet<<"bytes)\n";
else
cout<<path<<'>'; // 顯示提示符(當前目錄名+'>')
// *********** 鍵盤輸入 ************
input_len=0;
// 將命令開頭的無用字元過濾掉
while ((c=cin.get())==' ' || c=='\t' || c==EOF) ;
if (c=='\n') //輸入為空命令(僅輸入回車符)時
continue; //結束本次迴圈,回到迴圈開頭,重新顯示提示符
while (c != '\n')
{
buf[input_len++]=c;
c=cin.get();
}
buf[input_len++]='\0'; // 加上串結束符
// 分配動態儲存空間,將命令從快取複製到input中
input=new char[input_len];
strcpy(input,buf); //為了便於後邊的處理,將命令行復制到input中
// *********** 解析命令 ************
for (i=0,j=0,k=0; i<input_len && k<2; i++)//k<2是限制只處理1個命令引數
{ //即arg[0]為命令,arg[1]為引數
if (input[i]==' ' || input[i]=='\0')
{
if (j==0) // 去掉連在一起的多個空格
continue;
else
{
buf[j++]='\0';
arg[k]=new char[sizeof(char)*(j+1)];
strcpy(arg[k++],buf); // 將命令或引數複製到arg中
j=0; // 準備取下一個引數
}
}
else//不是' '和'\0'字元,則存入buf[]中
buf[j++]=input[i];
}
add_history(input); // 將輸入命令新增到歷史命令佇列中
// ****************** 命令處理 ******************
if (strcmp(arg[0],"cd") == 0) // **** cd命令 ****
{
if (arg[1] != NULL)
{
cd_cmd(arg[1]);
delete []arg[1];
}
else
cout<<"cd命令必須指定路徑名!\n";
delete []input;
delete []arg[0];
continue; //返回迴圈開頭,重新顯示提示符
}
if (strcmp(arg[0],"dir")==0) // **** dir命令 ****
{
char *route;
if (arg[1]==NULL) // 若dir命令無引數,則對當前目錄操作
{
route=path; // 取當前目錄
dir_cmd(route);
}
else
{
dir_cmd(arg[1]);
delete []arg[1];
}
delete []input; // 釋放堆空間
delete []arg[0];
continue;
}
if (strcmp(arg[0],"tasklist")==0) // **** tasklist命令 ****
{
GetProcessList();// 該函式通過呼叫若干API函式,獲取系統當前程序列表
delete []input;
delete []arg[0];
if (arg[1] != NULL)//防止使用者誤輸入命令引數
delete arg[1];
continue;
}
if (strcmp(arg[0],"fp")==0) // *** fp命令(前臺程序) ***
{
if (arg[1]==NULL)
{
cout<<"沒有指定可執行檔案\n";
delete []input;
delete []arg[0];
continue;
}
is_bg=0; // 後臺標誌置0(不是後臺程序)
hprocess=process(is_bg,arg[1]); //建立程序,返回新程序的控制代碼
// 等待新程序執行完畢(INFINTE表示等待無限制)
if (WaitForSingleObject(hprocess,INFINITE)==WAIT_OBJECT_0)
{
//如果程序執行完畢,釋放控制檯
delete []input;
delete []arg[0];
delete []arg[1];
}
continue;
}
if (strcmp(arg[0],"bg&")==0) // *** bg&命令(後臺程序) ***
{
if (arg[1]==NULL)
{
cout<<"沒有指定可執行檔案\n";
delete []input;
delete []arg[0];
continue;
}
is_bg=1; // 後臺標誌置1(真)
process(is_bg,arg[1]); //為可執行檔案arg[1]建立後臺程序
delete []input;
delete []arg[0];
delete []arg[1];
continue;
}
if (strcmp(arg[0],"taskkill")==0) // ***** kill程序 *****
{
BOOL success;
if (arg[1]!=NULL)
{
success=killProcess(arg[1]); // arg[1]指向程序ID
if (!success) // 若撤銷程序失敗,則顯示出錯資訊
cout<<"kill process failed!\n";
delete []arg[1];
}
else
cout<<"taskkill命令必須指定程序ID!"<<endl;
delete []input;
delete []arg[0];
if (arg[1] != NULL)//防止使用者誤輸入命令引數
delete arg[1];
continue;
}
if (strcmp(arg[0],"history")==0) // **** 顯示歷史命令 ****
{
history_cmd();
delete []input;
delete []arg[0];
if (arg[1] != NULL)//防止使用者誤輸入命令引數
delete arg[1];
continue;
}
if (strcmp(arg[0],"help")==0) // **** help命令 ****
{
help();
delete []input;
delete []arg[0];
if (arg[1] != NULL)//防止使用者誤輸入命令引數
delete arg[1];
continue;
}
if (strcmp(arg[0],"exit")==0) // **** exit命令 ****
{
cout<<"\nBye bye!\n\n";
delete []input;
delete []arg[0];
if (arg[1] != NULL)//防止使用者誤輸入命令引數
delete arg[1];
break; // 退出死迴圈,結束程式
}
else // 輸入命令不正確,給出出錯資訊
{
cout<<"please input correct commmand!\n";
delete []input;
if (arg[0])
delete []arg[0];
if (arg[1])
delete []arg[1];
continue;
}
}
} // **** 主函式結束 ****
// ************* 相關命令出路函式 **************
void cd_cmd(char *route) // **** cd命令實現函式 ****
{
if (!SetCurrentDirectory(route)) //設定當前目錄,若失敗則返回出錯資訊
cout<<"SetCurrentDirectory failed "<<GetLastError()<<endl;
}
// ***************** dir命令實現函式 *****************
void dir_cmd(char *route)
{
WIN32_FIND_DATA FindFileData; //將找到的檔案或目錄以WIN32_FIND_DATA結構返回
files_Content head,*p,*q; //定義指定檔案結構體的頭結點和指標
HANDLE hFind=INVALID_HANDLE_VALUE; // 控制代碼變數初值為“非法控制代碼值”
DWORD dwError; // 定義32位整數
char volume_name[256],str[22];
int file=0,dir=0; //檔案數和目錄數初始值為0
_int64 sum_file=0; //總檔案大小為0位元組,其值較大儲存為64位整數
_int64 l_user,l_sum,l_idle; //呼叫者可用空間,總容量,磁碟總可用空間
unsigned long volume_number; //卷序列號
char *DirSpec[4];
head.next=NULL;
DirSpec[0]=new char[2];
strncpy(DirSpec[0],route,1);
DirSpec[0][1]='\0'; //DirSpec[0]為驅動器名
DirSpec[1]=new char[4];
strcpy(DirSpec[1],DirSpec[0]);
strncat(DirSpec[1],":\\",3); //DirSpec[1]用於獲取驅動器資訊
DirSpec[2]=new char[strlen(route)+2];
DirSpec[3]=new char[strlen(route)+5];
strcpy(DirSpec[2],route); //DirSpec[2]為dir命令的目錄名
strcpy(DirSpec[3],route);
int len=strlen(route);
if (route[len-1]!='\\')
strncat(DirSpec[2],"\\",2);
strncat(DirSpec[3],"\\*.*",5); //DirSpec[3]用於查詢目錄中的所有檔案
//搜素DirSpec[3]指定的檔案,檔案資訊存於FindFileData變數中,返回找到的檔案控制代碼
hFind=FindFirstFile(DirSpec[3],&FindFileData);
if (hFind==INVALID_HANDLE_VALUE) //查詢控制代碼返回為無效值,查詢失敗
cout<<"Invalid file handle, Error is "<<GetLastError()<<endl;
else
{
//獲取卷的卷名(存於volume_name),卷序列號(存於volume_number)
GetVolumeInformation(DirSpec[1],volume_name,50,&volume_number,NULL,NULL, NULL,10);
if (strlen(volume_name)==0)
cout<<"\n\n驅動器"<<DirSpec[0]<<"中的卷沒有標籤。"<<endl;
else
cout<<"\n\n驅動器"<<DirSpec[0]<<"中的卷是 "<<volume_name<<endl;
cout<<"卷的序列號是 "<<hex<<volume_number<<dec<<endl<<endl;;
cout<<DirSpec[2]<<" 的目錄\n\n";
head.time=FindFileData.ftCreationTime;//獲得的檔案建立時間,存入檔案結構體head中
strcpy(head.name,FindFileData.cFileName);//獲得的檔名,存入檔案結構體head中
// 若資料屬性是目錄,則置type為0
if (FindFileData.dwFileAttributes==FILE_ATTRIBUTE_DIRECTORY)
{
head.type=0;
dir++;
}
else
{
//如果資料屬性是檔案,type位為1
head.type=1;
head.size=FindFileData.nFileSizeLow; //將檔案大小存入結構體head中
file++; //檔案數增1
sum_file += FindFileData.nFileSizeLow; //將檔案大小(位元組數)累加
}
p=&head; // p指向頭結點head
//如果還有下一個資料,繼續查詢
while (FindNextFile(hFind,&FindFileData) != 0)
{ // 第二個結點開始,分配動態空間
q=new files_Content[sizeof(files_Content)];
q->next=NULL;
q->time=FindFileData.ftCreationTime; // 儲存檔案建立時間
strcpy(q->name,FindFileData.cFileName); // 儲存檔名
if (FindFileData.dwFileAttributes==FILE_ATTRIBUTE_DIRECTORY)
{
q->type=0; // 找到的是目錄
dir++; // 目錄數增1
}
else // 否則,找到的是檔案
{
//如果資料屬性是檔案,type位為1
q->type=1;
q->size=FindFileData.nFileSizeLow; //將檔案大小存入結構體
file++; //檔案數增1
sum_file += FindFileData.nFileSizeLow; //將檔案大小累加
}
p->next=q; // 構成單鏈表
p=q; // p指向新結點
}
p->next=NULL; // 連結串列尾結點的next指標須置為NULL
//將結構體中資料的建立時間、型別、大小、名稱等資訊依次輸出
p=&head; // 從連結串列頭結點開始
while (p != NULL)
{
ftime(p->time); // 按規定格式顯示檔案建立時間
if (p->type==0) // 若是目錄,則顯示“<DOR>”
cout<<"\t<DIR>\t\t";
else
{ // 若是檔案,則按寬度為9的格式顯示檔案大小(位元組數)
cout<<"\t\t";cout.width(9);
cout<<dec<<(unsigned)p->size;
}
cout<<'\t'<<p->name<<endl; // 顯示檔名
p=p->next; // 準備顯示下一個目錄項(檔案或目錄)
}
//顯示檔案和目錄總數以及磁碟空間相關資訊
cout.width(15);
cout<<file<<" 個檔案\t\t\t";
//printf()使用格式符“%I64d”可以輸出64位整數,但“cout<<”只支援32位整數
//故此處先將64位整數sum_file轉換成以10進位制形式的字串後再輸出
_i64toa(sum_file,str,10); //64位整數sum_file轉換成10進位制字串存於str中
cout<<str<<" 位元組"<<endl;
GetDiskFreeSpaceEx(DirSpec[1],(PULARGE_INTEGER)&l_user,
(PULARGE_INTEGER)&l_sum,(PULARGE_INTEGER)&l_idle);
cout.width(15);
cout<<dir<<" 個目錄\t\t\t";
_i64toa(l_idle,str,10); //64位整數l_idle轉換成10進位制字串存於