子进程重叠的ReadFileEx'重定向的STDOUT从不触发

时间:2010-09-07 17:31:45

标签: c++ windows redirect

我有一个长期运行的基于控制台的应用程序Sender,它使用非缓冲输出(例如cout<< “消息”<<齐平()。我想创建一个基于MFC对话框的应用程序(名为Receiver),它启动Sender并可以读取它的输出。接收方还应该能够检测发件人何时死亡,或者能够在发生者希望时杀死发件人。发件人对Reciever一无所知,我也无法改变发件人的代码。

我已经问过separate question最好的方法。我的第一次尝试是为子进程创建带有重定向STDIN和STDOUT的管道,并使用异步ReadFileEx调用来读入Sender的数据。这不能正常工作,因为ReadFileEx函数只触发一次,并且只传输零字节,即使我知道Sender正在发送数据这一事实。

我正在使用重定向的STDIN和STDOUT创建2个管道,ala this MS example

// allow the child process to inherit handles
SECURITY_ATTRIBUTES sa = {0};
sa.nLength = sizeof(sa);
sa.bInheritHandle = 1;

// create pipes with rerouted stdin & stdout
CreatePipe(&handles[h_Child_StdOut_Read], &handles[h_Child_StdOut_Write], &sa, 0);
SetHandleInformation(handles[h_Child_StdOut_Read], HANDLE_FLAG_INHERIT, 0);
CreatePipe(&handles[h_Child_StdIn_Read], &handles[h_Child_StdIn_Write], &sa, 0);
SetHandleInformation(handles[h_Child_StdIn_Read], HANDLE_FLAG_INHERIT, 0);

... Receiver然后继续通过CreateProcess()启动Sender

// create child process
PROCESS_INFORMATION pi = {0};
STARTUPINFO si = {0};
si.cb = sizeof(si);
si.hStdOutput = handles[h_Child_StdOut_Write];
si.hStdInput = handles[h_Child_StdIn_Read];
si.dwFlags |= STARTF_USESTDHANDLES;
CreateProcess( 0, "Sender.EXE", 0, 0, 1, 0, 0, 0, &si, &pi);
handles[h_Child_Process] = pi.hProcess;
handles[h_Child_Thread] = pi.hThread;

我的主循环基于WaitForObjectsEx,置于可警告的等待状态以支持异步文件读取。我正在等待两个句柄:一个在Sender过早死亡时发出信号,另一个在Receiver主线程希望Sender死亡时发出信号。在开始循环之前,我在Sender的STDOUT上启动了重叠(异步)文件读取操作。忽略明显的内存泄漏和其他黑客 - 这是说明性的:

vector<HANDLE> wait_handles;
wait_handles.push_back(handles[h_Die_Sig]);
wait_handles.push_back(handles[h_Child_Process]);

for( bool cont = true; cont; )
{
    IO* io = new IO;
    memset(io, 0, sizeof(IO));
    io->buf_size_ = 16 * 1024;
    io->buf_ = new char[io->buf_size_];
    memset(io->buf_, 0, io->buf_size_);
    io->thread_ = &param;
    io->file_ = handles[h_Child_StdOut_Read];
    if( !ReadFileEx(io->file_, io->buf_, io->buf_size_, io, OnFileRead) )
    {
        DWORD err = GetLastError();
        string err_msg = util::strprintwinerr(err);
    }

    DWORD rc = WaitForMultipleObjectsEx(wait_handles.size(), &wait_handles[0], FALSE, INFINITE, TRUE);

    // ...
}

上面的IO对象是从OVERLAPPED公开派生的:

struct IO : public OVERLAPPED
{
    char* buf_;
    DWORD buf_size_;
    DWORD read_;
    ThreadParam* thread_;
    HANDLE file_;
};

当重叠的Read函数完成时,我读取传入的数据并生成一个字符串:

void CALLBACK OnFileRead(DWORD err, DWORD bytes, OVERLAPPED* ovr)
{
    IO* io = static_cast<IO*>(ovr);
    string msg(io->buf_, bytes);
}

SenderReceiver一无所知,并且使用非常简单但非缓冲的方式将文本发送到控制台。

问题:我知道Sender正在向其STDOUT发送数据,但我的OnFileRead函数只被调用一次,并且只传输零字节。

为什么我不能以这种方式接收Sender的输出?我有错误,或者我做错了什么?

2 个答案:

答案 0 :(得分:9)

除了@DyP指出的错误之外,您假设CreatePipe以重叠模式打开句柄。 您的假设不正确。 Microsoft documents it

  

不支持异步(重叠)读写操作   通过匿名管道。这意味着您无法使用ReadFileEx和   WriteFileEx函数具有匿名管道。除此之外   当这些时,忽略ReadFile和WriteFile的lpOverlapped参数   函数与匿名管道一起使用。

(的确,如果你看一下kernel32.dll,例如在Windows XP上,CreatePipe没有将第七个参数的低位设置为NtCreateNamedPipeFile;那个位在使用CreateNamedPipe调用FILE_FLAG_OVERLAPPED时设置。)

寻找Dave Hart的MyCreatePipeEx 实施;当需要重叠I / O时,它可以用作CreatePipe的替代品。只需将PipeSerialNumber++更改为InterlockedIncrement(&PipeSerialNumber)即可避免MT代码中的竞争条件。

答案 1 :(得分:2)

我认为你有一个错字:

CreatePipe(&handles[h_Child_StdOut_Read], &handles[h_Child_StdOut_Write], &sa, 0);
SetHandleInformation(handles[h_Child_StdOut_Read], HANDLE_FLAG_INHERIT, 0);
CreatePipe(&handles[h_Child_StdIn_Read], &handles[h_Child_StdIn_Write], &sa, 0);
SetHandleInformation(handles[h_Child_StdIn_Read], HANDLE_FLAG_INHERIT, 0);

将最后一个更改为

SetHandleInformation(handles[h_Child_StdIn_Write], HANDLE_FLAG_INHERIT, 0);

这也是他们在MSDN示例中所做的。