2017-01-12 33 views
0

我正在使用Microsoft示例如何使用句柄向父进程传递调用的进程中的数据。ReadFromFile C++具有未知字节读取大小

这里是我的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

你是怎么打开'g_hChildStd_OUT_Rd'句柄的?您可以调查“OVERLAPPED”模式和“PIPE_NOWAIT”以使用异步方法。 – Gonmator

+1

不,这个问题不太可能是由读取太多字节引起的。 (那么,除非缓冲区不够大?)但是我认为休止条件是错误的;零字节读取不是“管道末端”状况。我最好的猜测是你没有把你的手柄关在管子的末端。但我们需要[mcve]来确保。 –

回答

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()); 
} 

,你需要调用CreateProcess之后调用CloseHandle(g_hChildStd_OUT_Wr);。这确保管道写入结束的唯一句柄是子进程继承的句柄;当孩子离开时,手柄将关闭,管道将破裂。 (这是一个匿名管道安全;备案,命名管道规则是更复杂一点。)


现在,让我们来看看在您等待子进程中检索之前退出的情况下输出。在这种情况下,您不需要单独的线程,除非需要在等待孩子退出时执行其他操作。上面显示的代码仅在孩子出现时才会退出[请参阅脚注],以便您可以同时执行等待并检索数据。

如果您正在等待孩子在执行其他任务的同时退出,则可以假定所有输出都适合管道缓冲区,使用单独的线程检索输出或使用异步I/O。根据现有代码的结构,异步I/O可能是一个很好的选择,但单独的线程通常更容易。

由于没有关于管道缓冲区大小的保证,假设输出适合可能会导致死锁,并等待父节点从管道读取并等待子节点退出。

[脚注:从技术上讲,如果孩子关闭了管柄,循环也会退出,尽管这是不寻常的。在这种情况下,孩子将无法再写入更多的数据,所以你会知道你已经得到了所有的输出。但是,如果你一定要确保孩子实际上已经退出,你要单独检查的是,后的管坏了。]


我所知,没有简单的方法来处理的情况下你想要定期检查可用的输入。在原则上,你应该能够使用PeekNamedPipe,看看有多少数据是可用的,but it seems that this does not work as expected.

如果你可以控制孩子的输出,零长度写入将导致ReadFile父退出零个字节读,或者您可以将输出填充到固定长度。这仍然需要家长知道什么时候期待孩子的输入,所以这不是一个好的一般解决方案。另外,如果子进程产生任何意外的输出,例如来自C运行时库,它可能会中断。

我可以看到的唯一其他明智的选择是使用单独的线程或异步I/O,如前所述。

+0

谢谢我试过它,出于某种原因,即使我在等待过程完成后调用了读取管道函数,dwRead也会出现0。在我的子进程中,我发送数据的方式是通过fprintf(stdout,“%s”,temp.c_str());这是否是正确的方法? – mocode9

+0

对不起,我不清楚我有两个版本的项目。一个人永远不会终止这个孩子,并且在继续之前等待缓冲区被填充,我的解决方案是在子进程中用0填充缓冲区。我有另一个版本,其中的孩子终止然后readfrompipe函数被调用。在该版本中,没有数据从缓冲区中提取/读取 – mocode9

+0

我已经重写了我的答案,以解决PeekNamedPipe中的问题以及您对该方案的说明。 –