進程由兩部分組成:
- 作業系統管理進程的核心對象。存放該進程 的統計資訊的地方。
- 地址空間,包含可執行模組和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/