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

捕获cmd.exe输出时ReadFile出现阻塞的技术问题排查

问题描述

我正在编写一个C++例程,用于同步生成进程并以字符串形式返回输出。需求如下:

  • 替代popen,在Windows上静默执行(无控制台窗口弹出)
  • 保持与popen/system的调用兼容性:无需在dir c:/mydir这类命令前添加cmd /c即可直接通过cmd.exe执行

当前代码在传入命令"dir \"c:/Program Files\""时出现问题:ReadFile读取前128字节后阻塞。但如果将CreateProcessW的lpApplicationName设为NULL,并在命令前手动添加cmd /c,则能正常运行无阻塞。

当前代码如下:

static bool _GetCmdExeFullPath(std::basic_string<WCHAR>& result)
{
   WCHAR sysDir[MAX_PATH];

   // Get the system directory
   if (GetSystemDirectoryW(sysDir, MAX_PATH) == 0) {
      // Handle error
      return false;
   }

   result = sysDir;
   result += L"\\cmd.exe";

   return true;
}

bool __process_execute(const std::string& cmd, std::string& processOutput)
{
   std::basic_string<WCHAR> cmdExe;
   if (! _GetCmdExeFullPath(cmdExe))
      return false;

   SECURITY_ATTRIBUTES saAttr;
   HANDLE hRead, hWrite;

   saAttr.nLength = sizeof(saAttr);
   saAttr.bInheritHandle = TRUE;
   saAttr.lpSecurityDescriptor = NULL;

   if (!CreatePipe(&hRead, &hWrite, &saAttr, 0))
   {
      //std::cerr << "Error: Unable to create pipe" << std::endl;
      return false;
   }

   STARTUPINFOW si;
   PROCESS_INFORMATION pi;

   ZeroMemory(&si, sizeof(si));
   si.cb = sizeof(si);
   si.hStdError = hWrite;
   si.hStdOutput = hWrite;
   si.dwFlags |= STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
   si.wShowWindow = SW_HIDE;

   ZeroMemory(&pi, sizeof(pi));

   const std::basic_string<WCHAR> wCmd = __utf8_to_WCHAR(cmd.c_str());

   // We have to cast away const here because the API doesn't specify const. But it also does not modify the string.
   //if (!CreateProcessW(NULL, const_cast<LPWSTR>(wCmd.c_str()), NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi))
   if (!CreateProcessW(cmdExe.c_str(), const_cast<LPWSTR>(wCmd.c_str()), NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi))
   {
      DWORD error = GetLastError();
      //std::cerr << "Error: Unable to create process" << std::endl;
      CloseHandle(hRead);
      CloseHandle(hWrite);
      return false;
   }

   CloseHandle(hWrite);

   DWORD bytesRead;
   const int bufferSize = 4096;
   char buffer[bufferSize];
   std::string output = "";

   while (true)
   {
      if (!ReadFile(hRead, buffer, bufferSize - 1, &bytesRead, NULL) || bytesRead == 0)
      {
         break;
      }

      buffer[bytesRead] = '\0';
      output += buffer;
   }

   CloseHandle(hRead);

   WaitForSingleObject(pi.hProcess, INFINITE);

   CloseHandle(pi.hThread);
   CloseHandle(pi.hProcess);

   std::basic_string<WCHAR> wOutput = __char_to_WCHAR(output.c_str());
   processOutput = __WCHAR_to_utf8(wOutput.c_str());

   return true;
}
问题原因

当明确指定lpApplicationName为cmd.exe时,lpCommandLine参数会直接作为cmd.exe的启动参数。此时如果仅传入dir "c:/Program Files",cmd.exe会进入交互模式(而非执行命令后退出),它会等待用户输入更多指令,导致进程一直存活,管道的读端hRead会因为没有更多数据且写端虽关闭但进程未退出而阻塞在ReadFile调用上。

而当lpApplicationName设为NULL时,系统会自动解析命令行字符串,将开头的cmd识别为程序,后续的/c ...识别为参数,此时cmd.exe会执行指定命令后立即退出,管道能正常读取所有数据。

解决方案

需要手动构造cmd.exe的正确参数:给用户传入的命令添加/c前缀,并包裹引号(处理命令中含空格的情况),让cmd.exe执行完命令后自动退出。

1. 基础修改

替换代码中构造wCmd的部分:

// 替换原来的wCmd构造代码
std::wstring rawCmd = __utf8_to_WCHAR(cmd.c_str());
// 构造cmd.exe的参数:/c "用户命令"
std::wstring wCmd = L"/c \"";
wCmd += rawCmd;
wCmd += L"\"";

CreateProcessW调用保持不变:

if (!CreateProcessW(cmdExe.c_str(), const_cast<LPWSTR>(wCmd.c_str()), NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi))

2. 处理命令中的引号

如果用户传入的命令本身包含双引号,需要对内部引号转义(将"替换为\"),否则会导致命令解析错误。添加辅助函数:

std::wstring escapeQuotes(const std::wstring& input) {
    std::wstring result;
    for (wchar_t c : input) {
        if (c == L'"') {
            result += L'\\"';
        } else {
            result += c;
        }
    }
    return result;
}

修改构造wCmd的代码:

std::wstring rawCmd = __utf8_to_WCHAR(cmd.c_str());
std::wstring escapedCmd = escapeQuotes(rawCmd);
std::wstring wCmd = L"/c \"";
wCmd += escapedCmd;
wCmd += L"\"";
验证效果

修改后,传入"dir \"c:/Program Files\""会被转换成/c "dir \"c:/Program Files\"",cmd.exe会正确执行该命令并退出,ReadFile能读取完整输出数据,不会出现阻塞。

内容的提问来源于stack exchange,提问作者rpatters1

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 18:36:25