CreateProcessA 函数 (processthreadsapi.h)
创建一个新进程及其主线程。 新进程在调用进程的安全上下文中运行。
如果调用进程正在模拟另一个用户,则新进程使用调用进程的令牌,而不是模拟令牌。 若要在模拟令牌表示的用户的安全上下文中运行新进程,请使用 CreateProcessAsUserA 函数 或 CreateProcessWithLogonW 函数。
语法
BOOL CreateProcessA(
[in, optional] LPCSTR lpApplicationName,
[in, out, optional] LPSTR lpCommandLine,
[in, optional] LPSECURITY_ATTRIBUTES lpProcessAttributes,
[in, optional] LPSECURITY_ATTRIBUTES lpThreadAttributes,
[in] BOOL bInheritHandles,
[in] DWORD dwCreationFlags,
[in, optional] LPVOID lpEnvironment,
[in, optional] LPCSTR lpCurrentDirectory,
[in] LPSTARTUPINFOA lpStartupInfo,
[out] LPPROCESS_INFORMATION lpProcessInformation
);
参数
[in, optional] lpApplicationName
要执行的模块的名称。 此模块可以是基于 Windows 的应用程序。 如果本地计算机上提供了相应的子系统,它可以是某种其他类型的模块(例如,MS-DOS 或 OS/2)。
字符串可以指定要执行的模块的完整路径和文件名,也可以指定部分名称。 对于部分名称,该函数使用当前驱动器和当前目录来完成规范。 该函数不会使用搜索路径。 此参数必须包含文件扩展名;未假定默认扩展。
lpApplicationName 参数可以 NULL。 在这种情况下,模块名称必须是 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 位应用程序,lpApplicationName 应 NULL,lpCommandLine 指向的字符串应指定可执行模块及其参数。
若要运行批处理文件,必须启动命令解释器;将 lpApplicationName 设置为 cmd.exe,并将 lpCommandLine 设置为下列参数:/c 加上批处理文件的名称。
重要
MSRC 工程团队建议不要这样做。 有关更多详细信息,请参阅 MS14-019 – 通过.cmd或 .bat 文件 修复二进制劫持。
[in, out, optional] lpCommandLine
要执行的命令行。
此字符串的最大长度为 32,767 个字符,包括 Unicode 终止 null 字符。 如果 lpApplicationNameNULL,则 lpCommandLine 的模块名称部分限制为 MAX_PATH 字符。
此函数的 Unicode 版本(CreateProcessW)可以修改此字符串的内容。 因此,此参数不能是指向只读内存的指针(例如 常量 变量或文本字符串)。 如果此参数是常量字符串,则函数可能会导致访问冲突。
lpCommandLine 参数可以为 NULL。 在这种情况下,该函数使用 lpApplicationName 指向的字符串作为命令行。
如果 lpApplicationName 和 lpCommandLine 都是非NULL,则 lpApplicationName 指向的 null 终止字符串指定要执行的模块,lpCommandLine 指定命令行所指向的 null 终止字符串。 新进程可以使用 GetCommandLine 来检索整个命令行。 用 C 编写的控制台进程可以使用 argc 和 argv 参数分析命令行。 由于 argv[0] 是模块名称,C 程序员通常会将模块名称重复为命令行中的第一个标记。
如果 lpApplicationName 为 NULL,则命令行的第一个空格分隔标记指定模块名称。 如果使用包含空格的长文件名,请使用带引号的字符串来指示文件名的结尾和参数的开始位置(请参阅 lpApplicationName 参数的说明)。 如果文件名不包含扩展名,则追加 .exe。 因此,如果文件扩展名.com,此参数必须包含.com扩展名。 如果文件名以不带扩展名的句点 (.) 结尾,或者文件名包含路径,则不会追加 .exe。 如果文件名不包含目录路径,系统将按以下顺序搜索可执行文件:
- 从中加载应用程序的目录。
- 父进程的当前目录。
- 32 位 Windows 系统目录。 使用 GetSystemDirectoryA 函数 函数获取此目录的路径。
- 16 位 Windows 系统目录。 没有获取此目录路径的函数,但搜索该函数。 此目录的名称为 System。
- Windows 目录。 使用 GetWindowsDirectoryA 函数 获取此目录的路径。
- PATH 环境变量中列出的目录。 请注意,此函数不会搜索 应用路径 注册表项指定的每个应用程序路径。 若要在搜索序列中包含此每个应用程序路径,请使用 ShellExecute 函数。
系统将终止 null 字符添加到命令行字符串,以将文件名与参数分开。 这会将原始字符串划分为两个用于内部处理的字符串。
[in, optional] lpProcessAttributes
指向 SECURITY_ATTRIBUTES 结构的指针,该结构确定新进程对象的返回句柄是否可以由子进程继承。 如果 lpProcessAttributesNULL,则无法继承句柄。
lpSecurityDescriptor 结构的成员为新进程指定安全描述符。 如果 lpProcessAttributes 为 NULL 或 lpSecurityDescriptorNULL,则进程将获取默认的安全描述符。 进程的默认安全描述符中的 ACL 来自创建者的主要令牌。 Windows XP: 进程的默认安全描述符中的 ACL 来自创建者的主要或模拟令牌。 此行为随 SP2 和 Windows Server 2003 的 Windows XP 发生更改。
[in, optional] lpThreadAttributes
指向 SECURITY_ATTRIBUTES 结构的指针,该结构确定新线程对象的返回句柄是否可以由子进程继承。 如果 lpThreadAttributes 为 NULL,则无法继承句柄。
lpSecurityDescriptor 结构成员指定主线程的安全描述符。 如果 lpThreadAttributes 为 NULL 或 lpSecurityDescriptor 为 NULL,则线程将获取默认的安全描述符。 线程的默认安全描述符中的 ACL 来自进程令牌。 Windows XP: 线程的默认安全描述符中的 ACL 来自创建者的主令牌或模拟令牌。 此行为随 SP2 和 Windows Server 2003 的 Windows XP 发生更改。
[in] bInheritHandles
如果此参数为 TRUE,则调用进程中的每个可继承句柄都由新进程继承。 如果参数为 FALSE,则不会继承句柄。 请注意,继承的句柄的值和访问权限与原始句柄相同。 有关可继承句柄的其他讨论,请参阅“备注”。
终端服务: 不能跨会话继承句柄。 此外,如果此参数为 TRUE,则必须在与调用方相同的会话中创建进程。
受保护的进程轻型(PPL)进程: PPL 进程创建非 PPL 进程时阻止泛型句柄继承,因为不允许将非 PPL 进程PROCESS_DUP_HANDLE到 PPL 进程。 请参阅 处理安全和访问权限
Windows 7:即使参数为 FALSE,也会继承 STD_INPUT_HANDLE、STD_OUTPUT_HANDLE和STD_ERROR_HANDLE。
[in] dwCreationFlags
控制优先级类和创建进程的标志。 有关值列表,请参阅 进程创建标志。
此参数还控制新进程的优先级类,该类用于确定进程的线程的计划优先级。 有关值列表,请参阅 GetPriorityClass。 如果未指定任何优先级类标志,则优先级类默认为 NORMAL_PRIORITY_CLASS,除非创建过程的优先级类 IDLE_PRIORITY_CLASS 或 BELOW_NORMAL_PRIORITY_CLASS。 在这种情况下,子进程接收调用进程的默认优先级类。
如果 dwCreationFlags 参数的值为 0:
- 进程继承调用方和父控制台的错误模式。
- 假定新进程的环境块包含 ANSI 字符(请参阅 lpEnvironment 参数了解其他信息)。
- 基于 Windows 的 16 位应用程序在共享虚拟 DOS 计算机(VDM)中运行。
[in, optional] lpEnvironment
指向新进程的环境块的指针。 如果此参数 NULL,则新进程使用调用进程的环境。
环境块由以 null 结尾的字符串的 null 终止块组成。 每个字符串采用以下形式:
名称=值\0
由于等号用作分隔符,因此不得在环境变量的名称中使用。
环境块可以包含 Unicode 或 ANSI 字符。 如果由 lpEnvironment 指向的环境块包含 Unicode 字符,请确保 dwCreationFlags 包含 CREATE_UNICODE_ENVIRONMENT。
如果进程的环境块的总大小超过 32,767 个字符,则此函数的 ANSI 版本 CreateProcessA 失败。
请注意,ANSI 环境块由两个零字节终止:一个用于最后一个字符串,另一个用于终止该块。 Unicode 环境块由四个零字节终止:两个用于最后一个字符串,两个用于终止该块。
[in, optional] lpCurrentDirectory
进程的当前目录的完整路径。 该字符串还可以指定 UNC 路径。
如果此参数 NULL,则新进程将具有与调用进程相同的当前驱动器和目录。 (此功能主要用于需要启动应用程序的 shell,并指定其初始驱动器和工作目录。
[in] lpStartupInfo
指向 STARTUPINFO 或 STARTUPINFOEX 结构的指针。
若要设置扩展属性,请使用 STARTUPINFOEX 结构并在 dwCreationFlags 参数中指定EXTENDED_STARTUPINFO_PRESENT。
STARTUPINFO 或 STARTUPINFOEX 中的句柄必须用 不再需要的 closeHandle 关闭。
重要
调用方负责确保 STARTUPINFO 中的标准句柄字段 包含有效的句柄值。 即使 dwFlags 成员指定 STARTF_USESTDHANDLES,这些字段也不会复制到子进程。 不正确的值可能导致子进程错误或崩溃。 使用 应用程序验证程序 运行时验证工具检测无效句柄。
[out] lpProcessInformation
指向接收有关新进程的标识信息的 PROCESS_INFORMATION 结构的指针。
PROCESS_INFORMATION 中的句柄必须用不再需要 CloseHandle 关闭。
返回值
如果函数成功,则返回值为非零。
如果函数失败,则返回值为零。 若要获取扩展的错误信息,请调用 GetLastError。
请注意,该函数在进程完成初始化之前返回。 如果所需的 DLL 无法找到或无法初始化,则进程将终止。 若要获取进程的终止状态,请调用 GetExitCodeProcess。
言论
为进程分配了一个进程标识符。 标识符在进程终止之前有效。 它可用于标识进程,也可以在 OpenProcess 函数中指定以打开进程的句柄。 进程中的初始线程也分配了线程标识符。 可以在 OpenThread 函数中指定它,以打开线程的句柄。 标识符在线程终止之前有效,可用于唯一标识系统中的线程。 这些标识符在 PROCESS_INFORMATION 结构中返回。
操作系统提供给进程的命令行中的可执行文件名称不一定与调用进程提供给 CreateProcess 函数的命令行中的可执行文件名称相同。 操作系统可能会将完全限定的路径追加到未提供完全限定路径的可执行名称。
调用线程可以使用 WaitForInputIdle 函数等待,直到新进程完成其初始化,并且正在等待用户输入,且没有输入挂起。 这对于父进程和子进程之间的同步非常有用,因为 CreateProcess 返回,而无需等待新进程完成其初始化。 例如,在尝试查找与新进程关联的窗口之前,创建过程将使用 WaitForInputIdle。
关闭进程的首选方法是使用 ExitProcess 函数,因为此函数将接近终止的通知发送到附加到进程的所有 DLL。 关闭进程的其他方法不会通知附加的 DLL。 请注意,当线程调用 ExitProcess时,进程的其他线程将终止,而无需执行任何其他代码(包括附加 DLL 的线程终止代码)。 有关详细信息,请参阅 终止进程。
父进程可以在创建过程中直接更改子进程的环境变量。 这是进程直接更改另一个进程的环境设置的唯一情况。 有关详细信息,请参阅 更改环境变量。
如果应用程序提供环境块,则系统驱动器的当前目录信息不会自动传播到新进程。 例如,有一个名为 =C 的环境变量:其值是驱动器 C 上的当前目录。应用程序必须手动将当前目录信息传递到新进程。 为此,应用程序必须显式创建这些环境变量字符串,按字母顺序对其进行排序(因为系统使用排序的环境),并将其放入环境块中。 通常,由于环境块排序顺序,它们将位于环境块的前面。
获取驱动器 X 的当前目录信息的一种方法是进行以下调用:GetFullPathName("X:", ...)
。 这样就避免了应用程序必须扫描环境块。 如果返回的完整路径为 X:,则无需将该值作为环境数据传递,因为根目录是新进程的驱动器 X 的默认当前目录。
使用指定的 CREATE_NEW_PROCESS_GROUP 创建进程时,将代表新进程对 SetConsoleCtrlHandler(NULL,TRUE)进行隐式调用;这意味着新进程已禁用 CTRL+C。 这允许 shell 自行处理 Ctrl+C,并有选择地将该信号传递到子进程。 CTRL+BREAK 未禁用,可用于中断进程/进程组。
默认情况下,将 TRUE 作为 bInheritHandles 参数的值传递会导致新进程继承所有可继承的句柄。 对于同时从多个线程创建进程但希望每个进程继承不同的句柄的应用程序来说,这可能会造成问题。
应用程序可以将 UpdateProcThreadAttributeList 函数与 PROC_THREAD_ATTRIBUTE_HANDLE_LIST 参数一起使用,以提供要由特定进程继承的句柄列表。
安全备注
第一个参数 lpApplicationName,可以 NULL,在这种情况下,可执行文件名称必须位于由 lpCommandLine指向的空格分隔字符串中。 如果可执行文件或路径名称中有一个空格,则由于函数分析空格的方式,可能会运行不同的可执行文件。 下面的示例很危险,因为该函数将尝试运行“Program.exe”(如果存在)而不是“MyApp.exe”。
LPTSTR szCmdline = _tcsdup(TEXT("C:\\Program Files\\MyApp -L -S"));
CreateProcess(NULL, szCmdline, /* ... */);
如果恶意用户在系统上创建名为“Program.exe”的应用程序,则使用 Program Files 目录错误地调用 CreateProcess 的任何程序都将运行此应用程序,而不是预期应用程序。
为了避免此问题,请不要为 lpApplicationName传递 NULL。 如果为 lpApplicationName传递 NULL,请使用 lpCommandLine中可执行文件路径的引号,如以下示例所示。
LPTSTR szCmdline[] = _tcsdup(TEXT("\"C:\\Program Files\\MyApp\" -L -S"));
CreateProcess(NULL, szCmdline, /*...*/);
例子
有关示例,请参阅 创建进程。
注意
processthreadsapi.h 标头将 CreateProcess 定义为别名,该别名根据 UNICODE 预处理器常量的定义自动选择此函数的 ANSI 或 Unicode 版本。 将非中性编码别名与非非编码的代码混合使用可能会导致编译或运行时错误不匹配。 有关详细信息,请参阅函数原型的
要求
要求 | 价值 |
---|---|
最低支持的客户端 | Windows XP [桌面应用 |UWP 应用] |
支持的最低服务器 | Windows Server 2003 [桌面应用 |UWP 应用] |
目标平台 | 窗户 |
标头 | processthreadsapi.h(包括 Windows Server 2003、Windows Vista、Windows 7、Windows Server 2008 Windows Server 2008 R2 上的 Windows.h) |
库 | Kernel32.lib |
DLL | Kernel32.dll |