【C++】在 Windows 系统调用第三方程序(创建进程)
文章目录
- 零、对比总结
- 一、通过 CreateProcess 调用第三方程序
- 1.1 官方 API 文档
- 1.2 API 介绍
- 1.3 使用示例
- 1.3.1 简单调用
- 1.3.2 调用+挂起+执行
- 二、通过 ShellExecute 调用第三方程序
- 2.1 官方 API 文档
- 2.2 API 介绍
- 2.2.1 ShellExecute
- 2.2.2 ShellExecuteEx
零、对比总结
注意:以下 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; // 保留,必须为NULLLPSTR 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; // 保留,必须为0LPBYTE lpReserved2; // 保留,必须为NULLHANDLE 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();
}