Перекрытый ReadFileEx для дочернего процесса 'Перенаправленный STDOUT никогда не срабатывает - PullRequest
4 голосов
/ 07 сентября 2010

У меня есть долго работающее консольное приложение Sender, которое отправляет простой текст в STDOUT, используя небуферизованный вывод, такой как cout << "Message" << flush ().Я хочу создать диалоговое приложение MFC (с именем Receiver), которое запускает Sender и может читать его вывод.Получатель также должен иметь возможность определять, когда отправитель умер, или иметь возможность убивать отправителя, если он этого хочет.Отправитель ничего не знает о Получателе, и я не могу изменить код Отправителя. </p>

Я задал отдельный вопрос о наилучшем способе сделать это.Моей первой попыткой было создание каналов с перенаправленными STDIN и STDOUT для дочернего процесса и использование асинхронных вызовов ReadFileEx для чтения данных отправителя.Это не работает правильно, потому что функция ReadFileEx запускается только один раз, и только с нулевыми байтами, даже если я знаю, что Sender отправляет данные.

Я создаю 2 канала с перенаправленным STDINи STDOUT, ala этот пример MS :

// 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, затем запускается Sender через CreateProcess ():

// 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 умер.Перед началом цикла я запускаю перекрывающуюся (асинхронную) операцию чтения файла на STDOUT Sender.Не обращайте внимания на очевидные утечки памяти и другие хаки - это иллюстративно:

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_;
};

Когда перекрывающееся чтениеФункция завершается, я считываю входящие данные и генерирую строку:

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

Sender ничего не знает о Receiver, и отправляет текст на консоль, используя очень простые, но не буферизованные средства.

Проблема: я знаю, что Sender отправляет данные на свой STDOUT, но моя OnFileRead функция вызывается только один раз и только с нулевыми переданными байтами.

Почему я не могуполучить вывод Sender таким образом?У меня ошибка или я что-то не так делаю?

Ответы [ 2 ]

9 голосов
/ 09 октября 2012

Помимо ошибки, указанной @DyP, вы предполагаете, что CreatePipe открыл дескриптор в режиме перекрытия. Ваше предположение неверно . Microsoft документирует это :

Асинхронные (перекрывающиеся) операции чтения и записи не поддерживаются по анонимным каналам. Это означает, что вы не можете использовать ReadFileEx и WriteFileEx работает с анонимными каналами. В дополнение Параметр lpOverlapped для ReadFile и WriteFile игнорируется, когда эти функции используются с анонимными каналами.

(Действительно, если вы заглянете внутрь kernel32.dll, например, в Windows XP, CreatePipe не устанавливает младший бит седьмого параметра в NtCreateNamedPipeFile; этот бит равен , установленному при CreateNamedPipe вызывается с FILE_FLAG_OVERLAPPED.)

Ищите реализацию Дейва Харта MyCreatePipeEx; он может использоваться в качестве замены для CreatePipe, когда требуется перекрывающийся ввод-вывод. Просто измените PipeSerialNumber++ на InterlockedIncrement(&PipeSerialNumber), чтобы избежать условий гонки в коде MT.

2 голосов
/ 07 сентября 2010

Я думаю, что у вас есть опечатка:

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.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...