CreateProcess 是 Windows API 中的一个核心函数,用于创建一个新的进程(及其主线程),它功能非常强大,但也相对复杂,因为它可以让你精确控制新进程的创建方式。

(图片来源网络,侵删)
函数原型
我们来看一下 CreateProcess 的函数原型(在 Windows.h 中定义):
BOOL CreateProcess( [in, optional] LPCSTR lpApplicationName, // 可执行文件名 [in, out] 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 // 进程信息 );
参数详解
理解每个参数是使用 CreateProcess 的关键。
lpApplicationName (可执行文件名)
- 类型:
LPCSTR(指向以 null 结尾的字符串的指针) - 说明: 指定要执行的模块(通常是
.exe文件)的路径。 - 注意: 如果这个参数为
NULL,那么系统会从lpCommandLine参数中解析出可执行文件名,在很多情况下,直接设置此参数为NULL并构造完整的命令行会更灵活。
lpCommandLine (命令行)
- 类型:
LPSTR(指向以 null 结尾的字符串的指针) - 说明: 指定传递给新进程的命令行。
- 重要:
lpApplicationName为NULL,lpCommandLine的第一个 token 必须是可执行文件的路径,这个字符串不会被cmd.exe解释,所以不能使用dir,copy等内部命令,如果需要,应该先启动cmd.exe,再将命令作为参数传递给它("cmd /c dir c:\\")。 - 内存管理: 如果你在程序中构造了这个字符串,它必须是可写的,因为
CreateProcess可能会修改它,通常在栈上分配一个足够大的缓冲区即可。
lpProcessAttributes 和 lpThreadAttributes (进程/线程安全属性)
- 类型:
LPSECURITY_ATTRIBUTES - 说明: 用于定义新进程和新线程的安全描述符,如果你想让创建者(父进程)能够继承或操作这个新进程,可以在这里设置,对于简单的创建任务,通常都设置为
NULL,使用系统的默认安全描述符。
bInheritHandles (是否继承句柄)
- 类型:
BOOL - 说明: 指定新进程是否继承父进程的句柄。
TRUE: 新进程会继承所有可继承的句柄。FALSE: 不继承。
- 默认: 通常为
FALSE,如果需要父子进程通过共享句柄(如文件、管道)进行通信,则需要设置为TRUE。
dwCreationFlags (创建标志)
- 类型:
DWORD - 说明: 一个或多个标志的组合,用于控制进程的创建方式。
CREATE_NO_WINDOW: 对于控制台应用程序,不创建控制台窗口,常用于后台运行。DETACHED_PROCESS: 创建一个没有控制台的新进程,新进程无法访问父进程的控制台。CREATE_SUSPENDED: 进程创建后立即挂起,直到调用ResumeThread才开始执行,这允许你在进程运行前对其进行修改(在加载 DLL 后)。CREATE_NEW_CONSOLE: 为新进程创建一个新的控制台窗口。HIGH_PRIORITY_CLASS,NORMAL_PRIORITY_CLASS,IDLE_PRIORITY_CLASS等:设置进程的优先级。
lpEnvironment (环境块)
- 类型:
LPVOID - 说明: 指定新进程的环境块,如果为
NULL,新进程将继承父进程的环境变量。
lpCurrentDirectory (当前工作目录)
- 类型:
LPCSTR - 说明: 指定新进程的初始工作目录,如果为
NULL,新进程将继承父进程的当前目录,这对于确保程序能找到其依赖的文件(如配置文件、DLL)非常有用。
lpStartupInfo (启动信息)
- 类型:
LPSTARTUPINFOA(或LPSTARTUPINFOW用于 Unicode) - 说明: 这是一个结构体,用于指定新进程的主窗口的外观和行为,例如窗口标题、位置、大小、标准输入/输出/错误句柄等。
- 重要: 在使用前,必须将这个结构体清零,最简单的方法是使用
SecureZeroMemory或memset:STARTUPINFO si = { 0 }; si.cb = sizeof(si); // 必须设置此字段为结构体的大小
lpProcessInformation (进程信息)
- 类型:
LPPROCESS_INFORMATION - 说明: 这是一个输出参数,
CreateProcess会向这个结构体中填充新创建进程和主线程的相关信息。hProcess: 新进程的句柄。hThread: 新进程主线程的句柄。dwProcessId: 新进程的 ID (PID)。dwThreadId: 新进程主线程的 ID (TID)。
- 重要: 使用完这些句柄后,必须调用
CloseHandle来关闭它们,否则会造成资源泄漏。
完整示例代码
下面是一个最基本、最常用的示例,演示如何创建一个子进程(notepad.exe)并等待它结束。
#include <windows.h>
#include <tchar.h> // 为了使用 _tprintf 等宽字符函数
#include <stdio.h>
int main() {
// 1. 定义变量
STARTUPINFO si = { 0 }; // 启动信息
PROCESS_INFORMATION pi = { 0 }; // 进程信息
// 2. 准备要执行的命令
// 注意:这里我们使用 NULL 作为 lpApplicationName,
// 所以可执行文件名必须包含在命令行的开头。
TCHAR szCmdLine[] = _T("notepad.exe");
// 3. 清空并设置 STARTUPINFO 结构
// si.cb 是一个必须的字段,告诉系统这个结构体的大小
si.cb = sizeof(si);
// 4. 调用 CreateProcess
// 注意:这里使用 _T 宏来支持 Unicode/ANSI
if (CreateProcess(
NULL, // 不使用 lpApplicationName,而是从命令行解析
szCmdLine, // 命令行
NULL, // 默认进程安全属性
NULL, // 默认线程安全属性
FALSE, // 不继承句柄
0, // 默认创建标志
NULL, // 使用父进程的环境变量
NULL, // 使用父进程的当前工作目录
&si, // 指向 STARTUPINFO 结构体的指针
&pi // 指向 PROCESS_INFORMATION 结构体的指针
)) {
// 5. 如果创建成功
_tprintf(_T("进程创建成功!\n"));
_tprintf(_T("进程 ID (PID): %d\n"), pi.dwProcessId);
_tprintf(_T("线程 ID (TID): %d\n"), pi.dwThreadId);
// 6. 等待子进程执行完毕
// WaitForSingleObject 会阻塞当前线程,直到 pi.hProcess 指定的对象(这里是进程) signaled(结束)
WaitForSingleObject(pi.hProcess, INFINITE);
_tprintf(_T("子进程已结束,\n"));
// 7. 关闭句柄
// 创建进程返回的句柄必须手动关闭
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
} else {
// 8. 如果创建失败
// 获取并打印错误代码
DWORD dwError = GetLastError();
_tprintf(_T("创建进程失败! 错误代码: %d\n"), dwError);
// 可以使用 FormatMessage 来获取更详细的错误信息
}
return 0;
}
关键注意事项
- 句柄泄漏:
CreateProcess返回的hProcess和hThread是内核对象。必须在使用完毕后调用CloseHandle来关闭它们,如果忘记关闭,会导致系统资源(句柄表)泄漏,最终可能导致程序无法创建新的进程或线程。 - 错误处理:
CreateProcess失败时返回0,此时应该调用GetLastError()来获取具体的错误码,这对于调试至关重要。 - 路径问题:
lpApplicationName或lpCommandLine中的路径包含空格,且没有用引号括起来,可能会导致解析错误。"C:\Program Files\My App\app.exe"可能会被错误地解析为C:\Program.exe。lpApplicationName为NULL,CreateProcess内部有复杂的规则来处理这种情况,但最稳妥的方式是确保路径是正确的,或者让lpApplicationName指向完整路径。 - 命令行限制:
lpCommandLine的最大长度大约是 32KB,如果需要传递非常长的参数,应考虑使用文件或其他IPC机制。 - 同步:父进程需要决定如何与子进程同步。
WaitForSingleObject是最简单的方式,它会阻塞父进程,如果需要父进程和子进程并行执行,就不要调用等待函数。
CreateProcess vs. system()
很多初学者会混淆 CreateProcess 和 C 标准库的 system() 函数。

(图片来源网络,侵删)
| 特性 | CreateProcess (Windows API) |
system() (C标准库) |
|---|---|---|
| 平台 | 仅 Windows | 跨平台 (POSIX, Windows, 等) |
| 控制力 | 极高,可以精确控制进程的创建、继承、环境、目录、窗口等。 | 低,本质上是调用 cmd.exe /c command (Windows) 或 /bin/sh -c command (Linux)。 |
| 性能 | 快,直接创建进程,开销小。 | 慢,需要额外启动一个 shell 进程(cmd.exe 或 sh)。 |
| 返回值 | 返回 BOOL 表示是否成功,进程的退出状态需要通过 WaitForSingleObject 和 GetExitCodeProcess 获取。 |
返回进程的退出码(在 Windows 上),成功启动 cmd.exe 但其内部命令失败,返回值可能不明确。 |
| 用途 | 需要精细控制、高性能、后台服务、与子进程深度交互的场景。 | 简单地执行一个命令,对性能和控制力要求不高的场景,例如执行 dir, ping 等。 |
在 Windows 平台下进行 C/C++ 开发,当你需要以编程方式控制程序启动时,CreateProcess 是更强大、更专业、更可靠的选择。system() 只适合一些非常简单的、一次性的命令执行任务。

(图片来源网络,侵删)
