Created
March 31, 2024 05:56
-
-
Save Affonso-Gui/6c9672c59bfb79d394e55d26f2f05726 to your computer and use it in GitHub Desktop.
Creating subprocesses with stdout/stderr redirection using the Windows API (`CreateProcess`)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
enum class ExecuteCommandError | |
{ | |
ErrorNone, | |
ErrorCreatePipe, | |
ErrorSetHandleInformation, | |
ErrorCreateProcess, | |
}; | |
// ref. https://learn.microsoft.com/en-us/windows/win32/ProcThread/creating-a-child-process-with-redirected-input-and-output | |
// ref. https://github.com/godotengine/godot/pull/56012 | |
ExecuteCommandError ExecuteCommandCreateProcess(const char* command, int* retval, void (*PrintFunction)(const char*)) | |
{ | |
// pipe[0]: read | |
// pipe[1]: write | |
HANDLE pipe[2] = { nullptr, nullptr }; | |
{ | |
// Create pipes | |
SECURITY_ATTRIBUTES sa; | |
sa.nLength = sizeof(sa); | |
sa.bInheritHandle = true; | |
sa.lpSecurityDescriptor = nullptr; | |
if (!CreatePipe(&pipe[0], &pipe[1], &sa, 0) || nullptr == pipe[0] || nullptr == pipe[1]) | |
{ | |
return ExecuteCommandError::ErrorCreatePipe; | |
} | |
if (!SetHandleInformation(pipe[0], HANDLE_FLAG_INHERIT, 0)) | |
{ | |
return ExecuteCommandError::ErrorSetHandleInformation; | |
} | |
} | |
PROCESS_INFORMATION pi; | |
ZeroMemory(&pi, sizeof(pi)); | |
{ | |
STARTUPINFOA si; | |
ZeroMemory(&si, sizeof(si)); | |
si.cb = sizeof(si); | |
si.dwFlags |= STARTF_USESTDHANDLES; | |
si.hStdOutput = pipe[1]; | |
si.hStdError = pipe[1]; | |
// copy the command into a mutable string, which is required | |
// by the CreateProcess function. | |
char cmd[128]; | |
strcpy_s(cmd, command); | |
if (!CreateProcessA(nullptr, | |
cmd, | |
nullptr, | |
nullptr, | |
true, | |
CREATE_NO_WINDOW, | |
nullptr, | |
nullptr, | |
&si, &pi)) | |
{ | |
CloseHandle(pipe[0]); | |
CloseHandle(pipe[1]); | |
return ExecuteCommandError::ErrorCreatePipe; | |
} | |
} | |
CloseHandle(pipe[1]); | |
{ | |
constexpr int size = 4096; | |
char buffer[size]; | |
DWORD readCharacters = 0; | |
while (ReadFile(pipe[0], buffer, size - 1, &readCharacters, nullptr) && 0 != readCharacters) | |
{ | |
buffer[readCharacters] = '\0'; | |
PrintFunction(buffer); | |
} | |
} | |
CloseHandle(pipe[0]); | |
WaitForSingleObject(pi.hProcess, INFINITE); | |
GetExitCodeProcess(pi.hProcess, (DWORD*)retval); | |
CloseHandle(pi.hProcess); | |
CloseHandle(pi.hThread); | |
return ExecuteCommandError::ErrorNone; | |
} | |
int ExecuteCommand(const char* command) | |
{ | |
int retval = -1; | |
std::string start_message = std::format("Starting command: {}\n", command); | |
OutputDebugStringA(start_message.c_str()); | |
if (ExecuteCommandError::ErrorNone != | |
ExecuteCommandCreateProcess(command, &retval, OutputDebugStringA)) | |
{ | |
OutputDebugStringA("Error!"); | |
} | |
else | |
{ | |
std::string end_message = std::format("Command finished with result: {}\n", retval); | |
OutputDebugStringA(end_message.c_str()); | |
} | |
return retval; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment