CreateProcess詳解

CreateProcess
說明:
WIN32API函數CreateProcess用來創建一個新的進程和它的主線程,這個新進程運行指定的可執行文件。

函數原型:
BOOL CreateProcess
(
LPCTSTR lpApplicationName,
LPTSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes。
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCTSTR lpCurrentDirectory,
LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
);

一個線程調用它來首先創建一個進程內核對象,進程內核對象是用來管理這個新的進程的,然後,系統爲新進程創建虛擬地址空間,並將可執行文件(和DLL)的代碼和數據加載到這個地址空間,然後系統爲新進程的主線程創建一個線程內核對象.

先解釋下lpApplicationName和lpCommandLine

lpApplicationName和lpCommandLine分別指向新進程要使用的可執行文件的名稱,以及要傳給新進程的命令行字符串,lpCommandLine的類型爲LPTSTR,這是因爲在內部,CreateProcess實際上會修改我們傳給他的命令行字符串,當然在它返回前,它會把這個字符串還原,所以這樣的代碼是錯誤的:

[cpp] view plaincopy
STARTUPINFO si = {sizeof(si)} ;
PROCESS_INFORMATION pi ;
CreateProcess(NULL,TEXT(“NOTEPAD”),NULL,NULL,
FALSE,0,NULL,NULL,&si,&pi) ;
解決這個BUG的方法是把常量字符串複製到一個臨時緩衝區中,如下所示:

[cpp] view plaincopy
STARTUPINFO si = {sizeof(si)} ;
PROCESS_INFORMATION pi ;
TCHAR szCommandLine[] = TEXT(“NOTEPAD”) ;
CreateProcess(NULL,szCommandLine,NULL,NULL,
FALSE,0,NULL,NULL,&si,&pi) ;
所以,期待Microsoft能在windows未來版本修正,自己能創建字符串的一個臨時副本,讓我們解放吧

兩種狀態:

1:lpApplicationName爲NULL,(99%都設爲NULL)

在這種情況下,可執行模塊的名字必須處於 lpCommandLine 參數的最前面並由空格符與後面的字符分開,當CreateProcess解析lpCommandLine 字符串時,它會檢查字符串中的第一個標記(token),並假記此標記爲我們想運行的可執行文件的名稱,如果可執行文件的名稱沒有擴展名,默認爲.exe,並且如果文件名不包含一個完整的路徑,CreateProcess還會按以下順序來搜索可執行文件:

(1)主調進程.exe文件所在的目錄

(2)主調進程的當前目錄

(3)windows系統目錄,即GetSystemDirectory返回的System32子文件夾

(4)windows目錄

(5)PATH環境變量中列出的目錄

2:lpApplicationName不爲NULL

在這種情況下,必須指定文件擴展名,系統不會像1那樣自動假定擴展名了,並且如果文件名不包含一個完整的路徑,CreateProcess只會在當前目錄查找可執行文件,不會在其他任何目錄查找了。

一旦系統找到了可執行文件,就創建一個新進程,並將可執行文件的代碼和數據映射到新進程的地址空間,然後啓動例程,C/C++會將可執行文件名之後的第一個實參的地址傳給(w)WinMain的pszCmdLine參數

再解釋下lpProcessAttributes和lpThreadAttributes

爲了創建一個新的進程,系統必須創建一個進程內核對象和新進程的主線程的線程內核對象,這兩個參數就標識着這兩個內核對象的安全描述符。

bInheritHandles就不用說了,設置父子進程的句柄描繪表的繼承性

dwCreationFlags標識了影響新進程創建方式的標誌

值:CREATE_DEFAULT_ERROR_MODE
含義:新的進程不繼承調用進程的錯誤模式。CreateProcess函數賦予新進程當前的默認錯誤模式作爲替代。應用程序可以調用SetErrorMode函數設置當前的默認錯誤模式。
這個標誌對於那些運行在沒有硬件錯誤環境下的多線程外殼程序是十分有用的。
對於CreateProcess函數,默認的行爲是爲新進程繼承調用者的錯誤模式。設置這個標誌以改變默認的處理方式。

值:CREATE_NEW_CONSOLE
含義:新的進程將使用一個新的控制檯,而不是繼承父進程的控制檯。這個標誌不能與DETACHED_PROCESS標誌一起使用。

值:CREATE_NEW_PROCESS_GROUP
含義:新進程將使一個進程樹的根進程。進程樹種的全部進程都是根進程的子進程。新進程樹的用戶標識符與這個進程的標識符是相同的,由lpProcessInformation參數返回。進程樹經常使用GenerateConsoleCtrlEvent函數允許發送CTRL+C或CTRL+BREAK信號到一組控制檯進程。

值:CREATE_SEPARATE_WOW_VDM
含義:(只適用於Windows NT)這個標誌只有當運行一個16位的Windows應用程序時纔是有效的。如果被設置,新進程將會在一個私有的虛擬DOS機(VDM)中運行。另外,默認情況下所有的16位Windows應用程序都會在同一個共享的VDM中以線程的方式運行。單獨運行一個16位程序的優點是一個應用程序的崩潰只會結束這一個VDM的運行;其他那些在不同VDM中運行的程序會繼續正常的運行。同樣的,在不同VDM中運行的16位Windows應用程序擁有不同的輸入隊列,這意味着如果一個程序暫時失去響應,在獨立的VDM中的應用程序能夠繼續獲得輸入。

值:CREATE_SHARED_WOW_VDM
含義:(只適用於Windows NT)這個標誌只有當運行一個16位的Windows應用程序時纔是有效的。如果WIN.INI中的Windows段的DefaultSeparateVDM選項被設置爲真,這個標識使得CreateProcess函數越過這個選項並在共享的虛擬DOS機中運行新進程。

值:CREATE_SUSPENDED
含義:新進程的主線程會在創建後被掛起,直到調用ResumeThread函數被調用時才運行,這樣一來,父進程就可以修改子進程地址空間中的內存,更改子進程的主線程的優先級,或者在進程執行任何代碼前,把它加入到一個作業中,父進程修改好子進程後,再調用ResumeThread來允許子進程執行代碼。

值:CREATE_UNICODE_ENVIRONMENT
含義:如果被設置,由lpEnvironment參數指定的環境塊使用Unicode字符,如果爲空,環境塊使用ANSI字符。

值:DEBUG_PROCESS
含義:如果這個標誌被設置,調用進程將被當作一個調試程序,並且新進程會被當作被調試的進程。系統把被調試程序發生的所有調試事件通知給調試器。
如果你使用這個標誌創建進程,只有調用進程(調用CreateProcess函數的進程)可以調用WaitForDebugEvent函數。

值:DEBUG_ONLY_THIS_PROCESS
含義:如果此標誌沒有被設置且調用進程正在被調試,新進程將成爲調試調用進程的調試器的另一個調試對象。如果調用進程沒有被調試,有關調試的行爲就不會產生。

值:DETACHED_PROCESS
含義:對於控制檯進程,新進程沒有訪問父進程控制檯的權限。新進程可以通過AllocConsole函數自己創建一個新的控制檯。這個標誌不可以與CREATE_NEW_CONSOLE標誌一起使用。

dwCreationFlags參數還用來控制新進程的優先類,優先類用來決定此進程的線程調度的優先級。如果下面的優先級類標誌都沒有被指定,那麼默認的優先類是NORMAL_PRIORITY_CLASS,除非被創建的進程是IDLE_PRIORITY_CLASS。在這種情況下子進程的默認優先類是IDLE_PRIORITY_CLASS。
可以下面的標誌中的一個:

優先級:HIGH_PRIORITY_CLASS
含義:指示這個進程將執行時間臨界的任務,所以它必須被立即運行以保證正確。這個優先級的程序優先於正常優先級或空閒優先級的程序。一個例子是Windows任務列表,爲了保證當用戶調用時可以立刻響應,放棄了對系統負荷的考慮。確保在使用高優先級時應該足夠謹慎,因爲一個高優先級的CPU關聯應用程序可以佔用幾乎全部的CPU可用時間。

優先級:IDLE_PRIORITY_CLASS
含義:指示這個進程的線程只有在系統空閒時纔會運行並且可以被任何高優先級的任務打斷。例如屏幕保護程序。空閒優先級會被子進程繼承。

優先級:NORMAL_PRIORITY_CLASS
含義:指示這個進程沒有特殊的任務調度要求。

優先級:REALTIME_PRIORITY_CLASS
含義:指示這個進程擁有可用的最高優先級。一個擁有實時優先級的進程的線程可以打斷所有其他進程線程的執行,包括正在執行重要任務的系統進程。例如,一個執行時間稍長一點的實時進程可能導致磁盤緩存不足或鼠標反映遲鈍。

lpEnvironment:

指向一個新進程的環境塊。如果此參數爲空,新進程使用調用進程的環境。

一個環境塊存在於一個由以NULL結尾的字符串組成的塊中,這個塊也是以NULL結尾的。每個字符串都是name=value的形式。
因爲相等標誌被當作分隔符,所以它不能被環境變量當作變量名。
與其使用應用程序提供的環境塊,不如直接把這個參數設爲空,系統驅動器上的當前目錄信息不會被自動傳遞給新創建的進程。對於這個情況的探討和如何處理,請參見注釋一節。
環境塊可以包含Unicode或ANSI字符。如果lpEnvironment指向的環境塊包含Unicode字符,那麼dwCreationFlags字段的CREATE_UNICODE_ENVIRONMENT標誌將被設置。如果塊包含ANSI字符,該標誌將被清空。
請注意一個ANSI環境塊是由兩個零字節結束的:一個是字符串的結尾,另一個用來結束這個快。一個Unicode環境塊石油四個零字節結束的:兩個代表字符串結束,另兩個用來結束塊。

lpCurrentDirectory:

指向一個以NULL結尾的字符串,這個字符串用來指定子進程的工作路徑。這個字符串必須是一個包含驅動器名的絕對路徑。如果這個參數爲NULL,新進程將使用與調用進程相同的驅動器和目錄。這個選項是一個需要啓動啓動應用程序並指定它們的驅動器和工作目錄的外殼程序的主要條件。

lpStartupInfo:

[cpp] view plaincopy
typedef struct _STARTUPINFO
{
DWORD cb;
LPTSTR lpReserved;
LPTSTR lpDesktop;
LPTSTR lpTitle;
DWORD dwX;
DWORD dwY;
DWORD dwXSize;
DWORD dwYSize;
DWORD dwXCountChars;
DWORD dwYCountChars;
DWORD dwFillAttribute;
DWORD dwFlags;
WORD wShowWindow;
WORD cbReserved2;
LPBYTE lpReserved2;
HANDLE hStdInput;
HANDLE hStdOutput;
HANDLE hStdError;
} STARTUPINFO, *LPSTARTUPINFO;

指向一個用於決定新進程的主窗體如何顯示的STARTUPINFO結構體。

大多數應用程序都希望生成的應用程序只是使用默認值,最起碼要全部初始化爲0,再把cb成員設爲此結構體的大小,如果沒有清0,則新進程可能創建失敗.

表4-6 STARTUPINFO 結構的成員

成員 窗口,控制檯還是兩者兼有 作用
cb 兩者兼有 包含S TA RT U P I N F O 結構中的字節數。如果M i c r o s o f t 將來擴展該結構,它可用作版本控制手段。應用程序必須將c b 初始化爲s i z e o f ( S TA RT U P I N F O )
lpReserved 兩者兼有 保留。必須初始化爲N U L L
lpDesktop 兩者兼有 用於標識啓動應用程序所在的桌面的名字。如果該桌面存在,新進程便與指定的桌面相關聯。如果桌面不存在,便創建一個帶有默認屬性的桌面,並使用爲新進程指定的名字。如果l p D e s k t o p 是N U L L (這是最常見的情況),那麼該進程將與當前桌面相關聯
lpTitle 控制檯 用於設定控制檯窗口的名稱。如果l p Ti t l e 是N U L L ,則可執行文件的名字將用作窗口名
dwX
dwY 兩者兼有 用於設定應用程序窗口在屏幕上應該放置的位置的x 和y 座標(以像素爲單位)。只有當子進程用C W _ U S E D E FA U LT 作爲C r e a t e Wi n d o w 的x 參數來創建它的第一個重疊窗口時,才使用這兩個座標。若是創建控制檯窗口的應用程序,這些成員用於指明控制檯窗口的左上角
dwXSize 兩者兼有 用於設定應用程序窗口的寬度和長度(以像素爲單位)只有dwYsize 當子進程將C W _ U S E D E FA U LT 用作C r e a t e Wi n d o w 的n Wi d t h參數來創建它的第一個重疊窗口時,才使用這些值。若是創建控制檯窗口的應用程序,這些成員將用於指明控制檯窗口的寬度
dwXCountChars
dwYCountChars 控制檯 用於設定子應用程序的控制檯窗口的寬度和高度(以字符爲單位)
dwFillAttribute 控制檯 用於設定子應用程序的控制檯窗口使用的文本和背景顏色
dwFlags 兩者兼有 請參見下一段和表4 - 7 的說明
wShowWindow 窗口 用於設定如果子應用程序初次調用的S h o w Wi n d o w 將S W _ S H O W D E FA U LT 作爲n C m d S h o w 參數傳遞時,該應用程序的第一個重疊窗口應該如何出現。本成員可以是通常用於Show Wi n d o w 函數的任何一個S W _ *標識符
cbReserved2 兩者兼有 保留。必須被初始化爲0
lpReserved2 兩者兼有 保留。必須被初始化爲N U L L
hStdInput
hStdOutput
hStdError 控制檯 用於設定供控制檯輸入和輸出用的緩存的句柄。按照默認設置,h S t d I n p u t 用於標識鍵盤緩存,h S t d O u t p u t 和h S t d E r r o r用於標識控制檯窗口的緩存

dwFlags包含一組標誌,大多數標誌都只是告訴CreateProcess函數,STARTUPINFO 中其他成員是否包含有用的信息,或者是否應該忽略一些成員

表4-7 使用標誌及含義

標誌 含義
STARTF_USESIZE 使用d w X S i z e 和d w Y S i z e 成員
STARTF_USESHOWWINDOW 使用w S h o w Wi n d o w 成員
STARTF_USEPOSITION 使用d w X 和d w Y 成員
STARTF_USECOUNTCHARS 使用d w X C o u n t C h a r s 和dwYCount Chars 成員
STARTF_USEFILLATTRIBUTE 使用d w F i l l A t t r i b u t e 成員
STARTF_USESTDHANDLES 使用h S t d I n p u t 、h S t d O u t p u t 和h S t d E r r o r 成員
STARTF_RUN_FULLSCREEN 強制在x 8 6 計算機上運行的控制檯應用程序以全屏幕方式啓動運行

另外還有兩個標誌即STARTF_ORCEONFEEDBACK和STARTFFORCEOFFFEEDBACK,當啓動一個新進程時,它們可以用來控制鼠標的光標,由於windows支持真正的多任務搶佔式運行方式,因此可以啓動一個應用程序,然後在進程初始化時使用另一個程序,爲了向用戶提供視覺反饋,CreateProcess臨時會把系統的光標改成一個新的光標,但

 如果指定了STARTF_ORCEONFEEDBACK,CreateProcess就不會改變光標

如果指定了STARTFFORCEOFFFEEDBACK,CreateProcess會改變成新的光標,在2秒之後,如果新進程沒有執行任何GUI調用,光標還原,如果執行了GUI調用,則在5秒內必須顯示窗口,否則光標同樣還原,

lpStartupInfo

指向必須指定的PROCESS_INFORMATION結構體

[cpp] view plaincopy
typedef struct _PROCESS_INFORMATION {
HANDLE hProcess;
HANDLE hThread;
DWORD dwProcessId;
DWORD dwThreadId;
} PROCESS_INFORMATION;
如前所述,創建新進程可使系統建立一個進程內核對象和一個線程內核對象,在創建進程的時候,系統爲每個對象賦予一個初始使用計數值1,然後,在CreateProcess返回之前,該函數打開進程內核對象和線程內核對象,並將每個對象的與進程相關的句柄放_PROCESS_INFORMATION中的hProcess和hThread,當CreateProcess在內部打開這些對象時,每個對象的使用計數就變爲2。

所以注意必須關閉子進程和它的主線程的句柄,以避免在應用程序運行中泄漏資源,當然當進程終止運行時,系統如自動消除這些泄漏現象,但是,當進程不再需要訪問子進程和它的線程時,編寫得較好的軟件能夠顯示關閉這些句柄(通過調用CloseHandle函數來關閉),不能關閉這些句柄是開發人員最常犯的錯誤之一.

當進程和線程內核對象創建時,系統都爲賦予該對象一個獨一無二的,系統範圍內的ID號,進程ID和線程ID共享相同的號碼池,這意味着進程和線程不可能擁有相同的ID,另外ID不能爲0,同樣,在CreateProcess返回時,dwProcessId和dwThreadId被填充,ID使你能非常容易識別系統中的進程和線程,一些實用工具(如Task Manager)對ID使用最多,而高效率的應用程序則使用得很少,因於這個原因,大多數應用程序完全忽略ID。

如果應用程序使用ID來跟蹤進程和線程,必須懂得系統會立即複用進程ID和線程ID,如,一個進程被創建時,ID值122,如果創建其他新進程對象,系統不會把相同的ID賦予給它,但是,如果第一個進程對象被釋放,系統就可以把122賦予創建的下一個進程對象,因此,如果應用程序想要與它的“創建者”進行通信,最好不要使用ID,應該定義一個持久性更好的機制,對如內核對象和窗口句柄等。

如果想創建一個新進程,並等待結果,可用以下類似代碼:

[cpp] view plaincopy
PROCESS_INFORMATION pi;
DWORD dwExitCode;

//Spawn the child process.  
BOOL fSuccess = CreateProcess(..., π);  

if(fSuccess)  
{  
    //Close the thread handle as soon as  
    //it is no longer needed!  
    CloseHandle(pi.hThread);  

    //Suspend our execution until  
    //the child has terminated.  
    WaitForSingleObject(pi.hProcess,INFINITE);  

    //The child process terminated;  
    //get its exit code.  
    GetExitCodeProcess(pi.hProcess,  
        &dwExitCode);  

    //Close the process handle as soon as  
    //it is no longer needed.  
    CloseHandle(pi.hProcess);  
}  

需要說明的是,只有當進程對象終止運行時,WaitForSingleObject才能得到通知,因此對WaitForSingleObject的調用會將父進程的線程掛起,直到子進程終止運行,當WaitForSingleObject返回時,通過GetExitCodeProcess,就可以得到子進程的退出碼

註釋:
CreateProcess函數用來運行一個新程序。WinExec和LoadModule函數依舊可用,但是它們同樣通過調用CreateProcess函數實現。

另外CreateProcess函數除了創建一個進程,還創建一個線程對象。這個線程將連同一個已初始化了的堆棧一起被創建,堆棧的大小由可執行文件的文件頭中的描述決定。線程由文件頭處開始執行。

新進程和新線程的句柄被以全局訪問權限創建。對於這兩個句柄中的任一個,如果沒有安全描述符,那麼這個句柄就可以在任何需要句柄類型作爲參數的函數中被使用。當提供安全描述符時,在接下來的時候當句柄被使用時,總是會先進行訪問權限的檢查,如果訪問權限檢查拒絕訪問,請求的進程將不能使用這個句柄訪問這個進程。

這個進程會被分配給一個32位的進程標識符。直到進程中止這個標識符都是有效的。它可以被用來標識這個進程,或在OpenProcess函數中被指定以打開這個進程的句柄。進程中被初始化了的線程一樣會被分配一個32位的線程標識符。這個標識符直到縣城中止都是有效的且可以用來在系統中唯一標識這個線程。這些標識符在PROCESS_INFORMATION結構體中返回。

當在lpApplicationName或lpCommandLine參數中指定應用程序名時,應用程序名中是否包含擴展名都不會影響運行,只有一種情況例外:一個以.com爲擴展名的MS-DOS程序或Windows程序必須包含.com擴展名。

調用進程可以通過WaitForInputIdle函數來等待新進程完成它的初始化並等待用戶輸入。這對於父進程和子進程之間的同步是極其有用的,因爲CreateProcess函數不會等待新進程完成它的初始化工作。舉例來說,在試圖與新進程關聯的窗口之前,進程應該先調用WaitForInputIdle。

首選的結束一個進程的方式是調用ExitProcess函數,因爲這個函數通知這個進程的所有動態鏈接庫(DLLs)程序已進入結束狀態。其他的結束進程的方法不會通知關聯的動態鏈接庫。注意當一個進程調用ExitProcess時,這個進程的其他縣城沒有機會運行其他任何代碼(包括關聯動態鏈接庫的終止代碼)。

ExitProcess, ExitThread, CreateThread, CreateRemoteThread,當一個進程啓動時(調用了CreateProcess的結果)是在進程中序列化進行的。在一段地址空間中,同一時間內這些事件中只有一個可以發生。這意味着下面的限制將保留:
*在進程啓動和DLL初始化階段,新的線程可以被創建,但是直到進程的DLL初始化完成前它們都不能開始運行。
*在DLL初始化或卸下例程中進程中只能有一個線程。
*直到所有的線程都完成DLL初始化或卸下後,ExitProcess函數才返回。

在進程中的所有線程都終止且進程所有的句柄和它們的線程被通過調用CloseHandle函數終止前,進程會留在系統中。進程和主線程的句柄都必須通過調用CloseHandle函數關閉。如果不再需要這些句柄,最好在創建進程後立刻關閉它們。

當進程中最後一個線程終止時,下列的事件發生:
*所有由進程打開的對象都會關閉。
*進程的終止狀態(由GetExitCodeProcess函數返回)從它的初始值STILL_ACTIVE變爲最後一個結束的線程的結束狀態。
*主線程的線程對象被設置爲標誌狀態,供其他等待這個對象的線程使用。
*進程對象被設置爲標誌狀態,供其他等待這個對象的線程使用。

假設當前在C盤上的目錄是/MSVC/MFC且有一個環境變量叫做C:,它的值是C:/MSVC/MFC,就像前面lpEnvironment中提到過的那樣,這樣的系統驅動器上的目錄信息在CreateProcess函數的lpEnvironment參數不爲空時不會被自動傳遞到新進程裏。一個應用程序必須手動地把當前目錄信息傳遞到新的進程中。爲了這樣做,應用程序必須直接創建環境字符串,並把它們按字母順序排列(因爲Windows NT和Windows 95使用一種簡略的環境變量),並把它們放進lpEnvironment中指定的環境塊中。類似的,他們要找到環境塊的開頭,又要重複一次前面提到的環境塊的排序。

一種獲得驅動器X的當前目錄變量的方法是調用GetFullPathName(“x:”,..)。這避免了一個應用程序必須去掃描環境塊。如果返回的絕對路徑是X:/,就不需要把這個值當作一個環境數據去傳遞了,因爲根目錄是驅動器X上的新進程的默認當前目錄。

由CreateProcess函數返回的句柄對於進程對象具有PROCESS_ALL_ACCESS的訪問權限。

由lpcurrentDirectory參數指定的當前目錄室子進程對象的當前目錄。lpCommandLine參數指定的第二個項目是父進程的當前目錄。

對於Windows NT,當一個進程在指定了CREATE_NEW_PROCESS_GROUP的情況下被創建時,一個對於SetConsoleCtrlHandler(NULL,True)的調用被用在新的進程上,這意味着對新進程來說CTRL+C是無效的。這使得上層的外科程序可以自己處理CTRL+C信息並有選擇的把這些信號傳遞給子進程。CTRL+BREAK依舊有效,並可被用來中斷進程/進程樹的執行。

參見
AllocConsole, CloseHandle, CreateRemoteThread, CreateThread, ExitProcess, ExitThread, GenerateConsoleCtrlEvent, GetCommandLine, GetEnvironmentStrings, GetExitCodeProcess, GetFullPathName, GetStartupInfo, GetSystemDirectory, GetWindowsDirectory, LoadModule, OpenProcess, PROCESS_INFORMATION, ResumeThread, SECURITY_ATTRIBUTES, SetConsoleCtrlHandler, SetErrorMode, STARTUPINFO, TerminateProcess, WaitForInputIdle, WaitForDebugEvent, WinExec

快捷信息:
導入庫:kernel32.lib
頭文件:Winbase.h

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章