許多 Windows 安裝程式或工具需要呼叫主控台程式(如 PKZIP、dir)並捕捉其輸出文字,同時不想讓黑色的命令提示字元視窗閃現干擾使用者。Windows 匿名管道配合 CreateProcess 即可完美達成此需求。本文從管道基礎觀念出發,逐步建構完整範例程式碼。
管道提供單向資料流通道:寫入端(hWritePipe)送入資料,讀取端(hReadPipe)取出資料。匿名管道僅存在於父子行程之間,由 CreatePipe 建立:
BOOL CreatePipe(
PHANDLE hReadPipe, // 接收讀取端控制代碼
PHANDLE hWritePipe, // 接收寫入端控制代碼
LPSECURITY_ATTRIBUTES lpPipeAttributes, // 安全性屬性,決定可否繼承
DWORD nSize // 緩衝區大小,0 表由系統決定
);
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL; // 使用預設安全性描述元
sa.bInheritHandle = TRUE; // **關鍵:允許子行程繼承管道控制代碼**
bInheritHandle = TRUE 讓子行程能存取管道寫入端;若設 FALSE,子行程將拿不到控制代碼而導致管道失效。
使用 CreateProcess 啟動主控台程式,並透過 STARTUPINFO 結構指定:
hStdOutput = hWritePipe、hStdError = hWritePipe:將標準輸出/錯誤導向管道寫入端。wShowWindow = SW_HIDE:隱藏主控台視窗。dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW:啟用上述兩項設定。STARTUPINFO si;
GetStartupInfo(&si); // 取得目前行程預設值
si.cb = sizeof(STARTUPINFO);
si.hStdOutput = hWritePipe;
si.hStdError = hWritePipe;
si.wShowWindow = SW_HIDE;
si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
CreateProcess(NULL, L"cmd.exe /c dir /?", NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
bInheritHandles = TRUE(第 5 參數)確保子行程繼承父行程的管道控制代碼。
管道建立後,父行程同時持有讀取端與寫入端。子行程啟動後也持有寫入端(透過繼承)。若父行程不關閉自己的寫入端,管道會有「兩個寫入端、一個讀取端」,導致 ReadFile 永遠等待 EOF 而不會傳回。解決方法:
CloseHandle(hWritePipe); // 父行程立即關閉自己的寫入端
char buffer[4096];
DWORD bytesRead;
while (ReadFile(hReadPipe, buffer, 4095, &bytesRead, NULL)) {
buffer[bytesRead] = '\0';
m_Edit1 += buffer; // MFC CEdit 追加文字
UpdateData(FALSE);
Sleep(200); // 避免佔用過多 CPU
}
void CPipeDlg::OnButton1()
{
SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE };
HANDLE hRead, hWrite;
if (!CreatePipe(&hRead, &hWrite, &sa, 0)) { MessageBox(L"CreatePipe 失敗"); return; }
STARTUPINFO si; PROCESS_INFORMATION pi;
si.cb = sizeof(STARTUPINFO);
GetStartupInfo(&si);
si.hStdError = hWrite;
si.hStdOutput = hWrite;
si.wShowWindow = SW_HIDE;
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
if (!CreateProcess(NULL, L"cmd.exe /c dir /?", NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) {
MessageBox(L"CreateProcess 失敗"); CloseHandle(hRead); CloseHandle(hWrite); return;
}
CloseHandle(hWrite); // 關鍵步驟
char buf[4096]; DWORD dwRead;
while (ReadFile(hRead, buf, 4095, &dwRead, NULL)) {
buf[dwRead] = 0;
m_Edit1 += buf;
UpdateData(FALSE);
Sleep(200);
}
CloseHandle(hRead);
CloseHandle(pi.hProcess); CloseHandle(pi.hThread);
}
WriteFile。可另開執行緒持續讀取,或調大 nSize。ReadFile 讀取的是位元組流,若子行程輸出 Unicode 需自行轉換。CloseHandle(hWritePipe):最常見導致 ReadFile 不會傳回 0 的原因。匿名管道結合 CreateProcess 的標準控制代碼重導向,是 Windows 平台上「無聲執行主控台程式並捕獲輸出」的經典做法。掌握安全性屬性繼承、STARTUPINFO 設定與管道端點關閉時機,即可穩健實作此功能。