#ifndef __NOB_POPEN_H__ #define __NOB_POPEN_H__ #include "nob.h" typedef struct { Nob_String_Builder out; // stdout 内容 int exit_code; // 实际退出码 bool ok; // 是否成功启动 } Nob_Cmd_Result; typedef struct { const char **items; int capacity; int count; } Nob_Files; // 运行命令,捕获 stdout(Windows 下会合并 stderr)和真实退出码。 // 返回 true 仅表示命令成功启动并完成,不代表 exit_code == 0。 bool nob_cmd_capture_popen(const char *tname, Nob_Cmd_Result *res) { memset(res, 0, sizeof(*res)); #ifdef _WIN32 // ---------- Windows: CreateProcessA + 匿名管道 ---------- SECURITY_ATTRIBUTES sa = {0}; sa.nLength = sizeof(sa); sa.bInheritHandle = TRUE; sa.lpSecurityDescriptor = NULL; HANDLE hRead = NULL; HANDLE hWrite = NULL; if (!CreatePipe(&hRead, &hWrite, &sa, 0)) { nob_log(NOB_ERROR, "CreatePipe failed: %s", nob_win32_error_message(GetLastError())); return false; } // 读端不能被继承,否则父进程 ReadFile 永远等不到 EOF if (!SetHandleInformation(hRead, HANDLE_FLAG_INHERIT, 0)) { nob_log(NOB_ERROR, "SetHandleInformation failed: %s", nob_win32_error_message(GetLastError())); CloseHandle(hRead); CloseHandle(hWrite); return false; } STARTUPINFOA si = {0}; si.cb = sizeof(si); si.dwFlags = STARTF_USESTDHANDLES; si.hStdInput = GetStdHandle(STD_INPUT_HANDLE); si.hStdOutput = hWrite; si.hStdError = hWrite; // 把 stderr 也并入 out,需要分离的话另开一条管道 PROCESS_INFORMATION pi = {0}; // CreateProcessA 会就地修改 lpCommandLine,必须传可写缓冲 char *cmdline = NULL; { size_t n = strlen(tname) + 1; cmdline = (char *)malloc(n); if (!cmdline) { CloseHandle(hRead); CloseHandle(hWrite); nob_log(NOB_ERROR, "malloc failed"); return false; } memcpy(cmdline, tname, n); } BOOL ok = CreateProcessA(NULL, // lpApplicationName: 让系统从 lpCommandLine 里解析 cmdline, // lpCommandLine NULL, // lpProcessAttributes NULL, // lpThreadAttributes TRUE, // bInheritHandles: 必须为 TRUE,才能继承管道写端 CREATE_NO_WINDOW, // 不弹黑窗;想弹就传 0 NULL, // lpEnvironment NULL, // lpCurrentDirectory &si, &pi); free(cmdline); // 无论成功与否,父进程都不再需要写端(子进程已持有自己的副本) CloseHandle(hWrite); if (!ok) { nob_log(NOB_ERROR, "CreateProcessA failed for \"%s\": %s", tname, nob_win32_error_message(GetLastError())); CloseHandle(hRead); return false; } // ---------- 读取 stdout ---------- { char buffer[4096]; DWORD n = 0; while (ReadFile(hRead, buffer, sizeof(buffer), &n, NULL) && n > 0) { nob_sb_append_buf(&res->out, buffer, (size_t)n); } } CloseHandle(hRead); // ---------- 等待并取真实退出码 ---------- WaitForSingleObject(pi.hProcess, INFINITE); DWORD code = 0; if (!GetExitCodeProcess(pi.hProcess, &code)) { nob_log(NOB_ERROR, "GetExitCodeProcess failed: %s", nob_win32_error_message(GetLastError())); code = (DWORD)-1; } res->exit_code = (int)code; CloseHandle(pi.hProcess); CloseHandle(pi.hThread); #else // ---------- POSIX: popen ---------- FILE *fp = popen(tname, "r"); if (!fp) { nob_log(NOB_ERROR, "popen failed: %s", strerror(errno)); return false; } char buffer[4096]; while (fgets(buffer, sizeof(buffer), fp)) { nob_sb_append_cstr(&res->out, buffer); } int status = pclose(fp); if (status == -1) { nob_log(NOB_ERROR, "pclose failed: %s", strerror(errno)); return false; } if (WIFEXITED(status)) status = WEXITSTATUS(status); else if (WIFSIGNALED(status)) status = 128 + WTERMSIG(status); res->exit_code = status; #endif res->ok = true; return true; } #define nob_cmd_result_free(res) nob_sb_free((res).out) #endif /* __NOB_POPEN_H__ */