You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

调用prettier子进程后fgetc无法返回,求跨平台解决方案

问题解决:Windows下调用Prettier管道通信卡住

问题场景

要实现类似echo {a:1} | prettier --stdin-filepath index.js的跨平台功能,通过C++调用Prettier并获取其标准输出。Windows平台下用MSVC v143编译的代码执行到fgetc时完全卡住,无任何ch:输出。

问题原因

  1. 输入管道未关闭:向子进程写入数据后,未关闭输入管道的写端,导致Prettier一直等待更多输入,不会结束输出并关闭输出管道,父进程的fgetc会一直阻塞等待EOF。
  2. 未等待子进程完成:创建子进程后直接关闭进程句柄,无法确保子进程处理完输入并输出结果,父进程提前读取可能遇到空管道阻塞。
  3. 缓冲区未刷新:使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 19:36:17