这套工具做每一件实事都在启动子进程:调 g++ 编译、运行生成器、调 PowerShell 打包。子进程怎么接、输出往哪去,是 7.1.0 里最容易踩坑的一块。
编译输出:管道逐块读回
要让界面看到 g++ 的原始报错,就把子进程的 stdout 与 stderr 一起接到管道上:
SECURITY_ATTRIBUTES sa = { sizeof(sa), nullptr, TRUE }; // 句柄必须可继承
CreatePipe(&hRead, &hWrite, &sa, 0);
STARTUPINFOW si = {};
si.cb = sizeof(si);
si.dwFlags = STARTF_USESTDHANDLES;
si.hStdOutput = hWrite;
si.hStdError = hWrite; // 报错和普通输出合到一处
CreateProcessW(nullptr, &cmd[0], nullptr, nullptr, TRUE,
CREATE_NO_WINDOW, nullptr, dir, &si, &pi);
char buf[4096]; DWORD n; string s;
while (ReadFile(hRead, buf, sizeof(buf) - 1, &n, nullptr) && n)
s.append(buf, n); // 逐块收,界面就能看到完整报错运行生成器:重定向落在句柄上
这里有一个反直觉的点:< in > out 是命令解释器的语法,直接交给 CreateProcess 是无效的 —— 那两个符号会被当成普通参数原样传给子进程。想真正重定向,只能自己打开文件句柄再塞进启动信息:
HANDLE hIn = CreateFileW(inPath.c_str(), GENERIC_READ, FILE_SHARE_READ,
&sa, OPEN_EXISTING, 0, nullptr);
HANDLE hOut = CreateFileW(outFile.c_str(), GENERIC_WRITE, FILE_SHARE_READ,
&sa, CREATE_ALWAYS, 0, nullptr);
STARTUPINFOW si = {};
si.cb = sizeof(si);
si.dwFlags = STARTF_USESTDHANDLES;
si.hStdInput = hIn ? hIn : GetStdHandle(STD_INPUT_HANDLE);
si.hStdOutput = hOut;
si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
CreateProcessW(exePath.c_str(), &cmd[0], nullptr, nullptr, TRUE,
CREATE_NO_WINDOW, nullptr, dir, &si, &pi);正因为重定向是句柄级的,「生成器只管往标准输出写」这条约定才成立:写出去的每个字节都会被存成对应编号的文件,生成器自己不需要知道任何路径。
- 生成器模式:
generator.exe <编号>,标准输入接NUL(scanf会干净地读到 EOF 而不是挂住),标准输出存成<编号>.in - 自编译模式:
AC.exe < 3.in > 3.out,输入是上一个模式产出的.in,输出就是答案文件
工作目录就是 DLL 搜索路径
g++ 自己还要再拉起 cc1plus、as、ld 几个子程序,它们需要找到 MinGW 目录下的 DLL。Windows 的 DLL 搜索顺序里「当前目录」排在 PATH 之前,所以不必去改环境变量,只要把子进程的工作目录设成 mingw64\bin 即可:
static wstring MingwBinDir() {
if (g_exeDir.empty()) return {};
wstring mingwBin = Utf8ToWstr(g_exeDir) + L"\\mingw64\\bin";
DWORD attr = GetFileAttributesW(mingwBin.c_str());
if (attr == INVALID_FILE_ATTRIBUTES || !(attr & FILE_ATTRIBUTE_DIRECTORY))
return {}; // 便携包没带 mingw64,交给系统 PATH
return mingwBin;
}两个踩过的坑
- 绝不传自定义环境块 —— 实测自构环境块会让
CreateProcessW稳定报err=87(参数无效),所有编译直接失败。让它继承父进程环境即可。 - 静默失败要重试 —— 退出码非 0、零输出、又没产物,多半是安全软件对刚生成的新 exe 做了瞬时锁。程序会等 600 毫秒清掉残留重试一次,仍失败才如实报错,并在日志里点明「可能是安全软件拦截」。