CreateProcess
CreateProcess
是一個宏,根據是否定義符號常量 UNICODE
展開爲 ASCII 和 UNICODE 兩個版本,其實如果不是必須更推薦使用 UNICODE 版本的 API 函數,因爲即便是 ASCII 版本的函數,內核層面也是通過 UNICODE 版本來實現的。
函數原型:
// ASCII 版本
BOOL CreateProcessA(
LPCSTR lpApplicationName,
LPSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCSTR lpCurrentDirectory,
LPSTARTUPINFOA lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
);
// UNICODE 版本
BOOL CreateProcessW(
LPCWSTR lpApplicationName,
LPWSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCWSTR lpCurrentDirectory,
LPSTARTUPINFOW lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
);
該函數用於創建一個新的進程和他的主線程,新進程運行在父進程安全上下文(Security Context)中。
如果父進程正在模擬(impersonate)另一個用戶,此時創建的子進程使用父進程的訪問令牌,而非模擬令牌(impersonation token)。如果想在模擬令牌表示的安全上下文中創建新進程,應該使用 CreateProcessAsUser
或者 CreateProcessWithLogonW
。
參數說明:
lpApplicationName
:
該參數是待執行的模塊的名字,通常是一個基於 Windows 平臺的應用程序,如果本地計算機支持也可以是 MS-DOS 或者 OS/2 應用。
該參數可以指定可執行文件名和包含文件的完整目錄,當省略掉目錄的時候會默認是用進程的工作目錄來補全,而不會使用 PATH 變量搜索文件名。可執行文件必帶有後綴名,不能省略。
該參數可以爲 NULL,此時可執行文件名是 lpCommandLine
(第二個參數)的第一個空字符分隔開的子字符串。此時如果 lpCommandLine
使用了一個包含空格的長路徑,爲了避免歧義應該使用引號區分開文件名和參數。例如如果第二個參數爲 c:\program files\sub dir\program name,系統將使用下面的順序來解析:
- c:\program.exe
- c:\program files\sub.exe
- c:\program files\sub dir\program.exe
- c:\program files\sub dir\program name.exe
對於16位應用程序來說,第一個參數必須爲 NULL ,第二個參數應該同時包含文件名和命令行參數。
lpCommandLine
:
該參數是包含最多32768個字符(結尾的空字符也算在內了)的命令行參數,如果 lpApplicationName
是 NULL,則該參數中模塊名最長爲 MAX_PATH
。函數的 UNICODE 版本可能修改字符串的值,因此如果參數指向一塊只讀的內存(比如const變量,字符串字面量),會導致非法訪問(Access Violation)錯誤。
這個參數也可以是 NULL,此時函數使用 lpApplicationName
指向的字符串作爲命令行。如果前兩個參數都不是 NULL,則 lpApplicationName
作爲可執行文件名,lpCommandLine
作爲命令行參數。新的進程可以使用函數 GetCommandLine
來獲取完整的命令行,C語言編寫的控制檯應用程序則可以使用主函數參數 argc
和 argv
來獲取命令行。
當 lpApplicationName
是 NULL 時,第二個參數的首個空白分隔子字符串就是文件名,此時函數對文件名的處理方式和對第一個參數的處理有所區別,如果文件名沒有後綴則默認使用.exe,如果文件名包含路徑或者文件名以 . 結尾則不會自動附加文件後綴。如果文件名不包含目錄,則會按照啓動目錄,工作目錄,系統目錄,Windows目錄,PATH環境變量的順序來搜索可執行文件名(和 WinExec
的工作方式相同)。該函數不會搜索應用程序的 PATH(通過註冊表鍵 App Path 指定),如果需要可以通過 ShellExecute
函數來實現該功能。
lpProcessAttributes
:
這個參數是指向SECURITY_ATTRIBUTES
結構體的指針,用來決定 CreateProcess
得到的進程句柄能否被子進程所繼承,參數爲 NULL 表示不允許繼承。
SECURITY_ATTRIBUTES
結構體有一個名爲 lpSecurityDescriptor
的成員,該成員決定了新進程的安全描述符。如果參數 lpProcessAttributes
或者成員 lpSecurityDescriptor
爲 NULL,則進程使用默認安全描述符,對於 Windows XP 以上操作系統來說線程默認安全描述符的訪問控制列表(ACL) 來自創建者的主令牌(Primary Token),在此之前還可以是模擬令牌。
lpThreadAttributes
:
和上一個參數類似,也是 SECURITY_ATTRIBUTES
結構的指針,只不過此處用於描述線程。
bInheritHandles
:
子進程是否繼承父進程中的可繼承句柄,如果爲 TRUE 則繼承,爲 FALSE 則不繼承。繼承來的句柄和原句柄有着相同的值和訪問權限。
dwCreationFlags
:
進程創建標誌
lpEnvironment
:
進程環境塊指針,如果該參數爲 NULL 則子進程使用父進程的環境塊。
lpCurrentDirectory
:
進程工作目錄,該參數應該是進程當前目錄的完整路徑。如果這個參數爲 NULL 則新進程的工作目錄和父進程相同。
lpStartupInfo
:
指向 STARTUPINFO
或者 STARTUPINFOEX
結構體的指針。設置擴展屬性時要使用 STARTUPINFOEX
結構體,並且在參數 dwCreationFlags
中指定 EXTENDED_STARTUPINFO_PRESENT
。如果結構體 STARTUPINFO
或者 STARTUPINFOEX
中的句柄不再使用,必須通過函數 CloseHandle
關閉。
注意調用者要確保 STARTUPINFO
中的標準句柄成員取值合法。這些字段在拷貝給子進程時不經過校驗,即便 dwFlags
成員給了值 STARTF_USESTDHANDLES
。錯誤的句柄值將會導致子進程行爲異常或者崩潰。可以使用Application Verifier 運行時驗證工具來檢測非法句柄。
lpProcessInformation
:
這是一個輸出參數(由CreateProcess
函數負責填充),它是指向 PROCESS_INFORMATION
結構體的指針,該結構體包含子進程的身份信息。PROCESS_INFORMATION
中的句柄成員如果不再使用需要調用 CloseHandle
關閉。
PROCESS_INFORMATION
結構體定義如下:
typedef struct _PROCESS_INFORMATION {
HANDLE hProcess;
HANDLE hThread;
DWORD dwProcessId;
DWORD dwThreadId;
} PROCESS_INFORMATION, *PPROCESS_INFORMATION, *LPPROCESS_INFORMATION;
四個成員從上到下分別是進程句柄,線程句柄,進程ID,線程ID。
返回值:
如果函數成功執行,返回非0值。
如果函數執行失敗,返回0,詳細錯誤信息可以通過調用 GetLastError
來獲得。
該函數在進程完成初始化以前就已經返回,因此如果 DLL 加載或者初始化失敗進程會終止。通過調用 GetExitCodeProcess
獲取進程終止狀態。
注意事項:
子進程創建以後會分配進程 ID ,子進程的主線程也會分配線程 ID ,他們可以分別用在 OpenProcess
和 OpenThread
函數來獲得進程和線程的句柄。在相應進程和線程終止以前,進程ID和線程ID都可以作爲進程和線程的唯一合法標識。我們可以通過 PROCESS_INFORMATION
結構體來訪問進程 ID 和線程 ID 。
鑑於 CreateProcess
並不會等待子進程初始化,如果我們需要同步父進程和子進程,可以在父進程調用函數 WaitForInputIdle
,該函數會在子進程初始化完畢並開始等待用戶輸入後才返回。在某些情況下這很有用,比如我們在想要在創建子進程以後查找子進程的窗口。
最好使用 ExitProcess
來關閉一個進程,這個函數會向所有附加在該進程上的動態鏈接庫發送關閉通知。其他關閉方法不會通知進程相關的DLL。但線程調用 ExitProcess
之後,進程的其他線程也會立即關閉並且來不及執行任何其他代碼。
父進程在創建子進程時可以直接修改子進程的環境變量。這是一個進程直接修改另一個進程的環境設置的唯一機會。
當 lpApplicationName
爲 NULL 時,該函數和 WinExec
存在完全相同的安全隱患,具體可以參考這裏。爲了避免這個問題儘量不要給第一個參數設爲 NULL。
依賴信息:
名稱 | 值 |
---|---|
Header | processthreadsapi.h (include Windows Server 2003, Windows Vista, Windows 7, Windows Server 2008 Windows Server 2008 R2, Windows.h) |
Library | Kernel32.lib |
DLL | Kernel32.dll |