我使用Microsoft示例介绍如何使用句柄向父进程传递数据。
这是我的createChild函数
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
//output file for hash rate
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &sa, 0);
SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0);
si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
si.hStdInput = NULL;
si.hStdError = g_hChildStd_OUT_Wr;
si.hStdOutput = g_hChildStd_OUT_Wr;
si.wShowWindow = SW_HIDE;
int success;
success = CreateProcess((appPath + L"\\" + processName + L".exe").c_str(),
cmdArgs,
NULL,
NULL,
TRUE,
CREATE_NO_WINDOW,
NULL,
appPath.c_str(),
&si,
&pi)
;
//}
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
创建子项后,它永远不会终止。只要PC正在运行,它就会运行并向父节点发送输出。
父母每隔五分钟调用此函数来获取缓冲区。
void Helpers::ReadFromPipe(CHAR* chBuf)
{
DWORD dwRead, dwWritten;
BOOL bSuccess = FALSE;
HANDLE hParentStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
for (;;)
{
bSuccess = ReadFile(g_hChildStd_OUT_Rd, chBuf, 4096, &dwRead, NULL);
if (!bSuccess || dwRead == 0) break;
bSuccess = WriteFile(hParentStdOut, chBuf,
dwRead, &dwWritten, NULL);
if (!bSuccess) break;
}
return;
}
有时它会起作用,有时却不起作用。 当它在for循环中卡住时是因为我设置的字节读得太高了吗?
我的被调用进程写出5行文本,大约100个字节左右。但是每次运行时线条都是不同的,因此我不能100%确定设置字节数来读取数字。
答案 0 :(得分:0)
您似乎期望ReadFile
是非阻止的,但事实并非如此。
最直接的解决方案是在单独的线程中运行循环。这种方法的开销很小,因为每个子进程只需要一个线程,并且子进程使用远比线程更多的系统资源。
其中一个变体是使用异步I / O,通知将发送到主线程。这样既优雅又高效,但可能涉及更多工作,并且在您的情况下不太可能具有任何显着的实际优势。 (我之所以提到它,只是因为如果不这样做,有人会抱怨。)
[完整性:另一种变体是使用非阻塞管道。这是一项传统技术,仅用于向后兼容,Microsoft强烈建议不要在新代码中使用它。与使用单独的线程相比,它没有明显的优势。]
如果要在单独的线程中运行它,现有代码几乎是正确的。循环应该如下所示:
for (;;)
{
bSuccess = ReadFile(g_hChildStd_OUT_Rd, chBuf, 4096, &dwRead, NULL);
if (!bSuccess)
{
DWORD err = GetLastError();
if (err == ERROR_BROKEN_PIPE) break; // All data has been read
fail(err);
}
bSuccess = WriteFile(hParentStdOut, chBuf, dwRead, &dwWritten, NULL);
if (!bSuccess) fail(GetLastError());
}
,您需要在致电CloseHandle(g_hChildStd_OUT_Wr);
后致电CreateProcess
。这确保了管道写端的唯一句柄是子进程继承的句柄;当孩子离开时,手柄将被关闭,管道将会断裂。 (这对于匿名管道是安全的;对于记录,命名管道的规则稍微复杂一些。)
现在,让我们看一下在检索输出之前等待子进程退出的情况。在这种情况下,您不需要单独的线程,除非您在等待子进程退出时需要执行其他操作。上面显示的代码仅在孩子[见脚注]时退出,因此您可以同时执行等待并检索数据。
如果您正在等待孩子在执行其他任务的同时退出,您可以假设所有输出都适合管道缓冲区,使用单独的线程来检索输出,或者使用异步I / O。根据现有代码的结构,异步I / O在这里可能是一个不错的选择,但通常更容易使用单独的线程。
由于无法保证管道缓冲区的大小,因此假设输出适合可能会导致死锁,子进程等待父进程从管道中读取并且父进程等待子进程退出。 / p>
[脚注:从技术上讲,如果孩子关闭管道把手,循环也将退出,尽管这是不寻常的。在这种情况下,孩子将无法再写入任何数据,因此您将知道您已获得所有输出。但是如果您必须确定孩子实际上已经退出,那么您将需要单独检查,在管道已经损坏之后。]
我知道没有直接的方法来处理您想要定期检查可用输入的情况。原则上,您应该能够使用PeekNamedPipe
查看可用的数据量,but it seems that this does not work as expected.
如果可以控制子进程的输出,则零长度写入将导致父进程中的ReadFile
以零字节读取退出,或者您可以将输出填充到固定长度。这仍然需要父母知道何时期望来自孩子的输入,因此这不是一个好的通用解决方案。此外,如果子进程生成任何意外输出(例如,来自C运行时库),它可能会中断。
我能看到的唯一其他敏感选项是使用单独的线程或异步I / O,如前所述。