调用prettier子进程后fgetc无法返回,求跨平台解决方案
问题解决:Windows下调用Prettier管道通信卡住
问题场景
要实现类似echo {a:1} | prettier --stdin-filepath index.js的跨平台功能,通过C++调用Prettier并获取其标准输出。Windows平台下用MSVC v143编译的代码执行到fgetc时完全卡住,无任何ch:输出。
问题原因
- 输入管道未关闭:向子进程写入数据后,未关闭输入管道的写端,导致Prettier一直等待更多输入,不会结束输出并关闭输出管道,父进程的
fgetc会一直阻塞等待EOF。 - 未等待子进程完成:创建子进程后直接关闭进程句柄,无法确保子进程处理完输入并输出结果,父进程提前读取可能遇到空管道阻塞。
- 缓冲区未刷新:使用
fputs写入数据后,缓冲区可能未立即刷新到管道,Prettier无法及时收到完整输入。
修复后的代码
#include <Windows.h> #include <fcntl.h> #include <io.h> #include <iostream> #include <stdexcept> #include <string> HANDLE hChildStd_IN_Rd = NULL; HANDLE hChildStd_IN_Wr = NULL; HANDLE hChildStd_OUT_Rd = NULL; HANDLE hChildStd_OUT_Wr = NULL; int main() { const char source_code[] = "{a:1}"; FILE *fd_OUT = nullptr, *fd_IN = nullptr; PROCESS_INFORMATION piProcInfo = {0}; #ifdef _WIN32 try { SECURITY_ATTRIBUTES saAttr{}; saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); saAttr.bInheritHandle = TRUE; saAttr.lpSecurityDescriptor = NULL; if (!CreatePipe(&hChildStd_OUT_Rd, &hChildStd_OUT_Wr, &saAttr, 0)) throw std::runtime_error("StdoutRd CreatePipe"); if (!SetHandleInformation(hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0)) throw std::runtime_error("Stdout SetHandleInformation"); if (!CreatePipe(&hChildStd_IN_Rd, &hChildStd_IN_Wr, &saAttr, 0)) throw std::runtime_error("Stdin CreatePipe"); if (!SetHandleInformation(hChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0)) throw std::runtime_error("Stdin SetHandleInformation"); TCHAR szCmdline[] = TEXT( "C:\\Users\\yuto0214w\\AppData\\Roaming\\npm\\prettier.cmd " "--stdin-filepath index.js"); STARTUPINFO siStartInfo; ZeroMemory(&siStartInfo, sizeof(STARTUPINFO)); siStartInfo.cb = sizeof(STARTUPINFO); siStartInfo.hStdError = hChildStd_OUT_Wr; siStartInfo.hStdOutput = hChildStd_OUT_Wr; siStartInfo.hStdInput = hChildStd_IN_Rd; siStartInfo.dwFlags |= STARTF_USESTDHANDLES; if (!CreateProcess(NULL, szCmdline, NULL, NULL, TRUE, 0, NULL, NULL, &siStartInfo, &piProcInfo)) throw std::runtime_error("CreateProcess"); // 关闭不需要继承的句柄 CloseHandle(hChildStd_OUT_Wr); CloseHandle(hChildStd_IN_Rd); fd_OUT = _fdopen(_open_osfhandle((intptr_t)hChildStd_OUT_Rd, _O_RDONLY), "r"); fd_IN = _fdopen(_open_osfhandle((intptr_t)hChildStd_IN_Wr, _O_WRONLY), "w"); } catch (std::exception& e) { std::cout << "Encountered an error when running: " << e.what() << std::endl; // 异常时清理资源 if (piProcInfo.hProcess) CloseHandle(piProcInfo.hProcess); if (piProcInfo.hThread) CloseHandle(piProcInfo.hThread); if (hChildStd_OUT_Rd) CloseHandle(hChildStd_OUT_Rd); if (hChildStd_OUT_Wr) CloseHandle(hChildStd_OUT_Wr); if (hChildStd_IN_Rd) CloseHandle(hChildStd_IN_Rd); if (hChildStd_IN_Wr) CloseHandle(hChildStd_IN_Wr); return 1; } #else // pipe fork dup2 execlp fdopen 实现Linux平台逻辑 #endif // 写入输入并强制刷新缓冲区 fputs(source_code, fd_IN); fflush(fd_IN); // 关闭输入管道写端,告知子进程输入已结束 fclose(fd_IN); fd_IN = nullptr; // 等待子进程处理完成 WaitForSingleObject(piProcInfo.hProcess, INFINITE); std::string formatted_code; int c; while ((c = fgetc(fd_OUT)) != EOF) { formatted_code += static_cast<char>(c); std::cout << "ch: " << static_cast<char>(c) << std::endl; // 输出字符而非ASCII码更直观 } fclose(fd_OUT); // 清理子进程句柄 CloseHandle(piProcInfo.hProcess); CloseHandle(piProcInfo.hThread); std::cout << "\nFormatted result:\n" << formatted_code << std::endl; return 0; }
关键修改说明
- 关闭输入写端:写入数据并刷新后立即关闭
fd_IN,让Prettier知晓输入已结束,触发处理流程并输出结果。 - 等待子进程:调用
WaitForSingleObject等待子进程执行完毕,确保输出管道有完整数据可读。 - 资源清理:补充异常场景下的资源回收逻辑,避免句柄泄漏;子进程完成后及时关闭其进程和线程句柄。
- 输出优化:将读取的ASCII码转换为字符输出,便于直观验证读取内容。
内容的提问来源于stack exchange,提问作者yuto0214w
相关产品推荐
相关产品推荐

