#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 эквивалентом NTAPISTATUS_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;