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

Windows 匿名管道技術實作:隱藏主控台視窗並重導向標準輸出

     編輯:關於C++

許多 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
}

完整 VC++ 6.0 範例程式碼

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。
  • Unicode/ANSI 不匹配:ReadFile 讀取的是位元組流,若子行程輸出 Unicode 需自行轉換。
  • 遺漏 CloseHandle(hWritePipe):最常見導致 ReadFile 不會傳回 0 的原因。

結語

匿名管道結合 CreateProcess 的標準控制代碼重導向,是 Windows 平台上「無聲執行主控台程式並捕獲輸出」的經典做法。掌握安全性屬性繼承、STARTUPINFO 設定與管道端點關閉時機,即可穩健實作此功能。

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