Skip to content

Instantly share code, notes, and snippets.

@Affonso-Gui
Created March 31, 2024 05:56
Show Gist options
  • Save Affonso-Gui/6c9672c59bfb79d394e55d26f2f05726 to your computer and use it in GitHub Desktop.
Save Affonso-Gui/6c9672c59bfb79d394e55d26f2f05726 to your computer and use it in GitHub Desktop.
Creating subprocesses with stdout/stderr redirection using the Windows API (`CreateProcess`)
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