CreateProcess с 2 перенаправленными каналами возвращает код выхода 1

#c #windows #winapi #createprocess

#c #Windows #winapi #createprocess

Вопрос:

Я новичок в WIN32 API и мог бы воспользоваться некоторой помощью, чтобы понять, чего мне не хватает в моем коде. Моя цель — запустить пакетный скрипт (или сценарий PowerShell), дождаться завершения, а затем получить стандартный вывод / stderr по завершении.

После этого и этой статьи в msdn о создании процесса и перенаправлении stdout и stderr в дескриптор. Я собрал образцы вместе и придумал следующий код:

 int execute_commnad(std::stringamp; command,std::stringamp; output,intamp; timeout_seconds) {
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    DWORD return_code = NULL;

    ZeroMemory(amp;pi, sizeof(pi));

    ZeroMemory(amp;si,sizeof(si));
    si.cb = sizeof(si);
    si.hStdOutput = child_stdout_handle;
    si.hStdError = child_stdout_handle;
    si.dwFlags |= STARTF_USESTDHANDLES;

    SECURITY_ATTRIBUTES saAttr;
    saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
    saAttr.bInheritHandle = TRUE;
    saAttr.lpSecurityDescriptor = NULL;

    // Connect Child stdout to parent_stdout_handle
    if (!CreatePipe(amp;parent_stdout_handle,amp;child_stdout_handle,amp;saAttr,0))
    {
        output = "Could not create stdout pipe. Error: "   std::to_string(GetLastError());
        close_handles();
        return -1;
    }

    if (! SetHandleInformation(parent_stdout_handle,HANDLE_FLAG_INHERIT,0))
    {
        output = "Could not set handle information for stdout pipe. Error: "   std::to_string(GetLastError());
        close_handles();
        return -1;
    }

    // Create Process
    if (!CreateProcess(NULL,LPSTR(command.c_str()),NULL,NULL,TRUE,0,NULL,NULL,amp;si,amp;pi)) {
        output = "Failed to create process. Error: "   std::to_string(GetLastError());
        close_handles();
        return -1;
    }
    CloseHandle(child_stdout_handle);


    // Wait for process to finish or timeout
    if (timeout_seconds == NULL) {
        WaitForSingleObject(pi.hProcess,INFINITE);
    }
    else {
        WaitForSingleObject(pi.hProcess,DWORD(timeout_seconds * 1000));
    }
    
    if (!GetExitCodeProcess(pi.hProcess,amp;return_code)) {
        output = "Failed to fetch exit code";
        close_handles();
        return -1;
    }
    
    if (STILL_ACTIVE == return_code) {
        TerminateProcess(sub_process,return_code);
        output = "Command did not finish within defined timeout threshold";
    }
    else if (STATUS_PENDING == return_code) {
        output = "process is pending";
    }
    else {
        DWORD dwRead;
        CHAR chBuf[4096];
        BOOL bSuccess = FALSE;

        for (;;) {
            bSuccess = ReadFile(parent_stdout_handle,chBuf,4096,amp;dwRead,NULL);
            if (! bSuccess || dwRead == 0) break;
            output = *reinterpret_cast<std::string*>(parent_stdout_handle);
        }
    }
    
    close_handles();
    return 0;    
};


void close_handles(void) {
    CloseHandle(parent_stdout_handle);
    CloseHandle(child_stdout_handle);
    CloseHandle(sub_process);
};
  

Перед выполнением описанной выше функции я создаю файл .bat, используя std::ofstream содержащий «echo hello» и сохраняю полное имя файла в переменной с именем file_name и запускаю следующий код:

 int rc = 0;
std::string cmd = "cmd.exe /c "   file_name   " "   script_params;
rc = execute_commnad(cmd,this->output,timeout_in_seconds);
  

При отладке это значения, которые я получаю после GetExitCodeProcess :

  • cmd: cmd.exe / с «O:\Programming\cpp…\script_file.bat » 1 2 3
  • return_code: 1

Я попытался заменить cmd.exe на FQN, но с тем же результатом, попытался запустить что-то столь же простое, как cmd.exe /c "echo hello" и return_code 1, и все равно вернул return_code 1.

Последние 2 дня я пытался найти причину этого, но безрезультатно.

Помимо неэффективного кода (с точки зрения производительности), у кого-нибудь есть предложения, в чем может быть проблема?

Заранее спасибо!

Комментарии:

1. Вы хотите получить вывод дочернего процесса вместо кода возврата? Затем вы должны напечатать значение chBuf , которое вы прочитали ReadFile , и вам нужно установить si.hStdOutput/hStdError после вызова CreatePipe , в противном случае значение дескриптора недопустимо.

2. К вашему сведению, STILL_ACTIVE является ли WinAPI эквивалентом NTAPI STATUS_PENDING в контексте состояния завершения процесса. В minwinbase.h это просто #define STILL_ACTIVE STATUS_PENDING . Поэтому проверка обоих бессмысленна.

3. Кажется, вы должны добавлять содержимое chBuf в output строку на каждом проходе после успешного ReadFile вызова. Я не могу понять, почему вы делаете следующее : output = *reinterpret_cast<std::string*>(parent_stdout_handle) .

4. Похоже, вы на самом деле не устанавливаете глобальное sub_process значение pi.hProcess . Таким образом, вы pi.hProcess также пропускаете дескриптор pi.hThread , и TerminateProcess вызывается для некоторого случайного значения дескриптора. Использование глобальных переменных здесь сомнительно. Вам было бы лучше без них.

5. Вы можете упростить свой код, если удалите явное использование SECURITY_ATTRIBUTES . Передайте атрибуты безопасности как NULL , которые по умолчанию создают не наследуемые дескрипторы, а затем вручную сделайте дочерний дескриптор наследуемым через SetHandleInformation , а не наоборот. Кроме того, нет необходимости обнулять pi , поскольку успешный CreateProcess вызов устанавливает все поля и si может быть обнулен в стеке компилятором через STARTUPINFO si = {sizeof(STARTUPINFO)} .

Ответ №1:

Обязательно инициализируйте child_stdout_handle перед передачей дескриптора CreateProcess .

Кроме того, вам нужно напечатать значение chBuf , которое вы прочитали ReadFile , вместо parent_stdout_handle . Преобразование дескриптора файла в string бессмысленно, вы могли бы попробовать:

     for (;;) {
        bSuccess = ReadFile(parent_stdout_handle, chBuf, 4096, amp;dwRead, NULL);
        if (!bSuccess || dwRead == 0) break;
        chBuf[dwRead] = '';
        output  = chBuf;
    }
    cout << output << endl;