如何在C++中与StockFish交互并捕获其输出?
解决C++与StockFish双向交互并捕获输出的问题
原代码的关键错误
- 路径中的
> nul将StockFish的标准输出重定向到空设备,导致完全无法获取输出。 - 使用
popen的"w"模式仅支持写入子进程的标准输入,无法读取输出;而"r+"模式在Windows平台的_popen中不支持双向管道,因此启动进程失败。
简洁解决方案(Windows平台)
使用Windows API的CreateProcess配合管道实现双向通信,代码简洁且易理解:
#include <windows.h> #include <iostream> #include <string> #include <unordered_map> #include <sstream> #include <algorithm> #include <cctype> std::unordered_map<std::string, int> stockFishPerft(std::string fenString, int depth) { std::unordered_map<std::string, int> moveCounts; // 配置管道安全属性,允许句柄继承 SECURITY_ATTRIBUTES sa{}; sa.nLength = sizeof(SECURITY_ATTRIBUTES); sa.bInheritHandle = TRUE; HANDLE hStdinRead, hStdinWrite; HANDLE hStdoutRead, hStdoutWrite; // 创建输入、输出管道 if (!CreatePipe(&hStdinRead, &hStdinWrite, &sa, 0) || !CreatePipe(&hStdoutRead, &hStdoutWrite, &sa, 0)) { std::cerr << "Error: Failed to create communication pipes." << std::endl; return moveCounts; } // 设置进程启动信息,重定向标准输入输出 STARTUPINFO si{}; si.cb = sizeof(STARTUPINFO); si.hStdInput = hStdinRead; si.hStdOutput = hStdoutWrite; si.hStdError = hStdoutWrite; // 将错误输出合并到标准输出管道 si.dwFlags |= STARTF_USESTDHANDLES; PROCESS_INFORMATION pi{}; const std::string stockfishPath = R"(C:\Users\1flor\CLionProjects\Chess\src\utils\stockfish\stockfish-windows-x86-64-avx2.exe)"; // 启动StockFish进程 if (!CreateProcess(nullptr, const_cast<char*>(stockfishPath.c_str()), nullptr, nullptr, TRUE, 0, nullptr, nullptr, &si, &pi)) { std::cerr << "Error: Failed to start Stockfish. Error code: " << GetLastError() << std::endl; // 清理管道句柄 CloseHandle(hStdinRead); CloseHandle(hStdinWrite); CloseHandle(hStdoutRead); CloseHandle(hStdoutWrite); return moveCounts; } // 关闭父进程不需要的管道句柄(子进程已继承) CloseHandle(hStdinRead); CloseHandle(hStdoutWrite); // 封装命令发送函数 auto sendCmd = [&](const std::string& cmd) { DWORD bytesWritten; const std::string cmdWithNewLine = cmd + "\n"; WriteFile(hStdinWrite, cmdWithNewLine.c_str(), cmdWithNewLine.size(), &bytesWritten, nullptr); FlushFileBuffers(hStdinWrite); }; // 发送StockFish命令 sendCmd("position fen " + fenString); sendCmd("go perft " + std::to_string(depth)); sendCmd("quit"); // 发送退出命令,避免进程挂起 // 读取并输出StockFish的响应 char buffer[4096]; DWORD bytesRead; std::string fullOutput; while (ReadFile(hStdoutRead, buffer, sizeof(buffer)-1, &bytesRead, nullptr) && bytesRead > 0) { buffer[bytesRead] = '\0'; fullOutput += buffer; std::cout << buffer; } // 解析perft结果到map中 std::istringstream outputStream(fullOutput); std::string line; while (std::getline(outputStream, line)) { const size_t colonPos = line.find(':'); if (colonPos == std::string::npos) continue; // 提取走法并去除前后空格 std::string move = line.substr(0, colonPos); move.erase(move.begin(), std::find_if(move.begin(), move.end(), [](unsigned char c) { return !std::isspace(c); })); move.erase(std::find_if(move.rbegin(), move.rend(), [](unsigned char c) { return !std::isspace(c); }).base(), move.end()); // 提取数量并转换为整数 try { const int count = std::stoi(line.substr(colonPos + 1)); moveCounts[move] = count; } catch (...) { continue; // 解析失败跳过该行 } } // 等待进程结束并清理资源 WaitForSingleObject(pi.hProcess, INFINITE); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); CloseHandle(hStdinWrite); CloseHandle(hStdoutRead); return moveCounts; }
代码关键点说明
- 管道双向通信:创建两个独立管道分别处理StockFish的输入和输出,实现命令发送与结果读取的双向交互。
- 移除输出重定向:删除原路径中的
> nul,确保StockFish的输出能被捕获。 - 主动退出命令:发送
quit命令让StockFish执行完perft后主动退出,避免读取输出时无限等待。 - 结果解析:针对StockFish perft的输出格式(
走法: 数量),提取走法和对应计数存入unordered_map。
注意事项
- 该代码仅适用于Windows平台,依赖Windows API。
- 在CLion等IDE中编译时,需确保链接
kernel32.lib(通常IDE会自动处理,若报错可在CMakeLists.txt中添加target_link_libraries(你的项目名 kernel32))。
内容的提问来源于stack exchange,提问作者Popo
相关产品推荐
相关产品推荐

