ReadFileEx, переменная длина — несколько вопросов

Я пытаюсь прочитать из stderr дочернего процесса. Данные представляют собой строки текста, созданные с помощью sprintf(stderr, "some debug info\n"). Я использую ReadFileEx с подпрограммой завершения. Я не знаю, сколько строк текста или какой длины будет каждая строка. Итак, что мне указать в качестве параметра nNumberOfBytesToRead?

Я предполагаю, что я установил максимальный размер моего буфера, который я сделаю 4k; хотя я не знаю, является ли это оптимальным размером. Я предполагаю, что если строка, записанная в stderr, короче 4 КБ, процедура завершения не сработает. Я предполагаю, что когда 4 КБ достигнуто, но остается больше данных, мне нужно запустить еще один ReadFileEx в рамках процедуры завершения. Я буду знать, что это так, потому что GetLastError вернет ERROR_MORE_DATA. Я надеюсь, что мне позвонят, когда буфер не заполнен, но дочерний процесс завершился. Я не уверен, что получаю обратный вызов завершения, когда дочерний процесс выходит, потому что я передал дескриптор записи stderr дочернему процессу, когда я его создал; возможно, я получу обратный вызов, когда я закрою этот дескриптор. Есть ли какие-либо условия гонки, когда ребенок закрывает wrt от моего чтения stderr?

Вот псевдокод создания процесса и дескрипторов:

Attr.bInheritHandle = true
CreatePipe(&hr, &hw, &Attr, 0) and SetHandleInformation(hX, HANDLE_FLAG_INHERIT) on hX the child uses.
Si.hStdXXX = handles from CreatePipe that child uses
CreateProcess(inherit=true, &Si)

Подробности (расширение Tx — это оболочка, которая выдает ошибки):

HANDLE Create() {
    STARTUPINFO SI, *pSI = NULL;
    bool fInherit = m_fInherit;
    if (m_fStdOut || m_fStdIn || m_fStdErr) {
        fInherit = true;
        SECURITY_ATTRIBUTES Attr;
        Attr.nLength = sizeof(SECURITY_ATTRIBUTES); 
        Attr.bInheritHandle = TRUE; 
        Attr.lpSecurityDescriptor = NULL;
        if (m_fStdOut) // Create a pipe for the child process's STDOUT. The child will use the write.
            CHandle::CreatePipe(m_hStdOutR, m_hStdOutW, &Attr, CP_INHERIT_WRITE);
        if (m_fStdErr) // Create a pipe for the child process's STDERR. The child will use the write.
            CHandle::CreatePipe(m_hStdErrR, m_hStdErrW, &Attr, CP_INHERIT_WRITE);
        if (m_fStdIn) // Create a pipe for the child process's STDIN. The child will use the read.
            CHandle::CreatePipe(m_hStdInR, m_hStdInW, &Attr, CP_INHERIT_READ);
        // Set up members of the STARTUPINFO structure. 
        // This structure specifies the STDIN and STDOUT handles for redirection.
        ZeroStruct(SI);
        SI.cb = sizeof(STARTUPINFO); 
        SI.hStdError = m_hStdErrW, SI.hStdOutput = m_hStdOutW, SI.hStdInput = m_hStdInR;
        SI.dwFlags |= STARTF_USESTDHANDLES;
        pSI = &SI;
    }
    //  m_fCpu, m_fNuma are masks to set affinity to cpus or numas
    CreateProcessTx(NULL, m_szCmdLine, fInherit, m_fFlags, pSI, &m_pi, m_fCpu, m_fNuma, 5);
    m_hProc = m_pi.hProcess;
    m_hThread = m_pi.hThread;
    if (!m_fThread)
        m_hThread.Close();
    return m_hProc;
}

static void CreatePipe(CHandle &hRead, CHandle &hWrite, SECURITY_ATTRIBUTES* pAttr, BYTE fInheritMask) {
    HANDLE hReadTmp = NULL, hWriteTmp = NULL;
    CreatePipeTx(hReadTmp, hWriteTmp, pAttr);
    SetHandleInformation(hReadTmp, HANDLE_FLAG_INHERIT, (fInheritMask&CP_INHERIT_READ) ? HANDLE_FLAG_INHERIT : 0); 
    SetHandleInformation(hWriteTmp, HANDLE_FLAG_INHERIT, (fInheritMask&CP_INHERIT_WRITE) ? HANDLE_FLAG_INHERIT : 0);
    hRead = hReadTmp;
    hWrite = hWriteTmp;
}

person johnnycrash    schedule 23.07.2014    source источник
comment
Просто для ясности: вы используете дескрипторы, созданные с помощью CreatePipe?   -  person Ross Ridge    schedule 23.07.2014
comment
@RR хорошее замечание. Я отредактировал сообщение, добавив некоторые части кода, которые создают каналы и процессы.   -  person johnnycrash    schedule 23.07.2014


Ответы (1)


Анонимные каналы, созданные с помощью CreatePipe, не могут использоваться асинхронно. Из документации Windows SDK:

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

В основном CreatePipe не принимает флаг FILE_FLAG_OVERLAPPED, а асинхронный ввод-вывод требует, чтобы вы использовали этот флаг при создании дескриптора файла.

Вам придется использовать CreateNamedPipe для создания именованных каналов. На вопрос Перекрывающийся ввод-вывод на анонимном канале есть ответ со ссылкой на функцию замены MyCreatePipeEx вы можете использовать.

Ваш порт завершения должен получить событие чтения нулевой длины после попытки чтения из канала, который был закрыт на другом конце.

Чтобы прочитать переменный объем данных из клиентского процесса, просто отправьте запросы на чтение любого размера, который вы считаете удобным, и будьте готовы обрабатывать события чтения, которые короче, чем вы запрашивали. Не интерпретируйте короткую, но ненулевую длину как EOF. Продолжайте выдавать запросы на чтение, пока не получите чтение нулевой длины или ошибку.

Также WaitForMultipleObjects не будет работать с подпрограммами завершения, поскольку они вызываются только тогда, когда поток находится в изменяемое состояние. Используйте WaitForMultipleObjectEx с аргументом bAlertable, для которого установлено значение true. Эта функция вернет WAIT_IO_COMPLETION после запуска одной или нескольких подпрограмм завершения. В этом случае вы, вероятно, захотите снова вызвать WaitForMultipleObjectEx.

person Ross Ridge    schedule 23.07.2014
comment
Я только что пришел к такому выводу, поскольку мой вызов ReadFileEx блокировался. Я думал, что меня облажали, пока вы не сказали попробовать CreateNamedPipe, что я сейчас и делаю. Знаете ли вы что-нибудь о чтении переменной длины? - person johnnycrash; 23.07.2014
comment
Хм ... пропустил эту часть вашего вопроса, я обновил свой ответ, чтобы решить эту проблему. - person Ross Ridge; 23.07.2014
comment
@RR мне снова отправить запрос на чтение с помощью функции завершения? Я думаю, что должен, так как мой WaitForMultipleObjects ожидает нескольких дочерних процессов, а не всех операций чтения из stderr/stdout. - person johnnycrash; 23.07.2014
comment
Ага, снова вызовите ReadWriteEx в процедуре завершения. - person Ross Ridge; 23.07.2014
comment
ReadFileEx больше не блокируется. Однако я не получаю и обратные вызовы. sprintf(stderr...) в дочернем не блокируется. WriteFile(GetStdHandle(STD_OUTPUT_HANDLE)..) блокирует дочерний процесс. Это не блокируется, если я делаю неперекрывающийся ReadFile. Кажется, что мое перекрывающееся чтение дескрипторов не работает, но дескрипторы допустимы для записи и могут быть прочитаны с неперекрывающимся ReadFile. - person johnnycrash; 24.07.2014
comment
Вы должны использовать WaitForMultipleObjectEx с аргументом bAlertable, установленным в true. В противном случае подпрограммы завершения не будут вызываться. - person Ross Ridge; 24.07.2014
comment
бОсторожно! Сейчас что-то происходит! Позвольте мне немного очистить пыль здесь и посмотреть, работает ли это ... - person johnnycrash; 24.07.2014
comment
Добавьте немного о WaitForMultipleObjectsEX в свой ответ, чтобы принести пользу всем, кто его читает, и плохо приму. Большое спасибо! Я ждал на проце ручками. Интересно, позволяет ли этот flag = true окнам разбудить ваш поток для обработки обратного вызова, а затем он снова переводит ваш поток в спящий режим. - person johnnycrash; 24.07.2014