文章目录
零、对比总结
注意:以下 API 都需要包含头文件 #include <windows.h>
注意:以下 API 都需要包含头文件 #include <windows.h>
注意:以下 API 都需要包含头文件 #include <windows.h>
特性 | CreateProcess | ShellExecute | ShellExecuteEx |
---|---|---|---|
复杂度 | 高 | 低 | 中等 |
性能 | 最高 | 中等 | 中等 |
文件关联 | 不支持 | 支持 | 支持 |
进程句柄 | 支持 | 不支持 | 支持 |
同步执行 | 支持 | 不支持 | 支持 |
打开文档 | 不支持 | 支持 | 支持 |
管理员权限 | 复杂 | 支持 | 支持 |
错误处理 | 详细 | 简单 | 详细 |
重定向IO | 支持 | 不支持 | 不支持 |
选择建议
- 使用 CreateProcess:
- 需要精确控制进程创建
- 需要重定向输入输出
- 只启动可执行文件
- 对性能要求很高
- 使用 ShellExecute:
- 简单的文件打开操作
- 不需要等待进程结束
- 打开文档、网页等
- 代码简洁性优先
- 使用 ShellExecuteEx:
- 需要等待进程结束(如安装程序)
- 需要以管理员权限运行
- 需要进程句柄进行后续操作
- 平衡功能和易用性
一、通过 CreateProcess 调用第三方程序
1.1 官方 API 文档
CreateProcess
是宏定义,实际使用的是 CreateProcessA
和 CreateProcessW
函数,他会根据是否定义了 UNICODE
宏来决定使用哪个版本,如果定义了,则使用 CreateProcessW
版本,否则,则使用 CreateProcessA
版本。
访问:CreateProcessA CreateProcessW
1.2 API 介绍
BOOL CreateProcess(
[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
);
lpApplicationName
—— 第三方程序路径,比如:D:\yyh\earn_money.exe
,也可以填NULL
,如果为NULL
,则第二个参数必须指定第三方程序路径lpCommandLine
—— 调用参数,比如:--earn
,如果第一个参数为NULL
,则该参数必须指定第三方程序路径,同时为了区分第三方程序路径和参数,最好使用双引号将路径包裹起来(避免路径中有空格而造成错误),例如:"D:\yyh day\sleep.exe" --sleep
lpProcessAttributes
—— 进程安全属性,99% 的情况下都填NULL
,除非需要特殊的安全设置或句柄继承控制lpThreadAttributes
—— 线程安全属性,99% 的情况下都填NULL
,除非需要特殊的安全设置或句柄继承控制bInheritHandles
—— 继承句柄,子进程是否可以继承父进程的句柄,一般填FALSE
,除非被创建的进程需要共享父进程打开的资源(文件、管道、事件等),才填TRUE
。dwCreationFlags
—— 创建标志,具体可以访问:进程创建标志,下面列出一些常用的:0
- 默认值,正常创建进程CREATE_SUSPENDED
- 创建后立即挂起主线程,只有调用ResumeThread
函数后才执行。
lpEnvironment
—— 环境,一般填NULL
,表示继承父进程的环境,除非使用新的环境变量;lpCurrentDirectory
—— 当前目录,一般填NULL
,表示继承父进程的工作目录,除非使用新的工作目录;lpStartupInfo
—— 启动信息,用于指定新进程主窗口的外观和行为,参数类型如下:
typedef struct _STARTUPINFO {
DWORD cb; // 结构体大小
LPSTR lpReserved; // 保留,必须为NULL
LPSTR lpDesktop; // 桌面名称
LPSTR lpTitle; // 控制台窗口标题
DWORD dwX; // 窗口左上角X坐标
DWORD dwY; // 窗口左上角Y坐标
DWORD dwXSize; // 窗口宽度
DWORD dwYSize; // 窗口高度
DWORD dwXCountChars; // 控制台缓冲区宽度(字符)
DWORD dwYCountChars; // 控制台缓冲区高度(字符)
DWORD dwFillAttribute; // 控制台文本和背景颜色
DWORD dwFlags; // 指定哪些成员有效
WORD wShowWindow; // 窗口显示状态
WORD cbReserved2; // 保留,必须为0
LPBYTE lpReserved2; // 保留,必须为NULL
HANDLE hStdInput; // 标准输入句柄
HANDLE hStdOutput; // 标准输出句柄
HANDLE hStdError; // 标准错误句柄
} STARTUPINFO;
常用的初始化流程:
STARTUPINFO si = {0};
si.cb = sizeof(STARTUPINFO);
CreateProcess(NULL, L"xxx.exe", NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
STARTUPINFO si = {0};
si.cb = sizeof(STARTUPINFO);
si.dwFlags = STARTF_USESHOWWINDOW; // 使用 wShowWindow
si.wShowWindow = SW_HIDE; // 隐藏窗口
CreateProcess(NULL, L"xxx.exe", NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
lpProcessInformation
—— 进程信息,输出参数,指向PROCESS_INFORMATION
结构体,用于接收新创建进程和线程的信息,参数类型:
typedef struct _PROCESS_INFORMATION {
HANDLE hProcess; // 新进程的句柄
HANDLE hThread; // 新进程主线程的句柄
DWORD dwProcessId; // 新进程的进程ID (PID)
DWORD dwThreadId; // 新进程主线程的线程ID (TID)
} PROCESS_INFORMATION;
可以用来控制创建的进程的状态,比如:
STARTUPINFO si = {0};
PROCESS_INFORMATION pi = {0}; // 接收进程信息
si.cb = sizeof(STARTUPINFO);
BOOL success = CreateProcess(NULL, L"xxx.exe", NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi);
if (success) {
printf("进程ID: %d\n", pi.dwProcessId);
printf("线程ID: %d\n", pi.dwThreadId);
// 恢复进程执行
ResumeThread(pi.hThread);
// 等待进程结束
WaitForSingleObject(pi.hProcess, INFINITE);
// 获取退出代码
DWORD exitCode;
GetExitCodeProcess(pi.hProcess, &exitCode);
// 必须关闭句柄
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
- 返回值,创建成功返回
TRUE
,失败返回FALSE
;
1.3 使用示例
1.3.1 简单调用
std::wstring cmdLine = L"\"D:\\yyh\\happy.exe\" --play --eat";
STARTUPINFO si = {sizeof(si)};
PROCESS_INFORMATION pi;
BOOL success = CreateProcess(NULL, // 应用程序名称
const_cast<LPWSTR>(cmdLine.c_str()), // 命令行
NULL, // 进程安全属性
NULL, // 线程安全属性
FALSE, // 继承句柄
0, // 创建标志
NULL, // 环境
NULL, // 当前目录
&si, // 启动信息
&pi // 进程信息
);
if (success) {
WaitForSingleObject(pi.hProcess, INFINITE); // 等待进程完成
DWORD exitCode;
GetExitCodeProcess(pi.hProcess, &exitCode); // 获取退出码
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return exitCode;
} else {
DWORD err = GetLastError(); // 获取错误信息
return err;
}
1.3.2 调用+挂起+执行
std::wstring exePath = L"D:\\yyh\\happy.exe";
std::wstring param = L"--play --eat";
STARTUPINFO si = {sizeof(si)};
PROCESS_INFORMATION pi;
// 创建进程
BOOL success = CreateProcess(exePath, // 应用程序名称
param, // 命令行
NULL, // 进程安全属性
NULL, // 线程安全属性
FALSE, // 继承句柄
CREATE_SUSPENDED, // 创建标志 - 先挂起,检查成功后再恢复
NULL, // 环境
NULL, // 当前目录
&si, // 启动信息
&pi // 进程信息
);
DWORD exitCode;
if (success) {
// 恢复进程执行
ResumeThread(pi.hThread);
// 等待进程完成,设置超时时间(5分钟)
DWORD waitResult = WaitForSingleObject(pi.hProcess, 5 * 60 * 1000);
if (waitResult == WAIT_TIMEOUT) {
// 超时,直接强制终止
TerminateProcess(pi.hProcess, 1);
exitCode = -1;
} else if (waitResult == WAIT_OBJECT_0) {
// 进程正常结束,获取退出码
if (!GetExitCodeProcess(pi.hProcess, &exitCode)) {
// 获取退出码失败
exitCode = GetLastError();
}
} else {
// 等待进程完成出现其他问题
exitCode = GetLastError();
}
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
} else {
// 创建进程失败
DWORD exitCode = GetLastError();
// 根据具体错误码提供更详细的错误信息
switch (exitCode) {
case ERROR_FILE_NOT_FOUND:
Logger::LogDebug(L"没有找到文件!");
break;
case ERROR_ACCESS_DENIED:
Logger::LogDebug(L"权限不够!");
break;
case ERROR_BAD_EXE_FORMAT:
Logger::LogDebug(L"非法的调用格式!");
break;
case ERROR_OUTOFMEMORY:
Logger::LogDebug(L"超过内存限制!");
break;
case ERROR_PATH_NOT_FOUND:
Logger::LogDebug(L"没有找到路径!");
break;
default:
Logger::LogDebug(L"不知道什么问题!");
break;
}
}
return exitCode;
二、通过 ShellExecute 调用第三方程序
2.1 官方 API 文档
ShellExecute
也是宏定义,实际使用时也是根据是否定义了 UNICODE
宏来判断使用哪个版本,如果定义了,则使用 ShellExecuteW
版本,或者使用 ShellExecuteA
版本。
ShellExecute
是异步的,创建完只知道是否创建成功,不知道运行情况。如果需要知道运行情况,则使用增强版本 ShellExecuteEx
,同样,它也是宏定义,实际也是根据 UNICODE
来决定使用 ShellExecuteExA
还是 ShellExecuteExW
。
访问:ShellExecuteA ShellExecuteW ShellExecuteExA ShellExecuteExW
2.2 API 介绍
2.2.1 ShellExecute
HINSTANCE ShellExecute(
[in, optional] HWND hwnd,
[in, optional] LPCSTR lpOperation,
[in] LPCSTR lpFile,
[in, optional] LPCSTR lpParameters,
[in, optional] LPCSTR lpDirectory,
[in] INT nShowCmd
);
hwnd
类型:HWND
功能: 父窗口句柄
说明: 如果操作失败需要显示错误对话框,该对话框的父窗口。可以为NULL
lpOperation
类型:LPCSTR
功能: 指定要执行的操作
常用值:- “
open
” - 打开文件或程序(默认操作) - “
edit
” - 编辑文件 - “
print
” - 打印文件 - “
explore
” - 浏览文件夹 - “
find
” - 搜索 - “
runas
” - 以管理员权限运行 NULL
- 使用默认操作
- “
lpFile
类型:LPCSTR
功能: 要打开的文件名或程序名
示例:"notepad.exe" ,"C:\\test.txt","https://www.example.com"
lpParameters
类型:LPCSTR
功能: 传递给程序的命令行参数
说明: 如果 lpFile 是文档文件,此参数应为NULL
lpDirectory
类型:LPCSTR
功能: 指定工作目录
说明: 程序启动时的当前目录,可以为NULL
nShowCmd
类型:INT
功能: 指定程序窗口的显示方式
常用值:SW_HIDE
(0) - 隐藏窗口SW_SHOWNORMAL
(1) - 正常显示SW_SHOWMINIMIZED
(2) - 最小化显示SW_SHOWMAXIMIZED
(3) - 最大化显示
- 返回值
成功: 返回值 > 32
失败: 返回值 ≤ 32,具体错误码含义:
0: 内存不足
2: 文件未找到
3: 路径未找到
5: 访问被拒绝
8: 内存不足
26: 共享冲突
27: 关联不完整
28: DDE 超时
29: DDE 失败
30: DDE 忙
31: 没有关联
2.2.2 ShellExecuteEx
BOOL ShellExecuteExW(
[in, out] SHELLEXECUTEINFOW *pExecInfo
);
pExecInfo
—— 执行信息,类型为SHELLEXECUTEINFO*
,结构如下:
typedef struct _SHELLEXECUTEINFO {
DWORD cbSize; // 结构体大小
ULONG fMask; // 标志位,指定哪些成员有效
HWND hwnd; // 父窗口句柄
LPCSTR lpVerb; // 操作类型(如 "open", "runas")
LPCSTR lpFile; // 要执行的文件
LPCSTR lpParameters; // 命令行参数
LPCSTR lpDirectory; // 工作目录
int nShow; // 窗口显示方式
HINSTANCE hInstApp; // 应用程序实例句柄(输出)
LPVOID lpIDList; // PIDL(项目标识符列表)
LPCSTR lpClass; // 文件类
HKEY hkeyClass; // 注册表键
DWORD dwHotKey; // 热键
union {
HANDLE hIcon; // 图标句柄
HANDLE hMonitor; // 监视器句柄
} DUMMYUNIONNAME;
HANDLE hProcess; // 进程句柄(输出)
} SHELLEXECUTEINFO, *LPSHELLEXECUTEINFO;
实际使用时,不需要每个参数都要填写,只需要指定部分参数即可,例如:
SHELLEXECUTEINFO sei = { 0 };
sei.cbSize = sizeof(SHELLEXECUTEINFO); // 结构体大小
sei.fMask = SEE_MASK_NOCLOSEPROCESS; // 保持进程句柄打开,可以通过 hProcess 获取进程句柄
sei.hwnd = NULL;
sei.lpVerb = "open"; // 打开文件,如果要以管理员权限打开,则改为 "runas"
sei.lpFile = "xxx.exe";
sei.lpParameters = "--abc --cde";
sei.lpDirectory = NULL;
sei.nShow = SW_SHOWNORMAL; // 正常显示窗口
if (ShellExecuteEx(&sei)) {
// 成功启动
if (sei.hProcess) {
// 等待进程结束
WaitForSingleObject(sei.hProcess, INFINITE);
CloseHandle(sei.hProcess);
}
} else {
// 失败处理
DWORD error = GetLastError();
}