Refactor platform process startup with CreateProcessSettings

This commit is contained in:
Wojtek Figat
2023-03-22 14:09:20 +01:00
parent 497aca829d
commit 3bbc7faf11
11 changed files with 351 additions and 281 deletions

View File

@@ -552,23 +552,50 @@ bool PlatformBase::SetEnvironmentVariable(const String& name, const String& valu
return true;
}
int32 PlatformBase::StartProcess(const StringView& filename, const StringView& args, const StringView& workingDir, bool hiddenWindow, bool waitForEnd)
int32 PlatformBase::CreateProcess(CreateProcessSettings& settings)
{
// Not supported
return -1;
}
PRAGMA_DISABLE_DEPRECATION_WARNINGS
#include "Engine/Platform/CreateProcessSettings.h"
int32 PlatformBase::StartProcess(const StringView& filename, const StringView& args, const StringView& workingDir, bool hiddenWindow, bool waitForEnd)
{
CreateProcessSettings procSettings;
procSettings.FileName = filename;
procSettings.Arguments = args;
procSettings.WorkingDirectory = workingDir;
procSettings.HiddenWindow = hiddenWindow;
procSettings.WaitForEnd = waitForEnd;
procSettings.LogOutput = waitForEnd;
procSettings.ShellExecute = true;
return CreateProcess(procSettings);
}
int32 PlatformBase::RunProcess(const StringView& cmdLine, const StringView& workingDir, bool hiddenWindow)
{
return Platform::RunProcess(cmdLine, workingDir, Dictionary<String, String>(), hiddenWindow);
CreateProcessSettings procSettings;
procSettings.FileName = cmdLine;
procSettings.WorkingDirectory = workingDir;
procSettings.HiddenWindow = hiddenWindow;
return CreateProcess(procSettings);
}
int32 PlatformBase::RunProcess(const StringView& cmdLine, const StringView& workingDir, const Dictionary<String, String>& environment, bool hiddenWindow)
{
// Not supported
return -1;
CreateProcessSettings procSettings;
procSettings.FileName = cmdLine;
procSettings.WorkingDirectory = workingDir;
procSettings.Environment = environment;
procSettings.HiddenWindow = hiddenWindow;
return CreateProcess(procSettings);
}
PRAGMA_ENABLE_DEPRECATION_WARNINGS
Array<PlatformBase::StackFrame> PlatformBase::GetStackFrames(int32 skipCount, int32 maxDepth, void* context)
{
return Array<StackFrame>();

View File

@@ -10,6 +10,7 @@ struct Guid;
struct CPUInfo;
struct MemoryStats;
struct ProcessMemoryStats;
struct CreateProcessSettings;
struct CreateWindowSettings;
struct BatteryInfo;
@@ -742,9 +743,9 @@ public:
static bool SetEnvironmentVariable(const String& name, const String& value);
public:
/// <summary>
/// Starts a new process (runs app).
/// [Deprecated in v1.6]
/// </summary>
/// <param name="filename">The path to the executable file.</param>
/// <param name="args">Custom arguments for command line</param>
@@ -752,26 +753,35 @@ public:
/// <param name="hiddenWindow">True if start process with hidden window</param>
/// <param name="waitForEnd">True if wait for process competition</param>
/// <returns>Retrieves the termination status of the specified process. Valid only if processed ended.</returns>
API_FUNCTION() static int32 StartProcess(const StringView& filename, const StringView& args, const StringView& workingDir, bool hiddenWindow = false, bool waitForEnd = false);
API_FUNCTION() DEPRECATED static int32 StartProcess(const StringView& filename, const StringView& args, const StringView& workingDir, bool hiddenWindow = false, bool waitForEnd = false);
/// <summary>
/// Starts a new process (runs commandline). Waits for it's end and captures its output.
/// [Deprecated in v1.6]
/// </summary>
/// <param name="cmdLine">Command line to execute</param>
/// <param name="workingDir">The custom path of the working directory.</param>
/// <param name="hiddenWindow">True if start process with hidden window.</param>
/// <returns>Retrieves the termination status of the specified process. Valid only if processed ended.</returns>
API_FUNCTION() static int32 RunProcess(const StringView& cmdLine, const StringView& workingDir, bool hiddenWindow = true);
API_FUNCTION() DEPRECATED static int32 RunProcess(const StringView& cmdLine, const StringView& workingDir, bool hiddenWindow = true);
/// <summary>
/// Starts a new process (runs commandline). Waits for it's end and captures its output.
/// [Deprecated in v1.6]
/// </summary>
/// <param name="cmdLine">Command line to execute</param>
/// <param name="workingDir">The custom path of the working directory.</param>
/// <param name="environment">The process environment variables. If null the current process environment is used.</param>
/// <param name="hiddenWindow">True if start process with hidden window.</param>
/// <returns>Retrieves the termination status of the specified process. Valid only if processed ended.</returns>
API_FUNCTION() static int32 RunProcess(const StringView& cmdLine, const StringView& workingDir, const Dictionary<String, String, HeapAllocation>& environment, bool hiddenWindow = true);
API_FUNCTION() DEPRECATED static int32 RunProcess(const StringView& cmdLine, const StringView& workingDir, const Dictionary<String, String, HeapAllocation>& environment, bool hiddenWindow = true);
/// <summary>
/// Creates a new process.
/// </summary>
/// <param name="settings">The process settings.</param>
/// <returns>Retrieves the termination status of the specified process. Valid only if processed ended.</returns>
API_FUNCTION() static int32 CreateProcess(API_PARAM(Ref) CreateProcessSettings& settings);
/// <summary>
/// Creates the window.

View File

@@ -0,0 +1,65 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#pragma once
#include "Engine/Core/Types/String.h"
#include "Engine/Core/Collections/Array.h"
#include "Engine/Core/Collections/Dictionary.h"
/// <summary>
/// Settings for new process.
/// </summary>
API_STRUCT(NoDefault) struct CreateProcessSettings
{
DECLARE_SCRIPTING_TYPE_MINIMAL(CreateProcessSettings);
/// <summary>
/// The path to the executable file.
/// </summary>
API_FIELD() String FileName;
/// <summary>
/// The custom arguments for command line.
/// </summary>
API_FIELD() String Arguments;
/// <summary>
/// The custom folder path where start process. Empty if unused.
/// </summary>
API_FIELD() String WorkingDirectory;
/// <summary>
/// True if capture process output and print to the log.
/// </summary>
API_FIELD() bool LogOutput = true;
/// <summary>
/// True if capture process output and store it as Output text array.
/// </summary>
API_FIELD() bool SaveOutput = false;
/// <summary>
/// True if wait for the process execution end.
/// </summary>
API_FIELD() bool WaitForEnd = true;
/// <summary>
/// True if hint process to hide window. Supported only on Windows platform.
/// </summary>
API_FIELD() bool HiddenWindow = true;
/// <summary>
/// True if use operating system shell to start the process. Supported only on Windows platform.
/// </summary>
API_FIELD() bool ShellExecute = false;
/// <summary>
/// Custom environment variables to set for the process. Empty if unused. Additionally newly spawned process inherits this process vars which can be overriden here.
/// </summary>
API_FIELD() Dictionary<String, String> Environment;
/// <summary>
/// Output process contents.
/// </summary>
API_FIELD() Array<Char> Output;
};

View File

@@ -20,6 +20,7 @@
#include "Engine/Platform/StringUtils.h"
#include "Engine/Platform/MessageBox.h"
#include "Engine/Platform/WindowsManager.h"
#include "Engine/Platform/CreateProcessSettings.h"
#include "Engine/Platform/Clipboard.h"
#include "Engine/Platform/IGuiData.h"
#include "Engine/Platform/Base/PlatformUtils.h"
@@ -2799,11 +2800,19 @@ bool LinuxPlatform::SetEnvironmentVariable(const String& name, const String& val
return setenv(StringAsANSI<>(*name).Get(), StringAsANSI<>(*value).Get(), true) != 0;
}
int32 LinuxProcess(const StringView& cmdLine, const StringView& workingDir, const Dictionary<String, String>& environment, bool waitForEnd, bool logOutput)
int32 LinuxPlatform::CreateProcess(CreateProcessSettings& settings)
{
LOG(Info, "Command: {0} {1}", settings.FileName, settings.Arguments);
if (settings.WorkingDirectory.HasChars())
{
LOG(Info, "Working directory: {0}", settings.WorkingDirectory);
}
const bool captureStdOut = settings.LogOutput || settings.SaveOutput;
const String cmdLine = settings.FileName + TEXT(" ") + settings.Arguments;
int fildes[2];
int32 returnCode = 0;
if (logOutput && pipe(fildes) < 0)
if (captureStdOut && pipe(fildes) < 0)
{
LOG(Warning, "Failed to create a pipe, errno={}", errno);
}
@@ -2821,16 +2830,16 @@ int32 LinuxProcess(const StringView& cmdLine, const StringView& workingDir, cons
const char* const cmd[] = { "sh", "-c", StringAnsi(cmdLine).GetText(), (char *)0 };
// we could use the execve and supply a list of variable assignments but as we would have to build
// and quote the values there is hardly any benefit over using setenv() calls
for (auto& e : environment)
for (auto& e : settings.Environment)
{
setenv(StringAnsi(e.Key).GetText(), StringAnsi(e.Value).GetText(), 1);
}
if (workingDir.HasChars() && chdir(StringAnsi(workingDir).GetText()) != 0)
if (settings.WorkingDirectory.HasChars() && chdir(StringAnsi(settings.WorkingDirectory).GetText()) != 0)
{
LOG(Warning, "Failed to set working directory to {}, errno={}", workingDir, errno);
LOG(Warning, "Failed to set working directory to {}, errno={}", settings.WorkingDirectory, errno);
}
if (logOutput)
if (captureStdOut)
{
close(fildes[0]); // close the reading end of the pipe
dup2(fildes[1], STDOUT_FILENO); // redirect stdout to pipe
@@ -2850,21 +2859,25 @@ int32 LinuxProcess(const StringView& cmdLine, const StringView& workingDir, cons
{
// parent process
LOG(Info, "{} started, pid={}", cmdLine, pid);
if (waitForEnd)
if (settings.WaitForEnd)
{
int stat_loc;
if (logOutput)
if (captureStdOut)
{
char lineBuffer[1024];
close(fildes[1]); // close the writing end of the pipe
FILE* stdPipe = fdopen(fildes[0], "r");
while (fgets(lineBuffer, sizeof(lineBuffer), stdPipe) != NULL)
{
char *p = lineBuffer + strlen(lineBuffer)-1;
if (*p == '\n') *p=0;
Log::Logger::Write(LogType::Info, String(lineBuffer));
char *p = lineBuffer + strlen(lineBuffer) - 1;
if (*p == '\n') *p = 0;
String line(lineBuffer);
if (settings.SaveOutput)
settings.Output.Add(line.Get(), line.Length());
if (settings.LogOutput)
Log::Logger::Write(LogType::Info, line);
}
}
int stat_loc;
if (waitpid(pid, &stat_loc, 0) < 0)
{
LOG(Warning, "Waiting for pid {} failed, errno={}", pid, errno);
@@ -2899,24 +2912,6 @@ int32 LinuxProcess(const StringView& cmdLine, const StringView& workingDir, cons
return returnCode;
}
int32 LinuxPlatform::StartProcess(const StringView& filename, const StringView& args, const StringView& workingDir, bool hiddenWindow, bool waitForEnd)
{
// hiddenWindow has hardly any meaning on UNIX/Linux/OSX as the program that is called decides whether it has a GUI or not
String cmdLine(filename);
if (args.HasChars()) cmdLine = cmdLine + TEXT(" ") + args;
return LinuxProcess(cmdLine, workingDir, Dictionary<String, String>(), waitForEnd, false);
}
int32 LinuxPlatform::RunProcess(const StringView& cmdLine, const StringView& workingDir, bool hiddenWindow)
{
return LinuxProcess(cmdLine, workingDir, Dictionary<String, String>(), true, true);
}
int32 LinuxPlatform::RunProcess(const StringView& cmdLine, const StringView& workingDir, const Dictionary<String, String>& environment, bool hiddenWindow)
{
return LinuxProcess(cmdLine, workingDir, environment, true, true);
}
void* LinuxPlatform::LoadLibrary(const Char* filename)
{
const StringAsANSI<> filenameANSI(filename);

View File

@@ -134,9 +134,7 @@ public:
static void GetEnvironmentVariables(Dictionary<String, String, HeapAllocation>& result);
static bool GetEnvironmentVariable(const String& name, String& value);
static bool SetEnvironmentVariable(const String& name, const String& value);
static int32 StartProcess(const StringView& filename, const StringView& args, const StringView& workingDir, bool hiddenWindow = false, bool waitForEnd = false);
static int32 RunProcess(const StringView& cmdLine, const StringView& workingDir, bool hiddenWindow = true);
static int32 RunProcess(const StringView& cmdLine, const StringView& workingDir, const Dictionary<String, String, HeapAllocation>& environment, bool hiddenWindow = true);
static int32 CreateProcess(CreateProcessSettings& settings);
static void* LoadLibrary(const Char* filename);
static void FreeLibrary(void* handle);
static void* GetProcAddress(void* handle, const char* symbol);

View File

@@ -23,6 +23,7 @@
#include "Engine/Platform/Clipboard.h"
#include "Engine/Platform/IGuiData.h"
#include "Engine/Platform/Base/PlatformUtils.h"
#include "Engine/Platform/CreateProcessSettings.h"
#include "Engine/Utilities/StringConverter.h"
#include "Engine/Threading/Threading.h"
#include "Engine/Engine/Engine.h"
@@ -369,65 +370,22 @@ Window* MacPlatform::CreateWindow(const CreateWindowSettings& settings)
return New<MacWindow>(settings);
}
int32 MacProcess(const StringView& cmdLine, const StringView& workingDir, const Dictionary<String, String>& environment, bool waitForEnd, bool logOutput)
int32 MacPlatform::CreateProcess(CreateProcessSettings& settings)
{
LOG(Info, "Command: {0}", cmdLine);
LOG(Info, "Command: {0} {1}", settings.FileName, settings.Arguments);
String cwd;
if (workingDir.Length() != 0)
if (settings.WorkingDirectory.HasChars())
{
LOG(Info, "Working directory: {0}", workingDir);
LOG(Info, "Working directory: {0}", settings.WorkingDirectory);
cwd = Platform::GetWorkingDirectory();
Platform::SetWorkingDirectory(workingDir);
Platform::SetWorkingDirectory(settings.WorkingDirectory);
}
StringAsANSI<> cmdLineAnsi(*cmdLine, cmdLine.Length());
FILE* pipe = popen(cmdLineAnsi.Get(), "r");
if (cwd.Length() != 0)
{
Platform::SetWorkingDirectory(cwd);
}
if (!pipe)
{
LOG(Warning, "Failed to start process, errno={}", errno);
return -1;
}
// TODO: environment
int32 returnCode = 0;
if (waitForEnd)
{
int stat_loc;
if (logOutput)
{
char lineBuffer[1024];
while (fgets(lineBuffer, sizeof(lineBuffer), pipe) != NULL)
{
char *p = lineBuffer + strlen(lineBuffer) - 1;
if (*p == '\n') *p=0;
Log::Logger::Write(LogType::Info, String(lineBuffer));
}
}
else
{
while (!feof(pipe))
{
sleep(1);
}
}
}
return returnCode;
}
int32 MacPlatform::StartProcess(const StringView& filename, const StringView& args, const StringView& workingDir, bool hiddenWindow, bool waitForEnd)
{
// hiddenWindow has hardly any meaning on UNIX/Linux/OSX as the program that is called decides whether it has a GUI or not
const bool captureStdOut = settings.LogOutput || settings.SaveOutput;
// Special case if filename points to the app package (use actual executable)
String exePath = filename;
String exePath = settings.FileName;
{
NSString* processPath = (NSString*)AppleUtils::ToString(filename);
NSString* processPath = (NSString*)AppleUtils::ToString(exePath);
if (![[NSFileManager defaultManager] fileExistsAtPath: processPath])
{
NSString* appName = [[processPath lastPathComponent] stringByDeletingPathExtension];
@@ -448,18 +406,48 @@ int32 MacPlatform::StartProcess(const StringView& filename, const StringView& ar
}
}
String cmdLine = String::Format(TEXT("\"{0}\" {1}"), exePath, args);
return MacProcess(cmdLine, workingDir, Dictionary<String, String>(), waitForEnd, false);
}
const String cmdLine = exePath + TEXT(" ") + settings.Arguments;
const StringAsANSI<> cmdLineAnsi(*cmdLine, cmdLine.Length());
FILE* pipe = popen(cmdLineAnsi.Get(), "r");
if (cwd.Length() != 0)
{
Platform::SetWorkingDirectory(cwd);
}
if (!pipe)
{
LOG(Warning, "Failed to start process, errno={}", errno);
return -1;
}
int32 MacPlatform::RunProcess(const StringView& cmdLine, const StringView& workingDir, bool hiddenWindow)
{
return MacProcess(cmdLine, workingDir, Dictionary<String, String>(), true, true);
}
// TODO: environment
int32 MacPlatform::RunProcess(const StringView& cmdLine, const StringView& workingDir, const Dictionary<String, String>& environment, bool hiddenWindow)
{
return MacProcess(cmdLine, workingDir, environment, true, true);
int32 returnCode = 0;
if (settings.WaitForEnd)
{
if (captureStdOut)
{
char lineBuffer[1024];
while (fgets(lineBuffer, sizeof(lineBuffer), pipe) != NULL)
{
char* p = lineBuffer + strlen(lineBuffer) - 1;
if (*p == '\n') *p = 0;
String line(lineBuffer);
if (settings.SaveOutput)
settings.Output.Add(line.Get(), line.Length());
if (settings.LogOutput)
Log::Logger::Write(LogType::Info, line);
}
}
else
{
while (!feof(pipe))
{
sleep(1);
}
}
}
return returnCode;
}
#endif

View File

@@ -28,9 +28,7 @@ public:
static Rectangle GetVirtualDesktopBounds();
static String GetMainDirectory();
static Window* CreateWindow(const CreateWindowSettings& settings);
static int32 StartProcess(const StringView& filename, const StringView& args, const StringView& workingDir, bool hiddenWindow = false, bool waitForEnd = false);
static int32 RunProcess(const StringView& cmdLine, const StringView& workingDir, bool hiddenWindow = true);
static int32 RunProcess(const StringView& cmdLine, const StringView& workingDir, const Dictionary<String, String, HeapAllocation>& environment, bool hiddenWindow = true);
static int32 CreateProcess(CreateProcessSettings& settings);
};
#endif

View File

@@ -55,6 +55,7 @@
#undef MessageBox
#undef GetCommandLine
#undef CreateWindow
#undef CreateProcess
#undef SetWindowText
#undef DrawText
#undef CreateFont

View File

@@ -6,6 +6,7 @@
#include "Engine/Platform/File.h"
#include "Engine/Platform/Window.h"
#include "Engine/Platform/Windows/ComPtr.h"
#include "Engine/Platform/CreateProcessSettings.h"
#include "Engine/Core/Types/StringView.h"
#include "../Win32/IncludeWindowsHeaders.h"
@@ -16,6 +17,7 @@
#include <ShlObj.h>
#include <ShellAPI.h>
#include <KnownFolders.h>
#undef ShellExecute
namespace Windows
{
@@ -335,7 +337,14 @@ bool WindowsFileSystem::ShowBrowseFolderDialog(Window* parentWindow, const Strin
bool WindowsFileSystem::ShowFileExplorer(const StringView& path)
{
return Platform::StartProcess(path, StringView::Empty, StringView::Empty) != 0;
CreateProcessSettings procSettings;
procSettings.FileName = path;
procSettings.FileName = path;
procSettings.HiddenWindow = false;
procSettings.WaitForEnd = false;
procSettings.LogOutput = false;
procSettings.ShellExecute = true;
return Platform::CreateProcess(procSettings) != 0;
}
#endif

View File

@@ -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;
}

View File

@@ -77,9 +77,7 @@ public:
static void GetEnvironmentVariables(Dictionary<String, String, HeapAllocation>& result);
static bool GetEnvironmentVariable(const String& name, String& value);
static bool SetEnvironmentVariable(const String& name, const String& value);
static int32 StartProcess(const StringView& filename, const StringView& args, const StringView& workingDir, bool hiddenWindow = false, bool waitForEnd = false);
static int32 RunProcess(const StringView& cmdLine, const StringView& workingDir, bool hiddenWindow = true);
static int32 RunProcess(const StringView& cmdLine, const StringView& workingDir, const Dictionary<String, String, HeapAllocation>& environment, bool hiddenWindow = true);
static int32 CreateProcess(CreateProcessSettings& settings);
static Window* CreateWindow(const CreateWindowSettings& settings);
static void* LoadLibrary(const Char* filename);
static Array<StackFrame, HeapAllocation> GetStackFrames(int32 skipCount = 0, int32 maxDepth = 60, void* context = nullptr);