【C++】在 Windows 系统调用第三方程序(创建进程)

零、对比总结

注意:以下 API 都需要包含头文件 #include <windows.h>
注意:以下 API 都需要包含头文件 #include <windows.h>
注意:以下 API 都需要包含头文件 #include <windows.h>

特性CreateProcessShellExecuteShellExecuteEx
复杂度中等
性能最高中等中等
文件关联不支持支持支持
进程句柄支持不支持支持
同步执行支持不支持支持
打开文档不支持支持支持
管理员权限复杂支持支持
错误处理详细简单详细
重定向IO支持不支持不支持

选择建议

  1. 使用 CreateProcess:
    • 需要精确控制进程创建
    • 需要重定向输入输出
    • 只启动可执行文件
    • 对性能要求很高
  2. 使用 ShellExecute:
    • 简单的文件打开操作
    • 不需要等待进程结束
    • 打开文档、网页等
    • 代码简洁性优先
  3. 使用 ShellExecuteEx:
    • 需要等待进程结束(如安装程序)
    • 需要以管理员权限运行
    • 需要进程句柄进行后续操作
    • 平衡功能和易用性

一、通过 CreateProcess 调用第三方程序

1.1 官方 API 文档

  CreateProcess 是宏定义,实际使用的是 CreateProcessACreateProcessW 函数,他会根据是否定义了 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
);
  1. lpApplicationName —— 第三方程序路径,比如:D:\yyh\earn_money.exe ,也可以填 NULL ,如果为 NULL,则第二个参数必须指定第三方程序路径
  2. lpCommandLine —— 调用参数,比如:--earn ,如果第一个参数为 NULL,则该参数必须指定第三方程序路径,同时为了区分第三方程序路径和参数,最好使用双引号将路径包裹起来(避免路径中有空格而造成错误),例如:"D:\yyh day\sleep.exe" --sleep
  3. lpProcessAttributes —— 进程安全属性,99% 的情况下都填 NULL ,除非需要特殊的安全设置或句柄继承控制
  4. lpThreadAttributes —— 线程安全属性,99% 的情况下都填 NULL ,除非需要特殊的安全设置或句柄继承控制
  5. bInheritHandles —— 继承句柄,子进程是否可以继承父进程的句柄,一般填 FALSE ,除非被创建的进程需要共享父进程打开的资源(文件、管道、事件等),才填 TRUE
  6. dwCreationFlags —— 创建标志,具体可以访问:进程创建标志,下面列出一些常用的:
    • 0 - 默认值,正常创建进程
    • CREATE_SUSPENDED - 创建后立即挂起主线程,只有调用 ResumeThread 函数后才执行。
  7. lpEnvironment —— 环境,一般填 NULL ,表示继承父进程的环境,除非使用新的环境变量;
  8. lpCurrentDirectory —— 当前目录,一般填 NULL ,表示继承父进程的工作目录,除非使用新的工作目录;
  9. 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);
  1. 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);
}
  1. 返回值,创建成功返回 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
);
  1. hwnd
    类型: HWND
    功能: 父窗口句柄
    说明: 如果操作失败需要显示错误对话框,该对话框的父窗口。可以为 NULL
  2. lpOperation
    类型: LPCSTR
    功能: 指定要执行的操作
    常用值:
    • open” - 打开文件或程序(默认操作)
    • edit” - 编辑文件
    • print” - 打印文件
    • explore” - 浏览文件夹
    • find” - 搜索
    • runas” - 以管理员权限运行
    • NULL - 使用默认操作
  3. lpFile
    类型: LPCSTR
    功能: 要打开的文件名或程序名
    示例:"notepad.exe" ,"C:\\test.txt","https://www.example.com"
  4. lpParameters
    类型: LPCSTR
    功能: 传递给程序的命令行参数
    说明: 如果 lpFile 是文档文件,此参数应为 NULL
  5. lpDirectory
    类型: LPCSTR
    功能: 指定工作目录
    说明: 程序启动时的当前目录,可以为 NULL
  6. nShowCmd
    类型: INT
    功能: 指定程序窗口的显示方式
    常用值:
    • SW_HIDE (0) - 隐藏窗口
    • SW_SHOWNORMAL (1) - 正常显示
    • SW_SHOWMINIMIZED (2) - 最小化显示
    • SW_SHOWMAXIMIZED (3) - 最大化显示
  7. 返回值
    成功: 返回值 > 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
);
  1. 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();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值