主要原因是创建pipe时,将写句柄继承给了子进程,因此在父进程中即使关掉写句柄,那么自己成还是有该句柄的所有权,因此并没有关闭。所以在子进程中从 stdin 读数据,会造成阻塞。
在父进程中,创建pipe时或创建后,设置子进程不可继承写句柄,即可解决该问题。
int main(int arg, char* argv[])
{
int ret = 0;
SECURITY_ATTRIBUTES saAttr;
HANDLE hChildStd_IN_Rd = NULL;
HANDLE hChildStd_IN_Wr = NULL;
PROCESS_INFORMATION piProcInfo;
STARTUPINFO siStartInfo;
BOOL bSuccess = FALSE;
DWORD bytesWritten;
// 传送给外部 exe 的参数
char* cmd = NULL;
// 使用 windows API 是以宽字符传参, 故需要进行格式转换
wchar_t* cmdWide = NULL;
// argv[1]: 源文件路径; argv[2]: 保存路径
if (arg < 3)
{
fprintf(stderr, "Invalid args\r\n");
goto err;
}
// 为 cmd 分配内存, 视具体参数而定
cmd = calloc(256, sizeof(char));
if (cmd == NULL)
{
ret = -1;
goto err;
}
// windows.h 中的 API, 均已 宽字符 传参, 因此需要转换为宽字符
sprintf(cmd, "--stable --hash -s %s -d %s", argv[1], argv[2]);
int len = MultiByteToWideChar(CP_ACP, 0, cmd, -1, NULL, 0);
cmdWide = calloc(len, sizeof(wchar_t));
if (cmdWide == NULL)
{
ret = -1;
goto err;
}
MultiByteToWideChar(CP_ACP, 0, cmd, -1, cmdWide, len);
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
if (!CreatePipe(&hChildStd_IN_Rd, &hChildStd_IN_Wr, &saAttr, 0))
{
printf("CreatePipe for STDIN failed\n");
goto err;
}
// 注意要将 hChildStd_IN_Wr 设置为子进程不可继承
// 否则在附近中即使关闭了句柄, 但子进程还拥有其所有权, 因此子进程中使用 fread 等读取数据会阻塞
SetHandleInformation(hChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0);
SetHandleInformation(hChildStd_IN_Rd, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));
ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
siStartInfo.cb = sizeof(STARTUPINFO);
siStartInfo.hStdInput = hChildStd_IN_Rd;
siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
bSuccess = CreateProcess(
TEXT(".\\third_tool.exe"),
cmdWide,
NULL,
NULL,
TRUE,
0,
NULL,
NULL,
&siStartInfo,
&piProcInfo
);
if (!bSuccess)
{
printf("CreateProcess failed\n");
goto err;
}
// 释放子进程 读句柄, 父进程中并不会用到此
CloseHandle(hChildStd_IN_Rd);
if (!WriteFile(hChildStd_IN_Wr, cmd, strlen(cmd), &bytesWritten, NULL))
{
printf("WriteFile failed, error: %d\n", GetLastError());
goto err;
}
if (!FlushFileBuffers(hChildStd_IN_Wr)) {
goto err;
}
// 关闭写句柄时, 子进程中会读到 EOF, 读数据终止, 故一定要在写入完成后, 关闭写句柄
CloseHandle(hChildStd_IN_Wr);
hChildStd_IN_Wr = NULL;
// 等待子进程结束
WaitForSingleObject(piProcInfo.hProcess, INFINITE);
goto end;
err:
ret = (ret != 0) ? ret : -1;
end:
// 释放内存等
if (cmd)
{
free(cmd);
}
if (cmdWide)
{
free(cmdWide);
}
if (hChildStd_IN_Wr) CloseHandle(hChildStd_IN_Wr);
if (piProcInfo.hProcess)
{
CloseHandle(piProcInfo.hProcess);
}
CloseHandle(piProcInfo.hThread);
return 0;
}