首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

CreateProcess 完整實戰指南:建立子進程、等待結束、參數傳遞與安全屬性

     編輯:關於C++

前言:Win32 進程建立的核心 API

CreateProcess 是 Windows 建立新進程的唯一核心 API (底層為 NtCreateUserProcess)。功能強大但參數繁多,常見錯誤包括:命令列字串緩衝區不足、安全屬性繼承錯誤、環境區塊格式錯誤、當前目錄未正確設定。本文提供完整、可直接用於生產環境的封裝範例與最佳實務。

函式原型與關鍵參數


BOOL CreateProcess(
  LPCSTR lpApplicationName,        // 模組名稱 (NULL 時從命令列解析)
  LPSTR  lpCommandLine,            // **完整命令列** (需可寫入緩衝區)
  LPSECURITY_ATTRIBUTES lpProcessAttributes,  // 進程安全描述符 (NULL=預設)
  LPSECURITY_ATTRIBUTES lpThreadAttributes,   // 執行緒安全描述符
  BOOL   bInheritHandles,          // TRUE: 子進程繼承可繼承控制代碼
  DWORD  dwCreationFlags,          // 建立旗標 (CREATE_SUSPENDED, DETACHED_PROCESS, CREATE_NEW_CONSOLE...)
  LPVOID lpEnvironment,            // 環境區塊指標 (NULL=繼承父進程)
  LPCSTR lpCurrentDirectory,       // 子進程當前目錄 (NULL=繼承父進程)
  LPSTARTUPINFO lpStartupInfo,     // 啟動資訊 (視窗顯示、標準控制代碼重導向)
  LPPROCESS_INFORMATION lpProcessInformation // 輸出: 進程/執行緒句柄、ID
);

核心結構體初始化


STARTUPINFO si = { sizeof(STARTUPINFO) }; // cb 必須設定大小!
PROCESS_INFORMATION pi = {0};

// 常見啟動設定
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE; // 或 SW_SHOWDEFAULT, SW_MINIMIZE
// 重導向標準輸入/輸出/錯誤 (需 bInheritHandles=TRUE, 建立管道)
si.hStdInput  = hChildStdinRead;
si.hStdOutput = hChildStdoutWrite;
si.hStdError  = hChildStderrWrite;
si.dwFlags |= STARTF_USESTDHANDLES;

完整封裝範例:建立子進程並等待結束 (C++ / C++ Builder)


#include 
#include 
#include 

// 例外類別
class ProcessException : public std::runtime_error {
public:
  ProcessException(const char* msg, DWORD err) 
    : std::runtime_error(std::string(msg) + " Error: " + std::to_string(err)) {}
};

// 建立進程並等待結束,回傳退出碼
DWORD CreateChildProcessAndWait(const std::string& cmdLine, DWORD timeoutMs = INFINITE) {
  // 命令列需可寫入緩衝區 (CreateProcess 可能修改)
  std::vector cmdBuf(cmdLine.begin(), cmdLine.end());
  cmdBuf.push_back('\0');

  STARTUPINFO si = { sizeof(STARTUPINFO) };
  PROCESS_INFORMATION pi = {0};

  // 預設安全屬性、不繼承控制代碼、無特殊旗標、繼承環境與目錄
  if (!CreateProcess(
        NULL,                   // lpApplicationName
        cmdBuf.data(),          // lpCommandLine (可修改緩衝區)
        NULL, NULL,             // Process/Thread Security
        FALSE,                  // bInheritHandles
        0,                      // dwCreationFlags
        NULL,                   // Environment
        NULL,                   // CurrentDirectory
        &si, &pi))              // StartupInfo, ProcessInfo
  {
    throw ProcessException("CreateProcess failed", GetLastError());
  }

  // 立即關閉我們不需要的控制代碼 (避免洩漏)
  CloseHandle(pi.hThread); // 不需等待主執行緒單獨結束

  // 等待進程結束
  DWORD waitResult = WaitForSingleObject(pi.hProcess, timeoutMs);
  if (waitResult == WAIT_FAILED)
    throw ProcessException("WaitForSingleObject failed", GetLastError());
  if (waitResult == WAIT_TIMEOUT)
    throw ProcessException("Process timeout", ERROR_TIMEOUT);

  // 取得退出碼
  DWORD exitCode = 0;
  if (!GetExitCodeProcess(pi.hProcess, &exitCode))
    throw ProcessException("GetExitCodeProcess failed", GetLastError());

  CloseHandle(pi.hProcess);
  return exitCode;
}

// 使用範例
int main() {
  try {
    DWORD exitCode = CreateChildProcessAndWait("notepad.exe \"C:\\Test\\ReadMe.txt\"", 30000);
    printf("Notepad exited with code: %lu\n", exitCode);
  } catch (const ProcessException& e) {
    fprintf(stderr, "Error: %s\n", e.what());
    return 1;
  }
  return 0;
}

C++ Builder (VCL/AnsiString) 版本


DWORD CreateChildProcessAndWait(const AnsiString& cmdLine) {
  PROCESS_INFORMATION pi = {0};
  STARTUPINFO si = { sizeof(STARTUPINFO) };

  // AnsiString::c_str() 回傳 const char*,需複製到可寫緩衝區
  std::vector buf(cmdLine.Length() + 1);
  strcpy(buf.data(), cmdLine.c_str());

  if (!CreateProcess(NULL, buf.data(), NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi))
    throw Exception("CreateProcess 失敗,錯誤碼: " + IntToStr(GetLastError()));

  CloseHandle(pi.hThread);
  WaitForSingleObject(pi.hProcess, INFINITE);

  DWORD exitCode = 0;
  GetExitCodeProcess(pi.hProcess, &exitCode);
  CloseHandle(pi.hProcess);
  return exitCode;
}

進階主題與常見坑點

1. 命令列解析規則 (極重要)

  • lpApplicationName 為 NULL 時,系統從 lpCommandLine 首個 token 解析執行檔路徑。**路徑含空白必須雙引號括住**:"C:\Program Files\App\app.exe" arg1 arg2。
  • 引號巢狀規則複雜,建議**永遠提供完整路徑於 lpApplicationName**,lpCommandLine 僅放參數 (或重複完整命令列)。
  • Unicode 版 CreateProcessW 支援長路徑 (\\?\ 前綴) 與完整 Unicode 參數。

2. 控制代碼繼承

  • bInheritHandles=TRUE 時,**所有**標記為可繼承的控制代碼 (建立時 SECURITY_ATTRIBUTES.bInheritHandle=TRUE) 都會被子進程繼承。
  • **最佳實務**:預設 FALSE。需重導向標準 I/O 時,僅對管道控制代碼設定可繼承,並設定 STARTUPINFO.dwFlags |= STARTF_USESTDHANDLES。

3. 環境區塊格式

  • lpEnvironment=NULL 繼承父進程環境 (複製一份)。
  • 自訂環境:雙重 NULL 結尾的 NAME=VALUE\0NAME2=VALUE2\0\0 字串陣列。Unicode 版需 WCHAR 陣列。
  • 修改單一變數:GetEnvironmentStrings → 修改 → CreateProcess(..., newEnv, ...) → FreeEnvironmentStrings。

4. 建立旗標 (dwCreationFlags) 常用組合

旗標用途
CREATE_SUSPENDED主執行緒暫停建立,需 ResumeThread 繼續 (注入 DLL、記憶體修改常用)
DETACHED_PROCESS無主控台視窗 (適用服務/背景工作者)
CREATE_NEW_CONSOLE強制建立新主控台視窗
CREATE_UNICODE_ENVIRONMENT環境區塊為 Unicode (搭配 CreateProcessW)
DEBUG_PROCESS / DEBUG_ONLY_THIS_PROCESS以除錯器身分建立
EXTENDED_STARTUPINFO_PRESENT使用 STARTUPINFOEX (支援屬性清單: 親進程、CPU 群組、Mitigation 政策)

5. 非同步/背景監控模式

不等待結束,改用 WaitForSingleObject / WaitForMultipleObjects / RegisterWaitForSingleObject (執行緒池回呼) / CreateIoCompletionPort 監控進程句柄。結束後務必 GetExitCodeProcess + CloseHandle。

6. 安全性:ACL 與完整性等級

  • 傳遞 SECURITY_ATTRIBUTES 指定自訂 DACL,限制誰可操作子進程物件。
  • Vista+ 完整性等級:子進程預設繼承父進程 IL。需降低 IL (Low Integrity) 可用 SetTokenInformation + CreateProcessAsUser / CreateProcessWithTokenEx。

除錯檢查清單

  1. GetLastError() 常見碼:2 (檔案不存在/路徑錯誤)、5 (存取被拒/權限不足)、87 (參數錯誤/命令列格式)、193 (非有效 Win32 應用程式/位元數不符)、206 (命令列過長 > 32768 字元)。
  2. 使用 DebugView / Process Monitor (ProcMon) 過濾 CreateProcess / Process Start 事件,觀察實際命令列、環境、當前目錄、完整性等級。
  3. 檢查 STARTUPINFO.cb 是否正確設定 (最常見新手錯誤)。

結語

CreateProcess 雖參數繁多,但掌握「命令列格式、安全屬性繼承、環境區塊、啟動資訊、旗標組合」五大核心面向,並遵循「最小權限、明確指定路徑、資源即釋放」原則,即能穩健建立、控制、監控子進程。本文提供的封裝函式可直接整合至 C++ Builder、Visual C++、標準 C++17 專案,作為進程管理基礎設施。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved