進程由兩部分組成:
- 操作系統管理進程的內核對象。存放該進程 的統計信息的地方。
- 地址空間,包含可執行模塊和DLL模塊的代碼和數據。動態分配的內存(線程堆棧和堆)。
進程是不活潑的,進程當中至少要有一個線程,每個線程要有自己的堆棧和自己的CPU寄存器。CPU通過算法給每個線程分配時間片的辦法來造成假象是在同時工作(多核通過自己的算法實現同時運行)。
4.1 編寫第一個Windiws應用程序
Windows兩種類型的程序:
- CUI程序,比如CMD.EXE等等。Microsoft Visual C++連接開關為/SUBSYSTEM:CONDOLE(程序啟動時不能創建GUI程序)。
- GUI程序,圖形用戶程序,比如Notepad,Word等等。Microsoft Visual C++連接開關為/SUBSYSTEM:WINDOWS(程序啟動時不能創建CUI程序)。
注意:倆者的概念其實是很模糊的,CUI可以創建GUI圖形界面,反之GUI程序可能用CUI程序。
Windows進入點函數(區分在于CUI和GUI程序,ANSI碼和UNICODE碼)
int WINAPI WinMain(
HINSTANCE hinstExe,
HINSTANCE,
PSTR pszCmdLine,
int nCmdShow);
int WINAPI wWinMain(
HINSTANCE hinstExe,
HINSTANCE,
PWSTR pszCmdLine,
int nCmdShow);
int __cdecl main(
int argc,
char *argv[],
char *envp[]);
int __cdecl wmain(
int argc,
wchar_t *argv[],
wchar_t *envp[]);
其實Windows程序啟動時最開始并不調用自己寫的入口函數,而是調用系統的幾個入口函數,以便可以調用malloc和free之類的函數,初始化全局和靜態C++對象等。
- 檢索指向新進程的完整命令行的指針。
- 檢索指向新進程的環境變量的指針。
- 對C/C++運行期的全局變量進行初始化。如果包含StdLib.h文件,代碼就可以訪問這些變量。
- 對C運行期的malloc和callo和其他底層輸入/輸出例程使用的內存棧進行初始化。
- 為所有全局和靜態C++類對象調用構造函數。
應用程序類型 進入點 嵌入可執行文件的啟動函數
ANSI碼GUI應用程序 WinMain WinMainCRTStattup
UNICODE碼GUI應用程序 wWinMain wWinMainCRTStattup
ANSI碼CUI應用程序 main mainCRTStattup
UNICODE碼CUI應用程序 wmain wmainCRTStattup
注意:應用程序會根據SUBSYSTEM開關來查找嵌入可執行啟動函數,如果進入點函數和啟動函數不匹配則顯示鏈接錯誤。可以刪除SUBSYSTEM(VS Project Settings)開關,這樣應用程序會自動需找匹配的函數。
進入點函數返回時調用系統的exit函數,將返回值傳遞給它。exit函數負責下面操作:
- 調用由_onexit函數的調用而注冊的任何函數。
- 為所有全局的和靜態的C++類對象調用析構函數
- 調用操作系統的ExitProcess,并將返回值傳遞給他,關閉進程。
4.1.1 進程的實例句柄
WinMain/wWinMain函數的第一個參數表示進程加載的可執行文件的基地址/句柄。對于加載資源的調用都要使用此句柄,比如HICON LoadIcon(HINSTANCE, PCTSTR)。有的函數需要使用HMODULE,和HINSTANCE是一個意思(區分主要在于16位的操作系統中)。
HMODULE GetModuleHandle(PCTSTR pszModele);
函數作用,返回加載調用進程中的可執行文件或者DLL的基地址/句柄,參數是可執行文件或者DLL的名稱。給pszModule賦值NULL,則返回的是進程中可執行文件的句柄。
注意:如果找不到則返回NULL。如果在DLL中傳遞NULL,返回的仍然是進程加載的可執行文件的句柄。
4.1.2 進程的前一個實例句柄
第二個參數都傳遞NULL,是為16位系統所保留的。
4.1.3 進程的命令行
注意:不要試圖修改命令行內部內存的值,要使用修改先拷貝出來。
PTSTR GetCommandLine(); // 返回命令行字符串
PTSTR CommandLineToArgv(PTSTR pszCmdLine, int *pNumArgs); // 拆分命令行字符串函數
Demo:
int nNumargs;
PTSTR *ppArgv = CommandLineToArgv(GetCommandLine(), &nNumargs);
if ('x' == *ppArgv[1]) {
// TODO:
}
// 手動釋放內存,一般不需要釋放,系統會進程關閉時候自動釋放
HeapFree(GetProcessHeap, 0, ppArgv);
4.1.4 進程的環境變量
環境塊是進程地址空間中分配的內存塊每個環境塊都包含一組字符串,格式如下:
VarName1=VarVarlue1\0
VarName2=VarVarlue2\0
VarName3=VarVarlue3\0
…..
VarNameX=VarVarlueX\0
\0
注意:
- 排序必須按照字母順序。
- ‘=’號不能是變量名的一部分。
- 等號左右兩邊的空格將被算做名稱或者值。
- 最后必須加個’\0’表示結束。
- 子進程和父進程不共用環境塊,修改不會影響父/子進程。
DWORD GetEnvironmentVariable(PCTSTR pszName, PTSTR pszValue, DWORD cchValue);
pszName指變量名,pszValue指向變量值的緩存區,cchValue緩存區的大小。找不到變量名或者設置的長度不夠存放就返回0。
ExpandEnvironmentStrings(PCSTR pszSrc, PSTR pszDst, DWORD nSize);
用來用現實出可替換的環境變量的字符串。
BOOL SetEnvironmentVariable(PCTSTR pszName, PCTSTR pszValue);
設置環境變量的值,如果不存在則創建,如果存在則替換他的值。
4.1.5 進程的親緣性
子進程繼承父進程的親緣性。(具體什么意思沒明白)
4.1.6 進程的錯誤模式
進程可以設置如何處理一些錯誤。
UINT SetErrorMode(UINT fuErrorMode);
各個模式用OR連接
標志 說明
SEM_FAILCRITICALERRORS 系統不顯示關鍵錯誤句柄消息框,并將錯誤返回給調用進程
SEM_NOGOFAULTERRORBOX 系統不顯示一般保護故障消息框。本標志只應該由采用異常情況處理程序來處理一般保護(GP)故障的調式應用程式來設定
SEM_NOOPENFILEERRORBOX 當系統找不到文件時,它不顯示消息框。
SEM_NOALIGNMENTFAULTEXCEPT 系統自動排除內存沒有對其的故障,并使應用程序看不到這些故障。本標志對X86處理器不起作用。
子進程繼承父進程的錯誤模式,如果不想讓子進程繼承父進程的錯誤模式的話,可以再調用CreateProcess時設定CREATE_DEFAULT_ERROR_MODE標志。
4.1.7 進程的當前驅動器和目錄
默認情況下不提供全路徑的話,系統就會在當前驅動器和目錄中查找文件,比如CreateFile,因為驅動器和目錄是每個進程來維護的,所以某個線程改變了目錄和驅動器會改變整個進程的目錄和驅動器。
下面兩個函數讀取和設置:
DWORD GetCurrentDirectory(DWORD cchCurDir, PTSTR pszCurDir);
BOOL SetCurrentDirectory(PCTSTR pszCurDir);
4.1.8 進程的當前目錄
驅動器環境塊的格式:
=C:=C:\Utility\Bin
程序查找驅動器環境塊,如果沒有則按驅動器名查找。
子進程不能繼承父進程的驅動器塊,如果想繼承必須寫到環境變量中去。(好像是這樣,如果有不對請高人指點)。
DWORD GetFullPathName(PCTSTR pszFile, DWORD cchPath, PTSTR pszPath, PTSTR *ppszFilePart);
獲取驅動器的當前目錄,比如:
TCHAR szCurDir[MAX_PATH];
DWORD GetFullPathName(TEXT("C:"), MAX_PATH, szCurDir, NULL);
4.1.9 系統版本
DWORD GetVersion();此函數存在高地位的混論BUG,所以盡量不要使用。
BOOL GetVersion(POSVERSIONINFOEX pVersionInfomation);
typedef struct _OSVERSIONINFOEXA {
DWORD dwOSVersionInfoSize; // 在調用GetVersionEx函數之前,必必須置為sizeof(OSVERSIONINFOEX)
DWORD dwMajorVersion; // 主系統的主要版本號
DWORD dwMinorVersion; // 主系統的次要版本號
DWORD dwBuildNumber; // 當前系統的構建號
DWORD dwPlatformId; // 識別當前系統的平臺。可以使VER_PLATFORM_WIN32(WIN32),VER_PLATFORM_WIN32_WINDOWS(WINDOWS 95/WINDOWS 98),VER_PLATFORM_WIN32_NT(WINDOWS NT/WINDOWS 2000)或VER_PLATFORM_WIN32_CEHH(WINDOWS CE)
CHAR szCSDVersion[ 128 ]; // Maintenance string for PSS usage 本域包含了附加文本,用于提供關于已經安裝的操作系統的詳細信息
WORD wServicePackMajor; // 最新安裝的服務程序包的主要版本號
WORD wServicePackMinor; // 最新安裝的服務程序包的次要版本號
WORD wSuiteMask; // 用于標識系統上存在那個程序組(VER_SUITE_SMALLBUSINESS,VER_SUITE_ENTERPRISE,VER_SUITE_BACKOFFICE,VER_SUITE_COMMUNICATIONS,VER_SUITE_TERMINAL,VER_SUITE_SMALLBUSINESS_RESTRICTED,VER_SUITE_EMBEDDEDNT和VER_SUITE_DATACENTER)
BYTE wProductType; // 用于標識安裝了下面的哪個操作系統:VER_NT_WORKSTATION,VER_NT_SERVER或VER_NT_DOMAIN_CONTROLLER
BYTE wReserved; // 留作將來使用
} OSVERSIONINFOEXA, *POSVERSIONINFOEXA, *LPOSVERSIONINFOEXA;
這個是擴展版本。
4.2 CreateProcess函數
終于看到正題了~

BOOL CreateProcess(
LPCSTR lpApplicationName,
LPSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCSTR lpCurrentDirectory,
LPSTARTUPINFOA lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
);
- 進程啟動時,首先創建一個進程內核對象,該內核對象不是進程,是一個管理進程存儲進程信息的小型數據結構。(計數為1)
- 創建一個虛擬地址空間,加載可執行文件和DLL。
- 為進程創建個主線程的內核對象,和進程內核對象一樣,是用來管理和存儲線程信息的小型數據結構。(計數為1)
- 調用C/C++運行期啟動代碼,主線程開始運行,最終調用啟動函數,成功返回TRUE(未能正確加載DLL也返回TRUE,所以父進程無法查看)。
4.2.1 pszApplicationName和pszCommandLine
pszCommandLine參數:用來創建進程的命令行參數。查看第一個標記,如果沒有”.exe”會自動添加”.exe”上去。(如果pszApplicationName參數NULL)
- 包含調用進程的”.exe”文件的目錄。
- 調用進程的當前目錄。
- windows系統目錄
- windows目錄
- PATH環境變量中列出的目錄。
如果pszApplicationName參數不為NULL,系統將在當前目錄中查找.exe文件(不會自動添加“.exe”),如果找不到將失敗,此時pszCommandLine作為參數傳遞給可執行程序的進程。
4.2.2 psaProcess,psaThread和binHeritHandles
psaProcess,psaThread是進程和進程主線程內核對象的安全屬性。默認值為NULL。
binHeritHandles設置為TRUE表示父進程在創建子進程可以繼承安全屬性標志里設置為TRUE的任何可繼承的內核對象。如果設置為FALSE子進程將不繼承任何內核對象。
4.2.3 fdwCreate
用于標識標志,定義規則如何創建新進程。我一般寫默認值NULL。具體的太多了,請查看MSDN吧,不想寫了。
4.2.4 pvEnvironment
設置子進程使用的環境內存塊,一般默認值為NULL,表示子進程繼承父進程的環境塊。
PVOID GetEnvironmentString(); // 獲取當前內存塊的地址
BOOL FreeEnvironmentStrings(PTSTR pszEnvironmentBlock); // 不用的時候調用此函數釋放內存塊
4.2.5 pszCurDir
設定工作目錄和驅動器號,如果為NULL則和應用程序的目錄相同,如果設置比如以’\0’結尾的包含驅動器名的路徑。
4.2.6 psiStartInfo
typedef struct _STARTUPINFO {
DWORD cb; //(兩者兼有,控制臺和窗口程序)
LPSTR lpReserved; // (兩者兼有)保留,必須初始化為NULL
LPSTR lpDesktop; // (兩者兼有)用于標識啟動應用程序所在的桌面的名字。如果該桌面存在,新進程便與指定的桌面相關聯。如果桌面不存在,便創建一個帶有默認屬性的桌面,并使用為新進程指定的名字。如果lpDesktop是NULL(這是最常見的情況),那么該進程將與當前桌面相關聯。
LPSTR lpTitle; // (控制臺)用于設定控制臺窗口名稱。如果lpTitle是NULL,則可執行文件的名字將用作窗口名
DWORD dwX; // x,y坐標,只有當子進程用CW_USEDEFAULT作為CreateWindows的x參數來創建它的第一個重疊窗口時,才使用這兩個坐標。若是創建控制臺窗口的應用程序,這些成員用于指明控制臺窗口的左上角。
DWORD dwY;
DWORD dwXSize; //(兩者兼有)設定窗口寬度和長度,只有子進程用WM_USEDEFAULT作為CreateWIndows的nWidth參數來創建它的第一個重疊窗口時才是用這個值??刂婆_就是控制臺的寬和長
DWORD dwYSize;
DWORD dwXCountChars; //(控制臺)用于設定子應用程序控制臺的長度和寬度(字符表示)
DWORD dwYCountChars;
DWORD dwFillAttribute;// (控制臺)用于設定子應用程序的控制臺背影顏色和文本。
DWORD dwFlags; // (兩者兼有)參見下一段
WORD wShowWindow; // (窗口)用于設定子應用程序初次調用ShowWindow將SW_SHOWDEFAULT作為nCmdShow參數傳遞時,該應用程序的第一個重疊窗口應該如何出現。本成員可以是通常用于ShowWindow函數的任何一個SW_*標識符
WORD cbReserved2; // 保留,必須初始化為0
LPBYTE lpReserved2; // 保留,必須初始化為NULL
HANDLE hStdInput; // (控制臺)用于設定控制臺輸入和輸出用的緩存的句柄。默認設置hStdInput是鍵盤緩存,hStdOutput和hStdError窗口的緩存。
HANDLE hStdOutput; //
HANDLE hStdError;
} STARTUPINFO, *LPSTARTUPINFO;
設置某些值,大部分需要默認值,必須初始化為0都。
STARTUPINFO si = {sizeof(si)};
dwFlags標志,用于修改如何來創建子進程。
標志
STARTF_USESIZE 使用dwXSize和dwYSize成員
STARTF_USESHOWWINDOW 使用wShowWIndow成員
STARTF_USEPOSITION 使用dwX和dwY成員
STARTF_USECOUNTCHARS 使用dwXCountChars和dwYCountChars成員
STARTF_USEFILLATTRIBUTE 使用dwFillAttribute成員
STARTF_USESTDHANDLES 使用hStdInput,hStdOutput和hStdError成員
STARTF_RUN_FULLSCREEN 強制再x86計算機上運行的控制臺應用程序以全屏幕方式啟動運行
STARTF_FORCEONFEEDBACK 光標設置為沙漏,過了2秒如果進程沒啟動GUI,CreateProcess程序將光標設置為箭頭,5秒內顯示一個窗口,成功調用GetMessage則反復箭頭,如果沒有成功,等待5秒 變為箭頭
STARTF_FORCEOFFFEEDBACK
4.2.7 ppiProcInfo
typedef struct _PROCESS_INFORMATION {
HANDLE hProcess;
HANDLE hThread;
DWORD dwProcessId;
DWORD dwThreadId;
} PROCESS_INFORMATION, *PPROCESS_INFORMATION, *LPPROCESS_INFORMATION;
返回的分別是,子進程進程句柄,子進程中主線程的線程句柄,子進程ID,子進程中主線程的ID。
注意:
- hProcess和hThread被賦值后,內核對象的計數器分別被+1。
- 系統為每個進程和線程分配的ID值都是不同的,但是當某進程退出后,新進程很可能會使用退出進程的ID。
4.3 終止進程的運行
4.3.1 主線程的進入點函數返回
最好強力推薦使用這種方式。
- 調用C++析構函數。
- 釋放堆棧內存。
- 將進程退出代碼(進程內核對象中維護)設置為進入點函數返回值。
- 系統將進程內核對象的返回值減去1。
4.3.2 ExitProcess函數
避免使用這個方法。
VOID ExitProcess(UINT fuExitCode);
終止進程運行,并將退出碼設置為fuExitCode。
注意:
- 調用ExitProcess后,所有的代碼都將不會執行,關閉進程。
- 調用ExitProcess后,不會釋放C++析構函數資源,有系統清理進程時候直接釋放內存。
- 進程的主線程直接return退出后,啟動函數中也會調用ExitProcess函數,進程終止,其他線程也會關閉。
- 進程的主線程調用_endThreadex或者EndThread函數關閉主線程后,進程沒有被關閉,子線程繼續運行。
4.3.3 TerminiateProcess函數
能不用就別用。
BOOL TerminiateProcess(HANDLE hProcess, UINT fuExitCode;)
關閉指定為hProcess句柄的進程,推出代碼為fuExitCode。
注意:
- 關閉進程將丟失所有需要保存到硬盤的數據,因為進程關閉時候最后會自己釋放內存資源,關閉內核對象的句柄值,所以不會造成內存泄露。
- 此函數是個異步函數,它返回的時候無法知道,需要關閉的進程是否已經被強制關閉了。
4.3.4 進程終止運行時出現的情況
- 進程中剩余的所有線程全部終止運行。
- 釋放該進程引用的GDI和用戶對象,內核對象被關閉(別的進程有引用則計數器減1,如果沒有引用則關閉內核對象)。
- 推出代碼將從STILL_ACTIVE(后面章節線程將介紹該結構)改為傳遞給ExitProcess和TerminiateProcess代碼。
- 內核對象狀態變為收到通知狀態(線程中介紹),其他線程掛起,知道進程終止。
- 進程內核對象計數減去1,或者關閉。
注意:
進程內核對象的壽命可能遠遠大于進程本身,父進程保留子進程內核對象可以查看它的推出代碼調用下面函數:
BOOL GetExitCodeProcess(HANDLE hProcess, PDWORD pdwExitCode);
可以調用這個函數來判斷子進程是否關閉,如果子進程沒有關閉,它的STILL_ACTIVE標識符定義為0x103。但是這么作效率不是很高。
4.4 子進程
沒什么好說的,前面的都說了,用CloseHandle關閉子進程和子進程中主線程的句柄值來切斷父進程和子進程的所有聯系。
4.5 每局系統中運行的進程
利用ToolHelp函數族來開發管理操作系統上的進程。打算自己也寫個試試。
本文章的內容是本人學習Windows核心編程第四章后的總結,有錯誤請大家糾正,轉載注明出處:
http://www.cnblogs.com/xi52qian/
進程由兩部分組成:
- 操作系統管理進程的內核對象。存放該進程 的統計信息的地方。
- 地址空間,包含可執行模塊和DLL模塊的代碼和數據。動態分配的內存(線程堆棧和堆)。
進程是不活潑的,進程當中至少要有一個線程,每個線程要有自己的堆棧和自己的CPU寄存器。CPU通過算法給每個線程分配時間片的辦法來造成假象是在同時工作(多核通過自己的算法實現同時運行)。
4.1 編寫第一個Windiws應用程序
Windows兩種類型的程序:
- CUI程序,比如CMD.EXE等等。Microsoft Visual C++連接開關為/SUBSYSTEM:CONDOLE(程序啟動時不能創建GUI程序)。
- GUI程序,圖形用戶程序,比如Notepad,Word等等。Microsoft Visual C++連接開關為/SUBSYSTEM:WINDOWS(程序啟動時不能創建CUI程序)。
注意:倆者的概念其實是很模糊的,CUI可以創建GUI圖形界面,反之GUI程序可能用CUI程序。
Windows進入點函數(區分在于CUI和GUI程序,ANSI碼和UNICODE碼)
int WINAPI WinMain(
HINSTANCE hinstExe,
HINSTANCE,
PSTR pszCmdLine,
int nCmdShow);
int WINAPI wWinMain(
HINSTANCE hinstExe,
HINSTANCE,
PWSTR pszCmdLine,
int nCmdShow);
int __cdecl main(
int argc,
char *argv[],
char *envp[]);
int __cdecl wmain(
int argc,
wchar_t *argv[],
wchar_t *envp[]);
其實Windows程序啟動時最開始并不調用自己寫的入口函數,而是調用系統的幾個入口函數,以便可以調用malloc和free之類的函數,初始化全局和靜態C++對象等。
- 檢索指向新進程的完整命令行的指針。
- 檢索指向新進程的環境變量的指針。
- 對C/C++運行期的全局變量進行初始化。如果包含StdLib.h文件,代碼就可以訪問這些變量。
- 對C運行期的malloc和callo和其他底層輸入/輸出例程使用的內存棧進行初始化。
- 為所有全局和靜態C++類對象調用構造函數。
應用程序類型 進入點 嵌入可執行文件的啟動函數
ANSI碼GUI應用程序 WinMain WinMainCRTStattup
UNICODE碼GUI應用程序 wWinMain wWinMainCRTStattup
ANSI碼CUI應用程序 main mainCRTStattup
UNICODE碼CUI應用程序 wmain wmainCRTStattup
注意:應用程序會根據SUBSYSTEM開關來查找嵌入可執行啟動函數,如果進入點函數和啟動函數不匹配則顯示鏈接錯誤??梢詣h除SUBSYSTEM(VS Project Settings)開關,這樣應用程序會自動需找匹配的函數。
進入點函數返回時調用系統的exit函數,將返回值傳遞給它。exit函數負責下面操作:
- 調用由_onexit函數的調用而注冊的任何函數。
- 為所有全局的和靜態的C++類對象調用析構函數
- 調用操作系統的ExitProcess,并將返回值傳遞給他,關閉進程。
4.1.1 進程的實例句柄
WinMain/wWinMain函數的第一個參數表示進程加載的可執行文件的基地址/句柄。對于加載資源的調用都要使用此句柄,比如HICON LoadIcon(HINSTANCE, PCTSTR)。有的函數需要使用HMODULE,和HINSTANCE是一個意思(區分主要在于16位的操作系統中)。
HMODULE GetModuleHandle(PCTSTR pszModele);
函數作用,返回加載調用進程中的可執行文件或者DLL的基地址/句柄,參數是可執行文件或者DLL的名稱。給pszModule賦值NULL,則返回的是進程中可執行文件的句柄。
注意:如果找不到則返回NULL。如果在DLL中傳遞NULL,返回的仍然是進程加載的可執行文件的句柄。
4.1.2 進程的前一個實例句柄
第二個參數都傳遞NULL,是為16位系統所保留的。
4.1.3 進程的命令行
注意:不要試圖修改命令行內部內存的值,要使用修改先拷貝出來。
PTSTR GetCommandLine(); // 返回命令行字符串
PTSTR CommandLineToArgv(PTSTR pszCmdLine, int *pNumArgs); // 拆分命令行字符串函數
Demo:
int nNumargs;
PTSTR *ppArgv = CommandLineToArgv(GetCommandLine(), &nNumargs);
if ('x' == *ppArgv[1]) {
// TODO:
}
// 手動釋放內存,一般不需要釋放,系統會進程關閉時候自動釋放
HeapFree(GetProcessHeap, 0, ppArgv);
4.1.4 進程的環境變量
環境塊是進程地址空間中分配的內存塊每個環境塊都包含一組字符串,格式如下:
VarName1=VarVarlue1\0
VarName2=VarVarlue2\0
VarName3=VarVarlue3\0
…..
VarNameX=VarVarlueX\0
\0
注意:
- 排序必須按照字母順序。
- ‘=’號不能是變量名的一部分。
- 等號左右兩邊的空格將被算做名稱或者值。
- 最后必須加個’\0’表示結束。
- 子進程和父進程不共用環境塊,修改不會影響父/子進程。
DWORD GetEnvironmentVariable(PCTSTR pszName, PTSTR pszValue, DWORD cchValue);
pszName指變量名,pszValue指向變量值的緩存區,cchValue緩存區的大小。找不到變量名或者設置的長度不夠存放就返回0。
ExpandEnvironmentStrings(PCSTR pszSrc, PSTR pszDst, DWORD nSize);
用來用現實出可替換的環境變量的字符串。
BOOL SetEnvironmentVariable(PCTSTR pszName, PCTSTR pszValue);
設置環境變量的值,如果不存在則創建,如果存在則替換他的值。
4.1.5 進程的親緣性
子進程繼承父進程的親緣性。(具體什么意思沒明白)
4.1.6 進程的錯誤模式
進程可以設置如何處理一些錯誤。
UINT SetErrorMode(UINT fuErrorMode);
各個模式用OR連接
標志 說明
SEM_FAILCRITICALERRORS 系統不顯示關鍵錯誤句柄消息框,并將錯誤返回給調用進程
SEM_NOGOFAULTERRORBOX 系統不顯示一般保護故障消息框。本標志只應該由采用異常情況處理程序來處理一般保護(GP)故障的調式應用程式來設定
SEM_NOOPENFILEERRORBOX 當系統找不到文件時,它不顯示消息框。
SEM_NOALIGNMENTFAULTEXCEPT 系統自動排除內存沒有對其的故障,并使應用程序看不到這些故障。本標志對X86處理器不起作用。
子進程繼承父進程的錯誤模式,如果不想讓子進程繼承父進程的錯誤模式的話,可以再調用CreateProcess時設定CREATE_DEFAULT_ERROR_MODE標志。
4.1.7 進程的當前驅動器和目錄
默認情況下不提供全路徑的話,系統就會在當前驅動器和目錄中查找文件,比如CreateFile,因為驅動器和目錄是每個進程來維護的,所以某個線程改變了目錄和驅動器會改變整個進程的目錄和驅動器。
下面兩個函數讀取和設置:
DWORD GetCurrentDirectory(DWORD cchCurDir, PTSTR pszCurDir);
BOOL SetCurrentDirectory(PCTSTR pszCurDir);
4.1.8 進程的當前目錄
驅動器環境塊的格式:
=C:=C:\Utility\Bin
程序查找驅動器環境塊,如果沒有則按驅動器名查找。
子進程不能繼承父進程的驅動器塊,如果想繼承必須寫到環境變量中去。(好像是這樣,如果有不對請高人指點)。
DWORD GetFullPathName(PCTSTR pszFile, DWORD cchPath, PTSTR pszPath, PTSTR *ppszFilePart);
獲取驅動器的當前目錄,比如:
TCHAR szCurDir[MAX_PATH];
DWORD GetFullPathName(TEXT("C:"), MAX_PATH, szCurDir, NULL);
4.1.9 系統版本
DWORD GetVersion();此函數存在高地位的混論BUG,所以盡量不要使用。
BOOL GetVersion(POSVERSIONINFOEX pVersionInfomation);
typedef struct _OSVERSIONINFOEXA {
DWORD dwOSVersionInfoSize; // 在調用GetVersionEx函數之前,必必須置為sizeof(OSVERSIONINFOEX)
DWORD dwMajorVersion; // 主系統的主要版本號
DWORD dwMinorVersion; // 主系統的次要版本號
DWORD dwBuildNumber; // 當前系統的構建號
DWORD dwPlatformId; // 識別當前系統的平臺。可以使VER_PLATFORM_WIN32(WIN32),VER_PLATFORM_WIN32_WINDOWS(WINDOWS 95/WINDOWS 98),VER_PLATFORM_WIN32_NT(WINDOWS NT/WINDOWS 2000)或VER_PLATFORM_WIN32_CEHH(WINDOWS CE)
CHAR szCSDVersion[ 128 ]; // Maintenance string for PSS usage 本域包含了附加文本,用于提供關于已經安裝的操作系統的詳細信息
WORD wServicePackMajor; // 最新安裝的服務程序包的主要版本號
WORD wServicePackMinor; // 最新安裝的服務程序包的次要版本號
WORD wSuiteMask; // 用于標識系統上存在那個程序組(VER_SUITE_SMALLBUSINESS,VER_SUITE_ENTERPRISE,VER_SUITE_BACKOFFICE,VER_SUITE_COMMUNICATIONS,VER_SUITE_TERMINAL,VER_SUITE_SMALLBUSINESS_RESTRICTED,VER_SUITE_EMBEDDEDNT和VER_SUITE_DATACENTER)
BYTE wProductType; // 用于標識安裝了下面的哪個操作系統:VER_NT_WORKSTATION,VER_NT_SERVER或VER_NT_DOMAIN_CONTROLLER
BYTE wReserved; // 留作將來使用
} OSVERSIONINFOEXA, *POSVERSIONINFOEXA, *LPOSVERSIONINFOEXA;
這個是擴展版本。
4.2 CreateProcess函數
終于看到正題了~

BOOL CreateProcess(
LPCSTR lpApplicationName,
LPSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCSTR lpCurrentDirectory,
LPSTARTUPINFOA lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
);
- 進程啟動時,首先創建一個進程內核對象,該內核對象不是進程,是一個管理進程存儲進程信息的小型數據結構。(計數為1)
- 創建一個虛擬地址空間,加載可執行文件和DLL。
- 為進程創建個主線程的內核對象,和進程內核對象一樣,是用來管理和存儲線程信息的小型數據結構。(計數為1)
- 調用C/C++運行期啟動代碼,主線程開始運行,最終調用啟動函數,成功返回TRUE(未能正確加載DLL也返回TRUE,所以父進程無法查看)。
4.2.1 pszApplicationName和pszCommandLine
pszCommandLine參數:用來創建進程的命令行參數。查看第一個標記,如果沒有”.exe”會自動添加”.exe”上去。(如果pszApplicationName參數NULL)
- 包含調用進程的”.exe”文件的目錄。
- 調用進程的當前目錄。
- windows系統目錄
- windows目錄
- PATH環境變量中列出的目錄。
如果pszApplicationName參數不為NULL,系統將在當前目錄中查找.exe文件(不會自動添加“.exe”),如果找不到將失敗,此時pszCommandLine作為參數傳遞給可執行程序的進程。
4.2.2 psaProcess,psaThread和binHeritHandles
psaProcess,psaThread是進程和進程主線程內核對象的安全屬性。默認值為NULL。
binHeritHandles設置為TRUE表示父進程在創建子進程可以繼承安全屬性標志里設置為TRUE的任何可繼承的內核對象。如果設置為FALSE子進程將不繼承任何內核對象。
4.2.3 fdwCreate
用于標識標志,定義規則如何創建新進程。我一般寫默認值NULL。具體的太多了,請查看MSDN吧,不想寫了。
4.2.4 pvEnvironment
設置子進程使用的環境內存塊,一般默認值為NULL,表示子進程繼承父進程的環境塊。
PVOID GetEnvironmentString(); // 獲取當前內存塊的地址
BOOL FreeEnvironmentStrings(PTSTR pszEnvironmentBlock); // 不用的時候調用此函數釋放內存塊
4.2.5 pszCurDir
設定工作目錄和驅動器號,如果為NULL則和應用程序的目錄相同,如果設置比如以’\0’結尾的包含驅動器名的路徑。
4.2.6 psiStartInfo
typedef struct _STARTUPINFO {
DWORD cb; //(兩者兼有,控制臺和窗口程序)
LPSTR lpReserved; // (兩者兼有)保留,必須初始化為NULL
LPSTR lpDesktop; // (兩者兼有)用于標識啟動應用程序所在的桌面的名字。如果該桌面存在,新進程便與指定的桌面相關聯。如果桌面不存在,便創建一個帶有默認屬性的桌面,并使用為新進程指定的名字。如果lpDesktop是NULL(這是最常見的情況),那么該進程將與當前桌面相關聯。
LPSTR lpTitle; // (控制臺)用于設定控制臺窗口名稱。如果lpTitle是NULL,則可執行文件的名字將用作窗口名
DWORD dwX; // x,y坐標,只有當子進程用CW_USEDEFAULT作為CreateWindows的x參數來創建它的第一個重疊窗口時,才使用這兩個坐標。若是創建控制臺窗口的應用程序,這些成員用于指明控制臺窗口的左上角。
DWORD dwY;
DWORD dwXSize; //(兩者兼有)設定窗口寬度和長度,只有子進程用WM_USEDEFAULT作為CreateWIndows的nWidth參數來創建它的第一個重疊窗口時才是用這個值。控制臺就是控制臺的寬和長
DWORD dwYSize;
DWORD dwXCountChars; //(控制臺)用于設定子應用程序控制臺的長度和寬度(字符表示)
DWORD dwYCountChars;
DWORD dwFillAttribute;// (控制臺)用于設定子應用程序的控制臺背影顏色和文本。
DWORD dwFlags; // (兩者兼有)參見下一段
WORD wShowWindow; // (窗口)用于設定子應用程序初次調用ShowWindow將SW_SHOWDEFAULT作為nCmdShow參數傳遞時,該應用程序的第一個重疊窗口應該如何出現。本成員可以是通常用于ShowWindow函數的任何一個SW_*標識符
WORD cbReserved2; // 保留,必須初始化為0
LPBYTE lpReserved2; // 保留,必須初始化為NULL
HANDLE hStdInput; // (控制臺)用于設定控制臺輸入和輸出用的緩存的句柄。默認設置hStdInput是鍵盤緩存,hStdOutput和hStdError窗口的緩存。
HANDLE hStdOutput; //
HANDLE hStdError;
} STARTUPINFO, *LPSTARTUPINFO;
設置某些值,大部分需要默認值,必須初始化為0都。
STARTUPINFO si = {sizeof(si)};
dwFlags標志,用于修改如何來創建子進程。
標志
STARTF_USESIZE 使用dwXSize和dwYSize成員
STARTF_USESHOWWINDOW 使用wShowWIndow成員
STARTF_USEPOSITION 使用dwX和dwY成員
STARTF_USECOUNTCHARS 使用dwXCountChars和dwYCountChars成員
STARTF_USEFILLATTRIBUTE 使用dwFillAttribute成員
STARTF_USESTDHANDLES 使用hStdInput,hStdOutput和hStdError成員
STARTF_RUN_FULLSCREEN 強制再x86計算機上運行的控制臺應用程序以全屏幕方式啟動運行
STARTF_FORCEONFEEDBACK 光標設置為沙漏,過了2秒如果進程沒啟動GUI,CreateProcess程序將光標設置為箭頭,5秒內顯示一個窗口,成功調用GetMessage則反復箭頭,如果沒有成功,等待5秒 變為箭頭
STARTF_FORCEOFFFEEDBACK
4.2.7 ppiProcInfo
typedef struct _PROCESS_INFORMATION {
HANDLE hProcess;
HANDLE hThread;
DWORD dwProcessId;
DWORD dwThreadId;
} PROCESS_INFORMATION, *PPROCESS_INFORMATION, *LPPROCESS_INFORMATION;
返回的分別是,子進程進程句柄,子進程中主線程的線程句柄,子進程ID,子進程中主線程的ID。
注意:
- hProcess和hThread被賦值后,內核對象的計數器分別被+1。
- 系統為每個進程和線程分配的ID值都是不同的,但是當某進程退出后,新進程很可能會使用退出進程的ID。
4.3 終止進程的運行
4.3.1 主線程的進入點函數返回
最好強力推薦使用這種方式。
- 調用C++析構函數。
- 釋放堆棧內存。
- 將進程退出代碼(進程內核對象中維護)設置為進入點函數返回值。
- 系統將進程內核對象的返回值減去1。
4.3.2 ExitProcess函數
避免使用這個方法。
VOID ExitProcess(UINT fuExitCode);
終止進程運行,并將退出碼設置為fuExitCode。
注意:
- 調用ExitProcess后,所有的代碼都將不會執行,關閉進程。
- 調用ExitProcess后,不會釋放C++析構函數資源,有系統清理進程時候直接釋放內存。
- 進程的主線程直接return退出后,啟動函數中也會調用ExitProcess函數,進程終止,其他線程也會關閉。
- 進程的主線程調用_endThreadex或者EndThread函數關閉主線程后,進程沒有被關閉,子線程繼續運行。
4.3.3 TerminiateProcess函數
能不用就別用。
BOOL TerminiateProcess(HANDLE hProcess, UINT fuExitCode;)
關閉指定為hProcess句柄的進程,推出代碼為fuExitCode。
注意:
- 關閉進程將丟失所有需要保存到硬盤的數據,因為進程關閉時候最后會自己釋放內存資源,關閉內核對象的句柄值,所以不會造成內存泄露。
- 此函數是個異步函數,它返回的時候無法知道,需要關閉的進程是否已經被強制關閉了。
4.3.4 進程終止運行時出現的情況
- 進程中剩余的所有線程全部終止運行。
- 釋放該進程引用的GDI和用戶對象,內核對象被關閉(別的進程有引用則計數器減1,如果沒有引用則關閉內核對象)。
- 推出代碼將從STILL_ACTIVE(后面章節線程將介紹該結構)改為傳遞給ExitProcess和TerminiateProcess代碼。
- 內核對象狀態變為收到通知狀態(線程中介紹),其他線程掛起,知道進程終止。
- 進程內核對象計數減去1,或者關閉。
注意:
進程內核對象的壽命可能遠遠大于進程本身,父進程保留子進程內核對象可以查看它的推出代碼調用下面函數:
BOOL GetExitCodeProcess(HANDLE hProcess, PDWORD pdwExitCode);
可以調用這個函數來判斷子進程是否關閉,如果子進程沒有關閉,它的STILL_ACTIVE標識符定義為0x103。但是這么作效率不是很高。
4.4 子進程
沒什么好說的,前面的都說了,用CloseHandle關閉子進程和子進程中主線程的句柄值來切斷父進程和子進程的所有聯系。
4.5 每局系統中運行的進程
利用ToolHelp函數族來開發管理操作系統上的進程。打算自己也寫個試試。
本文章的內容是本人學習Windows核心編程第四章后的總結,有錯誤請大家糾正,轉載注明出處:
http://www.cnblogs.com/xi52qian/
頭文件 <iostream>
一. 對終端的操作
相關頭文件#include <iostream>
1. 輸入istream
2. 輸出ostream
3. iostream繼承istream和ostream 所以它具有輸入輸出功能。
為了方便這個庫定義了下列三個標準流對象:
1. cin 代表標準輸入istream類對象一般地cin使我們能夠從用戶終端讀入數據。
2. cout 代表標準輸出ostream類對象一般地cout使我們能夠向用戶終端寫數據。
3. cerr 代表標準錯誤ostream類對象一般地cerr是導出程序錯誤消息的地方。
另外,輸出主要由重載的左移操作符<< 來完成類似地輸入主要由重載的右移操作符>>來完成。
Demo1:
#include <iostream>
#include <string>
int main() {
string in_string;
// 向用戶終端寫字符串
std::cout << "Please enter your name: ";
// 把用戶輸入的讀取到 in_string 中
std::cin >> in_string;
if ( in_string.empty() )
// 產生一個錯誤消息輸出到用戶終端
std::cerr << "error: input string is empty!\n";
else std::cout << "hello, " << in_string << "!\n";
}
二. 對文件的操作
相關頭#include <fstream>
1. ifstream 從istream 派生把一個文件綁到程序上從文件讀取數據用來輸入。
2. ofstream 從ostream 派生把一個文件綁到程序上用來向文件寫入數據。
3. fstream 從iostream 派生把一個文件綁到程序上用來輸入和輸出。
注:由于在fstream 頭文件中也包含了iostream 頭文件所以我們不需要同時包含這兩個文
件C++對于文件的輸入輸出也支持同樣的輸入和輸出操作符。
Demo2:
#include <fstream>
#include <string>
#include <vector>
#include <algorithm>
int main()
{
string ifile;
cout << "Please enter file to sort: ";
cin >> ifile;
// 構造一個 ifstream 輸入文件對象
ifstream infile( ifile.c_str() );
if( ! infile ) {
cerr << "error: unable to open input file: " << ifile << endl;
return -1;
}
string ofile = ifile + ".sort";
// 構造一個 ofstream 輸出文件對象
ofstream outfile( ofile.c_str() );
if( !outfile ) {
cerr << "error: unable to open output file: " << ofile << endl;
return -2;
}
string buffer;
vector<string> text;
int cnt = 1;
while ( infile >> buffer ) {
text.push_back( buffer );
cout << buffer << ( cnt++ % 8 ? " " : "\n" );
}
sort( text.begin(), text.end() );
// ok: 把排序后的詞打印到 outfile
vector<string>::iterator iter = text.begin();
for ( cnt = 1; iter != text.end(); ++iter, ++cnt )
outfile << *iter << (cnt%8 ? " " : "\n" );
return 0;
}
三. 對字符流操作
相關的頭文件#include <sstream>
1 istringstream 從istream 派生從一個字符串中讀取數據。
2 ostringstream 從ostream 派生寫入到一個字符串中。
3 stringstream 從iostream 派生從字符串中讀取或者寫入到字符串中。
Demo3:
#include <sstream>
string program_name( "our_program" );
string version( "0.01" );
// ...
string mumble( int *array, int size )
{
if ( ! array ) {
ostringstream out_message;
out_message << "error: "
<< program_name << "--" << version
<< ": " << __FILE__ << ": " << __LINE__
<<" -- ptr is set to 0; "
<< " must address some array.\n";
// 返回底層 string 對象
return out_message.str();
}
四. wchar_t型
支持wchar_t類型的流讀寫操作
wcin wcout wcerr wiostream 等等....
20.1 輸出操作符<<
1. 支持所有的內置數據類型 包括string ,const char* 和complex。以及函數的調用。
2. endl等價于輸出換行操作符,然后再刷新緩存區。cout << '\n' << flush;
3. <<可以連接使用,因為operator<<的返回值是ostream&。
4. cout << p << &i << endl;會輸出指針的地址(p是int *,i是int)。如果是const char * pcStr不會輸出指針地址值,
輸出的是字符串。轉換為cout << static_cast<void *>(const_cast<char *>(pcStr));
5. <<運算符的優先級高于?:所以和?:使用時注意加括號。
6. ostream_iterator和cout的使用。
Demo4:
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
string pooh_pals[] = {
"Tigger", "Piglet", "Eeyore", "Rabbit"
};
int main()
{
vector<string> ppals( pooh_pals, pooh_pals+4 );
vector<string>::iterator iter = ppals.begin();
vector<string>::iterator iter_end = ppals.end();
cout << "These are Pooh's pals: ";
// 把每個元素拷貝到 cout ...
ostream_iterator< string > output( cout, " " );
copy( iter, iter_end, output );
cout << endl;
}
7. cout << 遇到‘\0’會定制輸出。
20.2 輸入操作符>>
1. while(cin >> 變量)的時候有兩種情況會讓循環停止(false):
(1) 讀到文件結束在這種情況下我們已經正確地讀完文件中所有的值。
(2) 遇到一個無效的值比如3.14159小數點是非法的1e-1字符文字e是非法的或者一般的任意字符串文字在讀入一個無效值的
情況下istream對象被放置到一種錯誤的狀態中并且對于值的所有讀入動作都將停止。
2. 預定義的輸入操作符可以接受任何的內置數據類型包括C 風格字符由以及標準庫string和complex 類類型。
3. 缺省情況下輸入操作符丟棄任何中間空白空格制表符換行符走紙以及回車。
Demo5:
#include <iostream>
#include <string>
int main()
{
int item_number;
string item_name;
double item_price;
cout << "Please enter the item_number, item_name, and price: " << endl;
cin >> item_number;
cin >> item_name;
cin >> item_price;
cout << "The values entered are: item# "
<< item_number << " "
<< item_name << " @$"
<< item_price << endl;
}
4. 如果希望讀入空白符號可以利用cin.get()方法一個個讀入,或者設置skipws和noskipws選項(后面介紹)。
5. isream_iterator和cin關聯使用。
Demo6:
#include <algorithm>
#include <string>
#include <vector>
#include <iostream>
int main()
{
istream_iterator< string > in( cin ), eos ;
vector< string > text ;
// 從標準輸入向 text 拷貝值
copy( in , eos , back_inserter( text ) ) ;
sort( text.begin() , text.end() ) ;
// 刪除所有重復的值
vector< string >::iterator it ;
it = unique( text.begin() , text.end() ) ;
text.erase( it , text.end() ) ;
// 顯示結果 vector
int line_cnt = 1 ;
for ( vector< string >::iterator iter = text.begin();
iter != text.end() ; ++iter , ++line_cnt )
cout << *iter
<< ( line_cnt % 9 ? " " : "\n" ) ;
cout << endl;
}
20.2.1 字符串輸入
1. setw函數:防止讀入字符串益處(char[4],輸入4個以上字節),while ( cin >> setw( bufSize ) >> buf )
這里bufSize是字符數組buf的長度setw()把長度等于或大于bufSize 的字符串分成最大長度為bufSize - 1的兩個
或多個字符串,在每個新串的末尾放一個空字符為了使用setw()要求程序包含iomanip頭文件#include <iomanip>。
2. 用string沒有char *那些長度和內存溢出問題,更容易使用。
20.3 其他輸入輸出操作符
一. istream的成員get()一次讀入一個字節。屬于istream
1. get(char& ch)從輸入流中提取一個字符包括空白字符并將它存儲在ch中它返回被應用的istream對象。
與之對應的是ostream的put()方法,每次讀入一個字節,然后返回ostream。
2. get()的第二個版本也從輸入流讀入一個字符區別是它返回該字符值而不是被應用的istream 對象它的返回類型
是int 而不是char 因為它也返回文件尾的標志end-of-file該標志通常用-1 來表示以便與字符集區分開為測試返回
值是否為文件尾我們將它與iostream 頭文件中定義的常量EOF 做比較。
Demo7:
#include <iostream>
int main()
{
int ch;
// 或使用:
// while (( ch = cin.get() ) && ch != EOF)
while (( ch = cin.get()) != EOF)
cout.put(ch);
return 0;
}
3. get(char *sink, streamsize size, char delimiter='\n')
(1) sink 代表一個字符數組用來存放被讀取到的字符。
(2) size 代表可以從istream 中讀入的字符的最大數目。
(3) delimiter 表示如果遇到它就結束讀取字符的動作delimiter 字符本身不會被讀入而是留在istream 中作為istream 的
下一個字符一種常見的錯誤是在執行第二個get()之前忘了去掉delimiter。
二. ignore( streamsize length = 1, int delim = traits::eof )函數: 屬于istream
我們用istream 成員函數ignore()來去掉delimiter
缺省情況下換行符被用作delimiter。
三. gcount()函數: 屬于istream
它返回由最后的get()或getline()調用實際提取的字符數。
Demo8:
#include <iostream>
int main()
{
const int max_line = 1024;
char line[ max_line ];
while ( cin.get( line, max_line ))
{
// 最大讀取數量 max_line - 1, 也可以為 null
int get_count = cin.gcount();
cout << "characters actually read: "
<< get_count << endl;
// 處理每一行
// 如果遇到換行符
// 在讀下一行之前去掉它
if ( get_count & max_line-1 )
cin.ignore();
}
}
四. getline(char *sink, streamsize size, char delimiter='\n')屬于istream
五. write( const char *sink, streamsize length )屬于ostream
提供了另外一種方法可以輸出字符數組”它不是輸出直到終止空字符為止的所有字符“而是輸出某個長度的字符序列包括內含的
空字符它的函數。length 指定要顯示的字符個數write()返回當前被調用的ostream 類對象。
六. read( char* addr, streamsize size )屬于istream
read()從輸入流中提取size 個連續的字節并將其放在地址從addr 開始的內存中gcount()返回由最后一個read()調用提取的字
節數而read()返回當前被調用的istream 類對象。
Demo9:
#include <iostream>
int main()
{
const lineSize = 1024;
int lcnt = 0; // 讀入多少行
int max = -1; // 最長行的長度
char inBuf[ lineSize ];
// 讀取 1024 個字符或者遇到換行符
while (cin.getline( inBuf, lineSize ))
{
// 實際讀入多少字符
int readin = cin.gcount();
// 統計: 行數最長行
++lcnt;
if ( readin > max )
max = readin;
cout << "Line #" << lcnt
<< "\tChars read: " << readin << endl;
cout.write( inBuf, readin).put('\n').put('\n');
}
cout << "Total lines read: " << lcnt << endl;
cout << "Longest line read: " << max << endl;
}
七. getline( istream &is, string str, char delimiter );
這個getline()實例的行為如下讀入最大數目為str::max_size-1 個字符如果輸入序列超出這個限制則讀操作失敗
并且istream 對象被設置為錯誤狀態否則當讀到delimiter 它被從istream 中丟棄但沒有被插入到string 中或遇
到文件結束符時輸入結束。
八. putback( char c ); 將字符放回 iostream。
九. unget();往回重置下一個 istream 項。
十. peek(); 返回下一個字符或 EOF,但不要提取出來。
Demo10:
char ch, next, lookahead;
while ( cin.get( ch ))
{
switch (ch) {
case '/':
// 是注釋行嗎? 用 peek() 看一看:
// 是的? ignore() 余下的行
next = cin.peek();
if ( next == '/' )
cin.ignore( lineSize, '\n' );
break;
case '>':
// 查找 >>=
next = cin.peek();
if ( next == '>' ) {
lookahead = cin.get();
next = cin.peek();
if ( next != '=' )
cin.putback( lookahead );
}
20.4 重載輸出操作符<<
輸出操作符是一個雙目操作符它返回一個ostream 引用重載定義的通用框架如下
// 重載 output 操作符的通用框架
ostream&
operator <<( ostream& os, const ClassType &object )
{
// 準備對象的特定邏輯
// 成員的實際輸出
os << // ...
// 返回 ostream 對象
return os;
}
注:因為第一個實參是一個ostream 引用所以輸出操作符必須定義為非成員函數,當輸出操作符要求訪問非公有成員
時必須將它聲明為該類的友元。
20.5 重載輸入操作符>>
1 由于不正確的格式而導致失敗istream 應該把狀態標記為fail。setstate( ios_base::failbit )。
2 對于錯誤狀態中的iostream 插入和提取操作沒有影響。
Demo11:
#include <iostream>
#include "WordCount.h"
/* 必須修改 WordCount, 指定輸入操作符為友元
class WordCount {
friend ostream& operator<<( ostream&, const WordCount& );
friend istream& operator>>( istream&, WordCount& );
*/
istream&
operator>>( istream &is, WordCount &wd )
{
/* WordCount 對象被讀入的格式:
* <2> string
* <7,3> <12,36>
*/
int ch;
/* 讀入小于符號, 如果不存在
* 則設置 istream 為失敗狀態并退出
*/
if ((ch = is.get()) != '<' )
{
is.setstate( ios_base::failbit );
return is;
}
// 讀入多少個
int occurs;
is >> occurs;
// 取 >; 不檢查錯誤
while ( is && (ch = is.get()) != '>' );
is >> wd._word;
// 讀入位置
// 每個位置的格式: < line, col >
for ( int ix = 0; ix < occurs; ++ix )
{
int line, col;
// 提取值
while (is && (ch = is.get())!= '<' );
is >> line;
while (is && (ch = is.get())!= ',' );
is >> col;
while (is && (ch = is.get())!= '>' );
wd.occurList.push_back( Location( line, col ));
}
return is;
}
20.6 文件輸入和輸出
包含頭文件#include <fstream>
1. ofstream的構造函數要制定打開模式
輸出模式ios_base::out 或附加模式ios_base::app 等等。在缺省情況下ostream文件以輸出模式打開。
注:如果在輸出模式下打開已經存在的文件則所有存儲在該文件中的數據都將被丟棄如果我們希望增加而不是替換現
有文件中的數據則應該以附加模式打開文件于是新寫到文件中的數據將添加到文件尾部在這兩種模式下如果文件不存
在程序都會創建一個新文件。
2. 判斷是否打開if (!outFile /*ofstream對象*/)
3. 因為ofstream派生于ostream,所以ofstream擁有ostream的操作,比如put()等等。
4. 自定義<<操作輸入到ofstream中。
5. 用ifstream讀入一個文件,派生于istream,所有擁有istream的操作,比如get()等等。
6. close()函數斷開和文件的關聯。
7. fstream派生于iostream,它具有對文件的讀寫操作。
8. seekg和seekp標記當前位置,g是在讀文件中使用,p再寫文件中使用。
注:第二個參數標示定位標記:
(1) ios_base::beg 文件的開始
(2) ios_base::cur 文件的當前位置
(3) ios_base::end 文件的結尾
9. tellg()或tellp()返回當前位置,g和p意義同seekg和seekp。返回值是ios_base::pos_type。
10. clear()函數:清除狀態。
20.7 條件狀態
一. 條件狀態
1 如果一個流遇到文件結束符則eof()返回true。
2 如果試圖做一個無效的操作比如seeking 重定位操作超出了文件尾則bad()返回true,一般地這表示該流由于某種
未定義的方式而被破壞了。
3 如果操作不成功比如打開一個文件流對象失敗或遇到一種無效的輸入格式則fail()
返回true 例如
ifstream iFile( filename, ios_base::in );
if ( iFile.fail() ) // 不能打開
error_message( ... );
4 如果其他條件都不為true 則good()返回true
二. 改變狀態
1. clear()函數,狀態變為顯示。
2. setstate()函數,添加狀態。參數設置為:
ios_base::badbit
ios_base::eofbit
ios_base::failbit
ios_base::goodbit
3. rdstate()獲取成員狀態,返回值ios_base::iostate。
20.8 string 流
1. 包括頭文件#include <sstream>
2. str()返回與ostringstream 類對象相關聯的string 對象。
20.9 格式狀態
操 作 符 含 義
boolalpha 把true 和false 表示為字符串
*noboolalpha 把true 和false 表示為0 1
showbase 產生前綴指示數值的進制基數
*noshowbase 不產生進制基數前綴
showpoint 總是顯示小數點
*noshowpoint 只有當小數部分存在時才顯示小數點
Showpos 在非負數值中顯示+
*noshowpos 在非負數值中不顯示+
*skipws 輸入操作符跳過空白字符
noskipws 輸入操作符不跳過空白字符
uppercase 在十六進制下顯示0X 科學計數法中顯示E
*nouppercase 在十六進制下顯示0x 科學計數法中顯示e
*dec 以十進制顯示
hex 以十六進制顯示
oct 以八進制顯示
left 將填充字符加到數值的右邊
right 將填充字符加到數值的左邊
Internal 將填充字符加到符號和數值的中間
*fixed 以小數形式顯示浮點數
scientific 以科學計數法形式顯示浮點數
flush 刷新ostream 緩沖區
ends 插入空字符然后刷新ostream 緩沖區
endl 插入換行符然后刷新ostream 緩沖區
ws 吃掉 空白字符
// 以下這些要求 #include <iomanip>
setfill(ch) 用ch 填充空白字符
setprecision(n) 將浮點精度設置為n
setw(w) 按照w 個字符來讀或者寫數值
setbase(b) 以進制基數b 輸出整數值
注*表示缺省的流狀態
20.10 強類型庫
iostream庫是強類型的例如試圖從一個ostream 讀數據或者寫數據到一個istream都會在編譯時刻被捕獲到并標記為類型違例。
3.1 什么是內核對象
內核對象就是內核中的一塊內存,是一個結構,并且只能由內核對象訪問,應用程序只能通過調用Windows提供的函數來操作內核對象。每個內核對象都有相同的部分比如安全屬性和使用計數器。
3.1.1 內核對象的使用計數
內核對象中的使用計數和進程無關,當進程第一次創建某個內核對象時候使用計數變為1,當另一個進程也調用此內核對象時計數變為2。當進程釋放時或者關閉內核對象時(CloseHandle),內核的使用計數減去1,如果使用計數不為0的話,內核不會釋放此內核對象。
3.2.2 安全性
內核對象能夠得到安全描述符的保護,安全描述符定義了誰能夠創建,訪問和使用該對象,一般在服務器代碼中使用,客戶端可以忽略。
所有創建內核對象的函數的參數都有一個指向SECURITY_ATTRIBUTES結構的指針。
typedef struct _SECURITY_ATTRIBUTES {
DWORD nLength;
LPVOID lpSecurityDescriptor;
BOOL bInheritHandle;
} SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;
只有lpSecurityDescriptor成員和安全屬性有關。一般此參數傳遞NULL,表示默認的安全描述。
如果需要:
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = FALSE;
HANDLE h = CreateMutex(&sa, FALSE, "XI");
其余進程可用用OpenMutex函數打開,如果權限可以就返回句柄,如果失敗返回NULL,GetLastError被設置為ERROR_ACCESS_DENIED。
Windows除了內核對象之外還有GDI和用戶對象,區分它們的簡單辦法就是,創建函數中帶有安全描述符參數的就是內核對象。
3.2 進程的內核對象句柄表
索引 內核對象內存塊得指針 訪問屏蔽(標志位的DWORD) 標志(標志位的DWORD)
1 0x???????? 0x???????? 0x????????
2 0x???????? 0x???????? 0x????????
… … … …
3.2.1 創建內核對象
調用Create&函數族來創建相應的內核對象,返回的是內核對象的句柄(也有個說法就是句柄表的索引),如果創建失敗一般會返回0(NULL),也有的會返回INVALID_HANDLE_VALUE=-1,比如CreateFile失敗后會返回后者,失敗的原因有可能是內存不足或者是安全問題等等。其他對內核操作的函數都需要此句柄值作為參數傳遞進去,如果傳遞一個無效的句柄進去,那么GetLastError函數的值將被置為6(ERROR_INVALID_HANDLE)。
3.2.2 關閉內核對象
BOOL CloseHandle(HANDLE hobj);
調用此函數,系統會了清理進程的句柄表中的對應項目,如果使用計數器為0,內核釋放該內核對象的資源,如果使用計數器不為0,說明其他進程還在使用此內核對象,則不釋放資源。
當進程忘記調用CloseHandle函數,可能造成內存泄露,但是當進程結束的時候資源一樣會被釋放。
如果傳遞的參數無效,則函數返回FALSE,并且GetLastError函數的值被設置成ERROR_INVALID_HANDLE。如果是DEBUG階段,則返回錯誤信息。
3.3 跨越進程邊界共享內核對象
3.3.1 對象句柄的繼承性
- 在父進程創建子進程的時候,將參數SECURITY_ATTRIBUTES結構的Inherithandle字段設置為TRUE的話,再父進程句柄表中標示該內核對象的項的標志位的值將會變成TRUE,標示該內核對象是可以讓子進程繼承的,具有可繼承性(僅僅標示 該句柄值具有可繼承性,而內核對象沒有可繼承性)。
- 將CreateProcess的參數bInherithandle參數的值設置為TRUE,標示創建的進程可以繼承有繼承性的父進程句柄。
- 子進程創建后不會先加載程序,它先搜索父進程的句柄表將有繼承性的項目原封不動的拷貝給自己(索引也沒有變,所以句柄值也不變)。
- 父進程可以有3種方式將句柄值傳遞給子進程,參數傳遞,進程間通信和環境變量(GetEnvironmentVariavle函數解析)。
BOOL
WINAPI
CreateProcess(
__in_opt LPCSTR lpApplicationName,
__inout_opt LPSTR lpCommandLine,
__in_opt LPSECURITY_ATTRIBUTES lpProcessAttributes,
__in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes,
__in BOOL bInheritHandles,
__in DWORD dwCreationFlags,
__in_opt LPVOID lpEnvironment,
__in_opt LPCSTR lpCurrentDirectory,
__in LPSTARTUPINFOA lpStartupInfo,
__out LPPROCESS_INFORMATION lpProcessInformation
);
注意:
子進程再創建其子進程,如果滿足上面方式,可以繼續繼承。
如果父進程再創建子進程后,再創建句柄,子進程不會被繼承。
3.3.2 改變句柄標志
BOOL SetHandleInformation(HANDLE hObject, DWORD dwMask, DWORD dwFlags);
改變句柄的標志,目前可改變的標志有兩種
#define HANDLE_FLAG_INHERIT 0x00000001 // 繼承標志
#define HANDLE_FLAG_PROJECT_FROM_CLOSE 0x00000001 // 保護不允許關閉句柄標志
可以用OR操作同時設置2個標志。第一個參數是要設置的句柄值,第二個就是要改變的標志,第三個參數是將標志改編成什么值。
BOOL GetHandleInformation(HANDLE hObkect, PDWORD pdwFlags);
獲取當前句柄的標志的值。
// 設置句柄值可繼承:
SetHandleInformation(hObject, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
// 設置句柄不可繼承:
SetHandleInformation(hObject, HANDLE_FLAG_INHERIT, 0);
// 設置句柄值不可關閉,受保護:
SetHandleInformation(hObject, HANDLE_FLAG_PROJECT_FROM_CLOSE, HANDLE_FLAG_PROJECT_FROM_CLOSE);
// 設置句柄值可關閉,不受保護:
SetHandleInformation(hObject, HANDLE_FLAG_PROJECT_FROM_CLOSE, 0);
3.3.3 命名對象
創建內核對象函數族Create&中的最后一個參數是pszName,該參數是如果傳遞NULL,表示是匿名內核對象,可以通過其他倆種方式來使用其他進程的內核對象。當pszName參數傳遞以’\0’(最多長度為MAX_PATH 260字符)結尾的字符串時,表示啟用命名對象,比如進程A調用CreateMutex(NULL, FALSE, “XI”)的時候,他將創建內核對象名字為“XI”,之后某一時刻如果進程B也調用CreateMutex(NULL, FALSE, “XI”)函數他將經過以下幾步:
- 判斷內核對象名稱是否相同。
- 判斷內核對象類型是否相同,如果名字相同但是類型不相同則Create&函數族返回NULL,GetLastError函數值為6(ERROR_INVALID_HANDLE)。
- 判斷安全性,返回同2步,GetLastError值同2步。
- 如果驗證通過則返回句柄(返回句柄的值和該內核對象其他句柄的值不一定相同),GetLastError的值等于ERROR_ALREADY_EXISTS。
也可以用Open&函數族來打開已經創建的句柄,成功后GetLastError也不會被設置。具體如下
HANDLE Open&(DWORD, BOOL, PCSTR);
第一個參數:表示訪問權限。
第二個參數:表示新創建的句柄是否有繼承性(注意不是內核對象?。?。
第三個參數:不能傳遞NULL。如果該句柄不存在則返回NULL,GetLastError被設置為2(ERROR_FILE_NOT_FOUND)。
3.3.4 終端服務器的名字空間
Globad,Local,Session程序保留關鍵字,具體的沒弄明白,理解的就是說當服務器的時候,客戶端可以訪問以這些名字開頭的內核對象。
3.3.5 復制對象句柄
BOOL DuplicateHandle(
HANDLE hSourceProcessHandle,
HANDLE hSourceHandle,
HANDLE TargetProcessHandle,
PHANDLE phTargetHandle,
DWORD dwDesiredAccess,
BOOL bInheritHandle,
DWORD dwOptions);
執行DuplicateHandle函數的進程為ProcessC,原進程為ProcessS,目標進程為ProcessT。則hSourceProcessHandle為進程ProcessS的進程句柄,TargetProcessHandle為進程ProcessT的進程句柄,ProcessC將句柄hSourceHandle從ProcessC拷貝到ProcessT中,值存在phTargetHandle中,dwDesiredAccess新句柄的反問權限,bInheritHandle新句柄的繼承性,參數dwOptions有兩種類型分別是:
DUPLICATE_SAME_ACCESS忽略參數dwDesiredAccess,新句柄和原進程句柄具有相同的反問權限。
DUPLICATE_CLOSE_SOURCE關閉ProcessS中的拷貝句柄,內核對象的計數不變。
HANDLE hObjProcessS = CreateMutex(NULL, FALSE, NULL);
HANDLE hProcessT = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessIdT);
HANDLE hObjProcessT;
DuplicateHandle(GetCurrentProcess(), hObjProcessS, hProcessT , &hObjProcessT, 0, FALSE, DUPLICATE_SAME_ACCESS);
CloseHandle(hObjProcessS);
CloseHandle(hProcessT);
注意:
一般DuplicateHandle函數沒有在三個進程中使用,因為很難知道原進程的句柄值。
要使用IPC機制通知目標進程,新句柄已經拷貝過去。