函數原型 BOOL CreateProcess數組
(安全
LPCTSTR lpApplicationName,數據結構
LPTSTR lpCommandLine,ide
LPSECURITY_ATTRIBUTES lpProcessAttributes。函數
LPSECURITY_ATTRIBUTES lpThreadAttributes,this
BOOL bInheritHandles,編碼
DWORD dwCreationFlags,spa
LPVOID lpEnvironment,操作系統
LPCTSTR lpCurrentDirectory,命令行
LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
);
CreateProcess函數
CreateProcess函數用於建立進程:
線程調用CreateProcess時,系統會建立一個進程內核對象,將其引用計數初始化爲1(進程內核對象並非進程自己,它只是操做系統用來管理進程的數據結構,其中包含了進程的一些統計信息)。而後系統爲新進程開闢虛擬地址空間,並將可執行文件的代碼和數據以及所需的DLL裝載到該地址空間中。接着系統爲進程主線程建立線程內核對象,並將其引用計數初始爲1(同進程同樣,線程內核對象也不是線程自己,並且操做系統用來管理線程的數據結構)。主線程將連接器設置的入口點函數做爲C/C++運行時啓動函數調用,這些啓動函數最終又調用代碼中的入口點函數如WinMain、wWinMain、main和 wmain。當操做系統成功建立了新的進程和主線程後,CreateProcess返回TRUE。以上是CreateProcess的簡要介紹,下面咱們來詳細討論它的參數。
pszApplicationName和pszCommandLine
pszApplicationName和pszCommandLine分別表示進程使用的可執行文件名和向其傳遞的命令行字符串,咱們先來看看 pszCommandLine參數。注意pszCommandLine是PTSTR,這意味着你必須爲其傳遞指向很是量字符串的地址。 CreateProcess內部會更改向其傳遞的命令行字符串,但在CreateProcess返回以前,它會將該字符串恢復原樣。這一點是很是重要的,由於若是你向CreateProcess傳遞的命令行字符串位於進程的只讀存儲區,就會發生Access Violation錯誤。好比,下面的代碼執行時會觸發Access Violation,由於微軟的C/C++編譯器會把常量字符串放入只讀存儲區(注意早期的微軟C/C++編譯器會將常量字符串放在可讀寫存儲區,所以下面的代碼在舊的編譯環境下不會出錯):
解決這個問題的方法很簡單,將命令行字符串複製到臨時緩衝區既可,以下所示:
微軟在其C++編譯器選項中提供了/GF開關,/GF打開時,程序中全部用到的常量字符串將只維護單一副本,且位於只讀存儲部分。在調用 CreateProcess時,開發人員應該打開/GF開關並使用緩衝區。咱們但願微軟在將來版本的Windows中會改進CreateProcess,使其接受常量字符串做爲命令行參數,並在其內部分配/釋放臨時緩衝區而不是讓API調用者來作。另外,假如你使用常量ANSI字符串做爲 CreateProcess參數,並不會發生Access Violation錯誤,咱們在前面的章節已經提到過,許多WinAPI函數的ANSI版本會將ANSI參數轉換爲UNIDOE編碼後調用其 Unicode版本,CreateProcess會把ANSI字符串轉換爲Unicode編碼後放在臨時緩衝區,並調用Unicode版的 CreateProcess,所以不會觸發Access Violation。
pszCommandLine參數指定了 CreateProcess建立新進程所需的完整命令行。當CreateProcess解析該參數時,它會檢查命令行參數中的第一個標記,並將其做爲進程要執行的可執行文件名,若是該文件名沒有指定後綴,函數將把它看成exe文件。CreateProcess會按下面的順序查找該文件:
1. 包含當前進程可執行文件的目錄
2. 當前進程的當前目錄
3. Windows系統目錄,既GetSystemDirectory返回的目錄
4. Windows目錄
5. PATH環境變量列出的目錄
固然,若是文件名包含了完整路徑,系統將會在該路徑中查找文件而不會再作上面的搜索。若是系統找到了可執行文件,它會建立一個新的進程並把可執行文件的代碼和數據映射到進程的地址空間,而後調用CRT啓動函數(linker選項卡中的入口點函數),接着CRT啓動函數檢查命令行參數,過濾掉其中的可執行文件部分,並把剩下字符串的地址做爲pszCmdLine傳給wWinMain/WinMain。
以上情形都是在pszApplicationName爲NULL時發生的。 pszApplicationName指定了進程要執行的可執行文件的名稱,假如沒有指定文件後綴,系統並不會作任何處理。 pszApplicationName不包含完整路徑時,CreateProcess只從當前目錄中查找可執行文件,查找失敗時函數失敗並返回 FALSE。即便指定了pszApplicationName,CreateProcess仍然會將pszCommandLine參數做爲新進程的命令行。好比下面的代碼:
執行上面代碼時,系統會打開notepad.exe(記事本),但它的命令行倒是WORDPAD README.TXT(WORDPAD是寫字板),這看上去很是奇怪,但CreateProcess就是這樣工做的。這種 pszApplicationName提供的特性被用來支持Windows的POSIX子系統。
psaProcess, psaThread和bInheritHandles
建立新進程時,系統會建立一個進程內核對象和一個線程內核對象(用於進程的主線程),和其它內核對象同樣,建立者(在這兒是父進程)必須指定其安全屬性。psaProcess和psaThread分別指定了新進程的進程內核對象和線程內核對象的安全屬性。將其設爲NULL時,系統爲對應的內核對象指定默認的安全屬性。你能夠建立SECURITY_ATTRIBUTES類型的變量,設置其中各個域的值而後將變量地址傳遞給psaProcess或 psaThread,以應用指定的安全屬性。正如在第3章討論內核對象時談到的,當你想要控制新的進內核對象的句柄可否被父進程之後建立的子進程繼承時,你應該設置SECURITY_ATTRIBUTES變量的bInheritHandle域的值。
下面的Inherit.cpp展現了內核對象句柄繼承。假設執行該代碼的進程爲A,它調用CreateProcess建立了進程B,接着又建立了子進程C。注意調用CreateProcess時A使用的psaProcess、psaThread和bInheritHandles參數,代碼註釋很詳細的描述了這些參數的做用:
fdwCreate
fdwCreate參數用來控制進程被建立時的行爲,下面列出了它可能的取值:
·DEBUG_PROCESS:父進程將調試子進程及子進程建立的全部進程,指定該參數後,在子進程或子進程建立的任意進程中發生特定事件時系統將通知父進程
·DEBUG_ONLY_THIS_PROCESS:父進程將調試子進程,指定該參數後,在子進程中發生特定事件時系統將通知父進程
·CREATE_SUSPENDED:進程建立後其主線程暫不執行。此時父進程能夠在子進程運行以前更改子進程地址空間中的數據、更改子進程主線程優先級、將子進程添加到做業中等。父進程完成其更改後,能夠調用ResumeThread函數恢復子進程主線程運行
·DETACHED_PROCESS:系統將阻止CUI程序向其父進程的CUI窗口寫入其輸出。當父進程爲CUI進程時,建立的CUI子進程默認使用父進程的CUI窗口(如cmd.exe程序)。指定該參數後,新進程在須要輸出到窗口時必須調用AllocConsole建立CUI窗口
·CREATE_NEW_CONSOLE:系統自動爲新進程建立一個CUI窗口,該標誌不能與DETACHED_PROCESS同時使用
·CREATE_NO_WINDOW:系統不爲新進程建立CUI窗口,使用該標誌能夠建立不含窗口的CUI程序
·CREATE_NEW_PROCESS_GROUP:新進程將做爲一個新的進程組的根進程,新的進程組將包含以根進程爲祖先的全部進程。用戶在進程組中的某個進程CUI窗口中按下Ctrl+C或Ctrl+B時,系統將通知進程組中的全部進程這一事件
·CREATE_DEFAULT_ERROR_MODE:子進程不繼承父進程的任何錯誤標誌
·CREATE_SEPARATE_WOW_VDM:僅用於16位Windows程序,不譯
·CREATE_SHARED_WOW_VDM:僅用於16位Windows程序,不譯
·CREATE_UNICODE_ENVIRONMENT:子進程的環境塊爲Unicode字符串。進程的環境塊默認只包含ANSI字符串
·CREATE_FORCEDOS:強制系統運行內嵌在16位OS/2系統中的MS-DOS程序
·CREATE_BREAKAWAY_FROM_JOB:當父進程屬於某個做業時,新建的子進程將再也不與該做業關聯
·EXTENDED_STARTUPINFO_PRESENT:傳遞給CreateProcess函數的psiStartInfo參數是STARTUPINFOEX類型的變量
fdwCreate參數也能夠用於設置新進程的優先級。但你沒必要這樣作,對大多數應用你也不該該這樣作——系統會爲新進程分配默認優先級。表4-5列出了可能的優先級常量:
這些常量決定了進程中的線程在CPU中調度的優先級,咱們在188頁的「優先級概述」中會討論該問題。
pvEnvironment
參數pvEnvironment指向一塊內存區域,其中包含新進程用到的環境字符串。大多數狀況下,你能夠爲其傳遞NULL,此時新進程將繼承父進程的環境字符串。
pszCurDir
參數pszCurDir容許父進程設置子進程的當前驅動器和目錄。若是該參數爲NULL,子進程將使用父進程的當前驅動器和目錄做爲其當前驅動器和目錄。若是pszCurDir非空,則其必須指向一個包含驅動器標識的以0結尾的路徑字符串。
psiStartInfo
psiStartInfo是指向STARTUPINFO或STARTUPINFOEX變量的提針:
Windows建立新進程時會使用STARTUPINFO(EX)的成員變量,大多數狀況下可使用這些變量的默認值,此時你應該該將其cb域設置爲結構的大小,並將其他域清0,以下:
許多開發人員經常會忘記執行上述操做,若是你沒有清空其內容,STARTUPINFO(EX)的內容會是調用線程堆棧上的一些數據。將這些垃圾數據傳遞給CreateProcess可能致使沒法預料的結果,爲了讓CreateProcess正常工做,你必須將STARTUPINFO(EX)中沒有用到的域清0。
表4-6列出了STARTUPINFO(EX)結構的成員,注意有些成員只在GUI應用中生效,而有些則只在CUI應用中生效:
如今咱們來討論dwFlags成員。dwFlags包含一組標誌用來指示如何建立子進程,其中大多數標誌只是告訴CreateProcess是否使用STARTUPINFO結構中的某個成員,表4-7列出了dwFlags的可取值:
另外兩個標誌 STARTF_FORCEONFEEDBACK和STARTF_FORCEOFFFEEDBACK能夠控制在建立子進程時如何顯示鼠標指針。因爲 Windows支持搶先式多任務調度,所以你能夠在建立子程並等待子進程初始化時,執行另外的程序。若是你指定了 STARTF_FORCEONFEEDBACK,Windows會在新進程初始化時將鼠標光標指針更改成「後臺運行」,以下圖:
這個標誌意味着系統後臺正在處理某些任務(在這裏是建立並初始化子進程),但你依然能夠繼續使用系統。當你指定了STARTF_FORCEOFFFEEDBACK標誌時,CreateProcess不會更改鼠標指針樣式。
若是指定了STARTF_FORCEONFEEDBACK,且子進程在CreateProcess調用後2秒內執行了GUI調用,CreateProcess會等待子進程顯示窗口。若是該GUI調用後5秒以內尚未窗口顯示,CreateProcess會將鼠標指針恢復原狀,不然繼續等待5秒,若是在這5秒以內子進程調用了GetMessage函數,CreateProcess會認爲子進程已經完成初始化並將鼠標指針復位。
STARTUPINFO的wShowWindow變量將傳遞給wWinMain/WinMain的最後一個參數nCmdShow,它的取值是 ShowWindow函數接受的參數值之一,一般被指定爲SW_SHOWNORMAL、SW_SHOWMINNOACTIVE或 SW_SHOWDEFAULT。
在結束本節以前,咱們來看看STARTUPINFOEX結構。經過使用同時兼容STARTUPINFOEX和STARTUPINFO結構的參數psiStartInfo,微軟在保持CreateProcess簽名的同時提升了其擴展性。下面是STARTUPINFOEX結構的定義:
lpAttributeList(屬性鏈表)是_PROC_THREAD_ATTRIBUTE_LIST結構的鏈表,其中每一個結構包含一個key/value對,目前,_PROC_THREAD_ATTRIBUTE_LIST中key的取值只能是下面兩種:
屬性鏈表的內容是不透明的,所以咱們須要一些函數來建立空的屬性鏈表。建立屬性鏈表須要如下幾個步驟,首先,爲其分配存儲空間,而後向其中添加鍵值對。函數InitializeProcThreadAttributeList用來建立新的屬性鏈表併爲其分配存儲空間:
參數dwFlags必須指定爲0,你能夠先用以下方式得到屬性鏈表所需的空間大小:
cbAttributeListSize 返回建立屬性鏈表所需的內存大小,該大小與dwAttributeCount參數相關,dwAttributeCount指定了屬性鏈表中的 key/value對的數目。接下來你能夠用cbAttributeListSize爲屬性鏈表分配空間:
而後再次調用InitializeProcThreadAttributeList初始化屬性鏈表的內容:
當屬性鏈表初始化完成後,就能夠調用UpdateProcThreadAttribute向其添加鍵/值對了:
pAttributeList是要添加鍵/值對的屬性列表,Attribute可取 PROC_THREAD_ATTRIBUTE_PARENT_PROCESS或PROC_THREAD_ATTRIBUTE_HANDLE_LIST,取前者時,pValue參數應指向另一個進程的句柄,cbSize取值應爲sizeof(HANDLE),不然,pValue指向子進程要繼承的全部內核對象的句柄數組,cbSize取值應是sizeof(HANDLE)乘以該數組的大小。參數dwFlags、pPreviousValue和 pReturnSize是保留參數,應分別賦0、NULL和NULL。
注意,若是在建立子進程時爲其指定新的父進程,既使用了 PROC_THREAD_ATTRIBUTE_PARENT_PROCESS,那麼在使用 PROC_THREAD_ATTRIBUTE_HANDLE_LIST時,pValue指向的句柄數組中的句柄應該是新父進程句柄表中對象的句柄,而不是調用CreateProcess的進程全部。
當你在CreateProcess的dwCreateFlags參數中指定了EXTENDED_STARTUPINFO_PRESENT時,你應該向CreateProcess的pStartupInfo參數傳遞一個STARTUPINFOEX結構的指針,以下面所示:
其中pAttributeList是按前面的方法建立的屬性列表。當你再也不須要該屬性列表時,應該調用下面的方法回收爲其分配的內存:
最後,應用程序能夠調用GetStartupInfo得到由其父進程在CreateProcess中指定的STARTUPINFO結構的拷貝:
注意,不管父進程在調用CreateProcess時參數pStartupInfo指向STARTUPINFO仍是STARTUPINFOEX結構,GetStartupInfo老是返回STARTUPINFO結構的拷貝。
ppiProcInfo
ppiProcInfo 參數是PROCESS_INFORMATION結構的指針,調用CreateProcess時該結構必須由開發人員手動分配。CreateProcess 在返回前會填充ppiProcInfo指向的結構的內容。PROCESS_INFORMATION定義以下:
CreateProcess會建立一個進程內核對象和一個線程內核對象,建立初期,系統將其引用計數分別置爲1。CreateProcess返回以前會得到這兩個對象的訪問權限,這樣每一個對象的引用計數會分別增長1,CreateProcess返回以後,兩個對象的引用計數變成2。這意味着若是系統要釋放CreateProcess進程/線程內核對象,相應的進程/線程必須終止,而且調用CreateProcess的線程必須調用 CloseHandle關閉相應的對象句柄,這樣才能使得其引用計數變爲0,系統方能釋放。