Windows平台下如何用现代C++创建重定向stdin/stdout的子进程
Windows平台下C++风格的子进程双向IO重定向实现
当然有更贴合现代C风格的方案!你提到的旧版C语言HANDLE写法确实繁琐且容易出错,我们可以结合Win32 API的进程创建能力,再用C的RAII(资源获取即初始化)思想封装原生句柄,既实现同时重定向子进程的stdin和stdout,又能避免手动管理资源的麻烦。
核心思路
用CreateProcess创建子进程时,提前创建两个匿名管道:
- 一个管道用于父进程写 → 子进程读(绑定子进程的stdin)
- 另一个管道用于子进程写 → 父进程读(绑定子进程的stdout)
然后用C智能指针封装HANDLE,自动处理句柄的关闭,完全符合现代C的资源管理习惯。
完整代码示例
第一步:RAII封装HANDLE
先写一个简单的智能指针包装,避免手动调用CloseHandle导致的资源泄漏:
#include <windows.h> #include <memory> #include <string> #include <stdexcept> #include <iostream> #include <vector> // 自定义删除器,自动关闭HANDLE struct HandleDeleter { void operator()(HANDLE handle) const { if (handle != INVALID_HANDLE_VALUE) { CloseHandle(handle); } } }; // 用unique_ptr实现RAII的句柄管理 using UniqueHandle = std::unique_ptr<void, HandleDeleter>;
第二步:创建双向管道并启动子进程
封装一个函数,负责管道创建、进程启动和资源清理:
// 启动子进程并建立双向IO管道 void StartDuplexChildProcess(const std::wstring& cmdLine, UniqueHandle& childStdinWrite, UniqueHandle& childStdoutRead) { SECURITY_ATTRIBUTES sa{}; sa.nLength = sizeof(SECURITY_ATTRIBUTES); sa.bInheritHandle = TRUE; // 允许子进程继承管道句柄 sa.lpSecurityDescriptor = nullptr; // 创建子进程stdin管道:父写子读 HANDLE hChildStdinRead, hChildStdinWrite; if (!CreatePipe(&hChildStdinRead, &hChildStdinWrite, &sa, 0)) { throw std::runtime_error("Failed to create stdin pipe"); } // 父进程保留的写句柄不需要被子进程继承,取消继承标记 if (!SetHandleInformation(hChildStdinWrite, HANDLE_FLAG_INHERIT, 0)) { throw std::runtime_error("Failed to set stdin write handle inheritance"); } // 创建子进程stdout管道:子写父读 HANDLE hChildStdoutRead, hChildStdoutWrite; if (!CreatePipe(&hChildStdoutRead, &hChildStdoutWrite, &sa, 0)) { throw std::runtime_error("Failed to create stdout pipe"); } // 父进程保留的读句柄取消继承标记 if (!SetHandleInformation(hChildStdoutRead, HANDLE_FLAG_INHERIT, 0)) { throw std::runtime_error("Failed to set stdout read handle inheritance"); } // 配置子进程的启动信息,绑定标准输入输出 STARTUPINFOW si{}; si.cb = sizeof(STARTUPINFOW); si.hStdInput = hChildStdinRead; si.hStdOutput = hChildStdoutWrite; si.hStdError = hChildStdoutWrite; // 可选:把子进程的stderr也重定向到stdout si.dwFlags |= STARTF_USESTDHANDLES; PROCESS_INFORMATION pi{}; // 启动子进程,这里用cmd.exe作为示例,你可以替换成自己的可执行文件 if (!CreateProcessW( nullptr, const_cast<wchar_t*>(cmdLine.c_str()), nullptr, nullptr, TRUE, // 允许子进程继承句柄 0, nullptr, nullptr, &si, &pi)) { throw std::runtime_error("Failed to start child process"); } // 关闭不需要的句柄:子进程端的管道句柄、子进程的线程和进程句柄 CloseHandle(hChildStdinRead); CloseHandle(hChildStdoutWrite); CloseHandle(pi.hThread); CloseHandle(pi.hProcess); // 将父进程需要的句柄交给调用者 childStdinWrite = UniqueHandle(hChildStdinWrite); childStdoutRead = UniqueHandle(hChildStdoutRead); }
第三步:使用示例
下面是一个简单的调用示例,向子进程发送命令并读取输出:
int main() { try { UniqueHandle childStdinWrite, childStdoutRead; // 启动cmd.exe作为子进程 StartDuplexChildProcess(L"cmd.exe", childStdinWrite, childStdoutRead); // 向子进程写入命令 const std::string command = "echo Hello from parent!\n"; DWORD bytesWritten; if (!WriteFile(childStdinWrite.get(), command.c_str(), command.size(), &bytesWritten, nullptr)) { throw std::runtime_error("Failed to write to child stdin"); } // 读取子进程的输出 std::vector<char> buffer(4096); DWORD bytesRead; if (ReadFile(childStdoutRead.get(), buffer.data(), buffer.size(), &bytesRead, nullptr)) { std::cout << "Child process output:\n" << std::string(buffer.data(), bytesRead) << std::endl; } else { throw std::runtime_error("Failed to read from child stdout"); } // 可以继续进行更多的读写操作... } catch (const std::exception& e) { std::cerr << "Error occurred: " << e.what() << std::endl; return 1; } return 0; }
关键说明
- RAII的优势:用
UniqueHandle自动管理HANDLE,不需要手动调用CloseHandle,避免了资源泄漏和忘记关闭句柄的问题,完全符合C++的资源管理哲学。 - 双向管道的必要性:不同于
_popen只能单向通信,这里用两个独立的管道分别处理输入和输出,实现了完整的双向交互。 - 句柄继承设置:必须取消父进程保留的句柄的继承标记,否则子进程会持有额外的管道句柄,导致管道无法正确关闭,进而引发读写阻塞。
- 异步扩展:如果需要异步IO,可以结合Windows的Overlapped机制和C++的异步工具(比如
std::async),实现非阻塞的读写操作。
内容的提问来源于stack exchange,提问作者KBOPYM
相关产品推荐
相关产品推荐

