Refactor platform process startup with CreateProcessSettings
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
#include "Engine/Platform/Window.h"
|
||||
#include "Engine/Platform/FileSystem.h"
|
||||
#include "Engine/Platform/CreateWindowSettings.h"
|
||||
#include "Engine/Platform/CreateProcessSettings.h"
|
||||
#include "Engine/Platform/WindowsManager.h"
|
||||
#include "Engine/Platform/MemoryStats.h"
|
||||
#include "Engine/Platform/BatteryInfo.h"
|
||||
@@ -23,6 +24,7 @@
|
||||
#include <Psapi.h>
|
||||
#include <objbase.h>
|
||||
#include <cstdio>
|
||||
#undef ShellExecute
|
||||
#if CRASH_LOG_ENABLE
|
||||
#include <dbghelp.h>
|
||||
#endif
|
||||
@@ -307,7 +309,7 @@ LONG CALLBACK SehExceptionHandler(EXCEPTION_POINTERS* ep)
|
||||
|
||||
// Log exception and return to the crash location when using debugger
|
||||
if (Platform::IsDebuggerPresent())
|
||||
{
|
||||
{
|
||||
LOG_STR(Error, errorMsg);
|
||||
const String stackTrace = Platform::GetStackTrace(0, 60, ep);
|
||||
LOG_STR(Error, stackTrace);
|
||||
@@ -951,54 +953,12 @@ bool WindowsPlatform::SetEnvironmentVariable(const String& name, const String& v
|
||||
return false;
|
||||
}
|
||||
|
||||
int32 WindowsPlatform::StartProcess(const StringView& filename, const StringView& args, const StringView& workingDir, bool hiddenWindow, bool waitForEnd)
|
||||
{
|
||||
// Info
|
||||
LOG(Info, "Command: {0} {1}", filename, args);
|
||||
if (workingDir.HasChars())
|
||||
{
|
||||
LOG(Info, "Working directory: {0}", workingDir);
|
||||
}
|
||||
|
||||
String filenameString(filename);
|
||||
|
||||
SHELLEXECUTEINFOW shExecInfo = { 0 };
|
||||
shExecInfo.cbSize = sizeof(SHELLEXECUTEINFOW);
|
||||
shExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
|
||||
shExecInfo.lpFile = filenameString.GetText();
|
||||
shExecInfo.lpParameters = args.HasChars() ? args.Get() : nullptr;
|
||||
shExecInfo.lpDirectory = workingDir.HasChars() ? workingDir.Get() : nullptr;
|
||||
shExecInfo.nShow = hiddenWindow ? SW_HIDE : SW_SHOW;
|
||||
if (ShellExecuteExW(&shExecInfo) == FALSE)
|
||||
{
|
||||
LOG(Warning, "Cannot start process '{0}' with arguments '{2}'. Error code: {1:x}", filename, (int64)GetLastError(), args);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int32 result = 0;
|
||||
if (waitForEnd)
|
||||
{
|
||||
WaitForSingleObject(shExecInfo.hProcess, INFINITE);
|
||||
DWORD exitCode;
|
||||
if (GetExitCodeProcess(shExecInfo.hProcess, &exitCode) != 0)
|
||||
result = exitCode;
|
||||
CloseHandle(shExecInfo.hProcess);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int32 WindowsPlatform::RunProcess(const StringView& cmdLine, const StringView& workingDir, bool hiddenWindow)
|
||||
{
|
||||
return RunProcess(cmdLine, workingDir, Dictionary<String, String>(), hiddenWindow);
|
||||
}
|
||||
|
||||
bool IsProcRunning(HANDLE handle)
|
||||
{
|
||||
return WaitForSingleObject(handle, 0) == WAIT_TIMEOUT;
|
||||
}
|
||||
|
||||
void ReadPipe(HANDLE pipe, Array<char>& rawData, Array<Char>& logData, LogType logType)
|
||||
void ReadPipe(HANDLE pipe, Array<char>& rawData, Array<Char>& logData, LogType logType, CreateProcessSettings& settings)
|
||||
{
|
||||
// Check if any data is ready to read
|
||||
DWORD bytesAvailable = 0;
|
||||
@@ -1022,162 +982,183 @@ void ReadPipe(HANDLE pipe, Array<char>& rawData, Array<Char>& logData, LogType l
|
||||
logData.Resize(rawData.Count() + 1);
|
||||
StringUtils::ConvertANSI2UTF16(rawData.Get(), logData.Get(), rawData.Count());
|
||||
logData.Last() = '\0';
|
||||
Log::Logger::Write(logType, StringView(logData.Get(), rawData.Count()));
|
||||
if (settings.LogOutput)
|
||||
Log::Logger::Write(logType, StringView(logData.Get(), rawData.Count()));
|
||||
if (settings.SaveOutput)
|
||||
settings.Output.Add(logData.Get(), rawData.Count());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int32 WindowsPlatform::RunProcess(const StringView& cmdLine, const StringView& workingDir, const Dictionary<String, String>& environment, bool hiddenWindow)
|
||||
int32 WindowsPlatform::CreateProcess(CreateProcessSettings& settings)
|
||||
{
|
||||
const bool captureStdOut = true;
|
||||
|
||||
// Info
|
||||
LOG(Info, "Command: {0}", cmdLine);
|
||||
if (workingDir.HasChars())
|
||||
LOG(Info, "Command: {0} {1}", settings.FileName, settings.Arguments);
|
||||
if (settings.WorkingDirectory.HasChars())
|
||||
{
|
||||
LOG(Info, "Working directory: {0}", workingDir);
|
||||
LOG(Info, "Working directory: {0}", settings.WorkingDirectory);
|
||||
}
|
||||
const bool captureStdOut = settings.LogOutput || settings.SaveOutput;
|
||||
|
||||
int32 result = -1;
|
||||
|
||||
STARTUPINFOEX startupInfoEx;
|
||||
ZeroMemory(&startupInfoEx, sizeof(startupInfoEx));
|
||||
startupInfoEx.StartupInfo.cb = sizeof(startupInfoEx);
|
||||
if (hiddenWindow)
|
||||
int32 result = 0;
|
||||
if (settings.ShellExecute)
|
||||
{
|
||||
startupInfoEx.StartupInfo.dwFlags |= STARTF_USESHOWWINDOW;
|
||||
startupInfoEx.StartupInfo.wShowWindow |= SW_HIDE | SW_SHOWNOACTIVATE;
|
||||
}
|
||||
|
||||
DWORD dwCreationFlags = NORMAL_PRIORITY_CLASS | DETACHED_PROCESS;
|
||||
if (hiddenWindow)
|
||||
dwCreationFlags |= CREATE_NO_WINDOW;
|
||||
|
||||
Char* environmentStr = nullptr;
|
||||
if (environment.HasItems())
|
||||
{
|
||||
dwCreationFlags |= CREATE_UNICODE_ENVIRONMENT;
|
||||
|
||||
int32 totalLength = 1;
|
||||
for (auto& e : environment)
|
||||
totalLength += e.Key.Length() + e.Value.Length() + 2;
|
||||
|
||||
environmentStr = (Char*)Allocator::Allocate(totalLength * sizeof(Char));
|
||||
|
||||
Char* env = environmentStr;
|
||||
for (auto& e : environment)
|
||||
SHELLEXECUTEINFOW shExecInfo = { 0 };
|
||||
shExecInfo.cbSize = sizeof(SHELLEXECUTEINFOW);
|
||||
shExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
|
||||
shExecInfo.lpFile = settings.FileName.GetText();
|
||||
shExecInfo.lpParameters = settings.Arguments.HasChars() ? settings.Arguments.Get() : nullptr;
|
||||
shExecInfo.lpDirectory = settings.WorkingDirectory.HasChars() ? settings.WorkingDirectory.Get() : nullptr;
|
||||
shExecInfo.nShow = settings.HiddenWindow ? SW_HIDE : SW_SHOW;
|
||||
if (ShellExecuteExW(&shExecInfo) == FALSE)
|
||||
{
|
||||
auto& key = e.Key;
|
||||
auto& value = e.Value;
|
||||
Platform::MemoryCopy(env, key.Get(), key.Length() * sizeof(Char));
|
||||
env += key.Length();
|
||||
*env++ = '=';
|
||||
Platform::MemoryCopy(env, value.Get(), value.Length() * sizeof(Char));
|
||||
env += value.Length();
|
||||
*env++ = 0;
|
||||
result = 1;
|
||||
LOG(Warning, "Cannot start process. Error code: 0x{0:x}", (uint64)GetLastError());
|
||||
}
|
||||
*env++ = 0;
|
||||
ASSERT((uint64)env - (uint64)environmentStr == (uint64)(totalLength * sizeof(Char)));
|
||||
}
|
||||
|
||||
HANDLE stdOutRead = nullptr;
|
||||
HANDLE stdErrRead = nullptr;
|
||||
|
||||
if (captureStdOut)
|
||||
{
|
||||
dwCreationFlags |= EXTENDED_STARTUPINFO_PRESENT;
|
||||
startupInfoEx.StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
|
||||
|
||||
SECURITY_ATTRIBUTES securityAttributes;
|
||||
securityAttributes.nLength = sizeof(SECURITY_ATTRIBUTES);
|
||||
securityAttributes.bInheritHandle = TRUE;
|
||||
securityAttributes.lpSecurityDescriptor = nullptr;
|
||||
|
||||
if (!CreatePipe(&stdOutRead, &startupInfoEx.StartupInfo.hStdOutput, &securityAttributes, 0) ||
|
||||
!CreatePipe(&stdErrRead, &startupInfoEx.StartupInfo.hStdError, &securityAttributes, 0))
|
||||
else if (settings.WaitForEnd)
|
||||
{
|
||||
LOG(Warning, "CreatePipe failed");
|
||||
return 1;
|
||||
WaitForSingleObject(shExecInfo.hProcess, INFINITE);
|
||||
DWORD exitCode;
|
||||
if (GetExitCodeProcess(shExecInfo.hProcess, &exitCode) != 0)
|
||||
result = exitCode;
|
||||
CloseHandle(shExecInfo.hProcess);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result = -1;
|
||||
const String cmdLine = settings.FileName + TEXT(" ") + settings.Arguments;
|
||||
|
||||
STARTUPINFOEX startupInfoEx;
|
||||
ZeroMemory(&startupInfoEx, sizeof(startupInfoEx));
|
||||
startupInfoEx.StartupInfo.cb = sizeof(startupInfoEx);
|
||||
if (settings.HiddenWindow)
|
||||
{
|
||||
startupInfoEx.StartupInfo.dwFlags |= STARTF_USESHOWWINDOW;
|
||||
startupInfoEx.StartupInfo.wShowWindow |= SW_HIDE | SW_SHOWNOACTIVATE;
|
||||
}
|
||||
|
||||
SIZE_T bufferSize = 0;
|
||||
if (!InitializeProcThreadAttributeList(nullptr, 1, 0, &bufferSize) && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
|
||||
DWORD dwCreationFlags = NORMAL_PRIORITY_CLASS | DETACHED_PROCESS;
|
||||
if (settings.HiddenWindow)
|
||||
dwCreationFlags |= CREATE_NO_WINDOW;
|
||||
|
||||
Char* environmentStr = nullptr;
|
||||
if (settings.Environment.HasItems())
|
||||
{
|
||||
startupInfoEx.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)Allocator::Allocate(bufferSize);
|
||||
if (!InitializeProcThreadAttributeList(startupInfoEx.lpAttributeList, 1, 0, &bufferSize))
|
||||
dwCreationFlags |= CREATE_UNICODE_ENVIRONMENT;
|
||||
|
||||
int32 totalLength = 1;
|
||||
for (auto& e : settings.Environment)
|
||||
totalLength += e.Key.Length() + e.Value.Length() + 2;
|
||||
|
||||
environmentStr = (Char*)Allocator::Allocate(totalLength * sizeof(Char));
|
||||
|
||||
Char* env = environmentStr;
|
||||
for (auto& e : settings.Environment)
|
||||
{
|
||||
LOG(Warning, "InitializeProcThreadAttributeList failed");
|
||||
auto& key = e.Key;
|
||||
auto& value = e.Value;
|
||||
Platform::MemoryCopy(env, key.Get(), key.Length() * sizeof(Char));
|
||||
env += key.Length();
|
||||
*env++ = '=';
|
||||
Platform::MemoryCopy(env, value.Get(), value.Length() * sizeof(Char));
|
||||
env += value.Length();
|
||||
*env++ = 0;
|
||||
}
|
||||
*env++ = 0;
|
||||
ASSERT((uint64)env - (uint64)environmentStr == (uint64)(totalLength * sizeof(Char)));
|
||||
}
|
||||
|
||||
HANDLE stdOutRead = nullptr;
|
||||
HANDLE stdErrRead = nullptr;
|
||||
|
||||
if (captureStdOut)
|
||||
{
|
||||
dwCreationFlags |= EXTENDED_STARTUPINFO_PRESENT;
|
||||
startupInfoEx.StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
|
||||
|
||||
SECURITY_ATTRIBUTES securityAttributes;
|
||||
securityAttributes.nLength = sizeof(SECURITY_ATTRIBUTES);
|
||||
securityAttributes.bInheritHandle = TRUE;
|
||||
securityAttributes.lpSecurityDescriptor = nullptr;
|
||||
|
||||
if (!CreatePipe(&stdOutRead, &startupInfoEx.StartupInfo.hStdOutput, &securityAttributes, 0) ||
|
||||
!CreatePipe(&stdErrRead, &startupInfoEx.StartupInfo.hStdError, &securityAttributes, 0))
|
||||
{
|
||||
LOG(Warning, "CreatePipe failed");
|
||||
return 1;
|
||||
}
|
||||
|
||||
SIZE_T bufferSize = 0;
|
||||
if (!InitializeProcThreadAttributeList(nullptr, 1, 0, &bufferSize) && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
|
||||
{
|
||||
startupInfoEx.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)Allocator::Allocate(bufferSize);
|
||||
if (!InitializeProcThreadAttributeList(startupInfoEx.lpAttributeList, 1, 0, &bufferSize))
|
||||
{
|
||||
LOG(Warning, "InitializeProcThreadAttributeList failed");
|
||||
goto ERROR_EXIT;
|
||||
}
|
||||
}
|
||||
|
||||
HANDLE inheritHandles[2] = { startupInfoEx.StartupInfo.hStdOutput, startupInfoEx.StartupInfo.hStdError };
|
||||
if (!UpdateProcThreadAttribute(startupInfoEx.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, inheritHandles, sizeof(inheritHandles), nullptr, nullptr))
|
||||
{
|
||||
LOG(Warning, "UpdateProcThreadAttribute failed");
|
||||
goto ERROR_EXIT;
|
||||
}
|
||||
}
|
||||
|
||||
HANDLE inheritHandles[2] = { startupInfoEx.StartupInfo.hStdOutput, startupInfoEx.StartupInfo.hStdError };
|
||||
if (!UpdateProcThreadAttribute(startupInfoEx.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, inheritHandles, sizeof(inheritHandles), nullptr, nullptr))
|
||||
// Create the process
|
||||
PROCESS_INFORMATION procInfo;
|
||||
if (!CreateProcessW(nullptr, const_cast<LPWSTR>(cmdLine.GetText()), nullptr, nullptr, TRUE, dwCreationFlags, (LPVOID)environmentStr, settings.WorkingDirectory.HasChars() ? settings.WorkingDirectory.Get() : nullptr, &startupInfoEx.StartupInfo, &procInfo))
|
||||
{
|
||||
LOG(Warning, "UpdateProcThreadAttribute failed");
|
||||
LOG(Warning, "Cannot start process. Error code: 0x{0:x}", (uint64)GetLastError());
|
||||
goto ERROR_EXIT;
|
||||
}
|
||||
}
|
||||
|
||||
// Create the process
|
||||
PROCESS_INFORMATION procInfo;
|
||||
if (!CreateProcessW(nullptr, const_cast<LPWSTR>(String(cmdLine).GetText()), nullptr, nullptr, TRUE, dwCreationFlags, (LPVOID)environmentStr, workingDir.HasChars() ? workingDir.Get() : nullptr, &startupInfoEx.StartupInfo, &procInfo))
|
||||
{
|
||||
LOG(Warning, "Cannot start process '{0}'. Error code: 0x{1:x}", cmdLine, static_cast<int64>(GetLastError()));
|
||||
goto ERROR_EXIT;
|
||||
}
|
||||
|
||||
if (stdOutRead != nullptr)
|
||||
{
|
||||
// Keep reading std output and std error streams until process is running
|
||||
Array<char> rawData;
|
||||
Array<Char> logData;
|
||||
do
|
||||
if (stdOutRead != nullptr)
|
||||
{
|
||||
ReadPipe(stdOutRead, rawData, logData, LogType::Info);
|
||||
ReadPipe(stdErrRead, rawData, logData, LogType::Error);
|
||||
Sleep(1);
|
||||
} while (IsProcRunning(procInfo.hProcess));
|
||||
ReadPipe(stdOutRead, rawData, logData, LogType::Info);
|
||||
ReadPipe(stdErrRead, rawData, logData, LogType::Error);
|
||||
}
|
||||
else
|
||||
{
|
||||
WaitForSingleObject(procInfo.hProcess, INFINITE);
|
||||
}
|
||||
// Keep reading std output and std error streams until process is running
|
||||
Array<char> rawData;
|
||||
Array<Char> logData;
|
||||
do
|
||||
{
|
||||
ReadPipe(stdOutRead, rawData, logData, LogType::Info, settings);
|
||||
ReadPipe(stdErrRead, rawData, logData, LogType::Error, settings);
|
||||
Sleep(1);
|
||||
} while (IsProcRunning(procInfo.hProcess));
|
||||
ReadPipe(stdOutRead, rawData, logData, LogType::Info, settings);
|
||||
ReadPipe(stdErrRead, rawData, logData, LogType::Error, settings);
|
||||
}
|
||||
else
|
||||
{
|
||||
WaitForSingleObject(procInfo.hProcess, INFINITE);
|
||||
}
|
||||
|
||||
DWORD exitCode;
|
||||
if (GetExitCodeProcess(procInfo.hProcess, &exitCode) != 0)
|
||||
result = exitCode;
|
||||
DWORD exitCode;
|
||||
if (GetExitCodeProcess(procInfo.hProcess, &exitCode) != 0)
|
||||
result = exitCode;
|
||||
|
||||
CloseHandle(procInfo.hProcess);
|
||||
CloseHandle(procInfo.hThread);
|
||||
CloseHandle(procInfo.hProcess);
|
||||
CloseHandle(procInfo.hThread);
|
||||
|
||||
// Cleanup
|
||||
ERROR_EXIT:
|
||||
if (startupInfoEx.StartupInfo.hStdOutput != nullptr)
|
||||
{
|
||||
CloseHandle(startupInfoEx.StartupInfo.hStdOutput);
|
||||
// Cleanup
|
||||
ERROR_EXIT:
|
||||
if (startupInfoEx.StartupInfo.hStdOutput != nullptr)
|
||||
CloseHandle(startupInfoEx.StartupInfo.hStdOutput);
|
||||
if (startupInfoEx.StartupInfo.hStdError != nullptr)
|
||||
CloseHandle(startupInfoEx.StartupInfo.hStdError);
|
||||
if (stdOutRead != nullptr)
|
||||
CloseHandle(stdOutRead);
|
||||
if (stdErrRead != nullptr)
|
||||
CloseHandle(stdErrRead);
|
||||
if (startupInfoEx.lpAttributeList != nullptr)
|
||||
{
|
||||
DeleteProcThreadAttributeList(startupInfoEx.lpAttributeList);
|
||||
Allocator::Free(startupInfoEx.lpAttributeList);
|
||||
}
|
||||
if (environmentStr)
|
||||
Allocator::Free(environmentStr);
|
||||
}
|
||||
if (startupInfoEx.StartupInfo.hStdError != nullptr)
|
||||
{
|
||||
CloseHandle(startupInfoEx.StartupInfo.hStdError);
|
||||
}
|
||||
if (stdOutRead != nullptr)
|
||||
{
|
||||
CloseHandle(stdOutRead);
|
||||
}
|
||||
if (stdErrRead != nullptr)
|
||||
{
|
||||
CloseHandle(stdErrRead);
|
||||
}
|
||||
if (startupInfoEx.lpAttributeList != nullptr)
|
||||
{
|
||||
DeleteProcThreadAttributeList(startupInfoEx.lpAttributeList);
|
||||
Allocator::Free(startupInfoEx.lpAttributeList);
|
||||
}
|
||||
if (environmentStr)
|
||||
Allocator::Free(environmentStr);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user