diff --git a/Source/Engine/Platform/Base/PlatformBase.cpp b/Source/Engine/Platform/Base/PlatformBase.cpp index 99e2a5d06..8649c4441 100644 --- a/Source/Engine/Platform/Base/PlatformBase.cpp +++ b/Source/Engine/Platform/Base/PlatformBase.cpp @@ -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(), 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& 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::GetStackFrames(int32 skipCount, int32 maxDepth, void* context) { return Array(); diff --git a/Source/Engine/Platform/Base/PlatformBase.h b/Source/Engine/Platform/Base/PlatformBase.h index 4927f17d4..d3bcb8dfb 100644 --- a/Source/Engine/Platform/Base/PlatformBase.h +++ b/Source/Engine/Platform/Base/PlatformBase.h @@ -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: - /// /// Starts a new process (runs app). + /// [Deprecated in v1.6] /// /// The path to the executable file. /// Custom arguments for command line @@ -752,26 +753,35 @@ public: /// True if start process with hidden window /// True if wait for process competition /// Retrieves the termination status of the specified process. Valid only if processed ended. - 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); /// /// Starts a new process (runs commandline). Waits for it's end and captures its output. + /// [Deprecated in v1.6] /// /// Command line to execute /// The custom path of the working directory. /// True if start process with hidden window. /// Retrieves the termination status of the specified process. Valid only if processed ended. - 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); /// /// Starts a new process (runs commandline). Waits for it's end and captures its output. + /// [Deprecated in v1.6] /// /// Command line to execute /// The custom path of the working directory. /// The process environment variables. If null the current process environment is used. /// True if start process with hidden window. /// Retrieves the termination status of the specified process. Valid only if processed ended. - API_FUNCTION() static int32 RunProcess(const StringView& cmdLine, const StringView& workingDir, const Dictionary& environment, bool hiddenWindow = true); + API_FUNCTION() DEPRECATED static int32 RunProcess(const StringView& cmdLine, const StringView& workingDir, const Dictionary& environment, bool hiddenWindow = true); + + /// + /// Creates a new process. + /// + /// The process settings. + /// Retrieves the termination status of the specified process. Valid only if processed ended. + API_FUNCTION() static int32 CreateProcess(API_PARAM(Ref) CreateProcessSettings& settings); /// /// Creates the window. diff --git a/Source/Engine/Platform/CreateProcessSettings.h b/Source/Engine/Platform/CreateProcessSettings.h new file mode 100644 index 000000000..8fa8ae0c0 --- /dev/null +++ b/Source/Engine/Platform/CreateProcessSettings.h @@ -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" + +/// +/// Settings for new process. +/// +API_STRUCT(NoDefault) struct CreateProcessSettings +{ + DECLARE_SCRIPTING_TYPE_MINIMAL(CreateProcessSettings); + + /// + /// The path to the executable file. + /// + API_FIELD() String FileName; + + /// + /// The custom arguments for command line. + /// + API_FIELD() String Arguments; + + /// + /// The custom folder path where start process. Empty if unused. + /// + API_FIELD() String WorkingDirectory; + + /// + /// True if capture process output and print to the log. + /// + API_FIELD() bool LogOutput = true; + + /// + /// True if capture process output and store it as Output text array. + /// + API_FIELD() bool SaveOutput = false; + + /// + /// True if wait for the process execution end. + /// + API_FIELD() bool WaitForEnd = true; + + /// + /// True if hint process to hide window. Supported only on Windows platform. + /// + API_FIELD() bool HiddenWindow = true; + + /// + /// True if use operating system shell to start the process. Supported only on Windows platform. + /// + API_FIELD() bool ShellExecute = false; + + /// + /// Custom environment variables to set for the process. Empty if unused. Additionally newly spawned process inherits this process vars which can be overriden here. + /// + API_FIELD() Dictionary Environment; + + /// + /// Output process contents. + /// + API_FIELD() Array Output; +}; diff --git a/Source/Engine/Platform/Linux/LinuxPlatform.cpp b/Source/Engine/Platform/Linux/LinuxPlatform.cpp index a6160474e..cdd7800f7 100644 --- a/Source/Engine/Platform/Linux/LinuxPlatform.cpp +++ b/Source/Engine/Platform/Linux/LinuxPlatform.cpp @@ -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& 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(), waitForEnd, false); -} - -int32 LinuxPlatform::RunProcess(const StringView& cmdLine, const StringView& workingDir, bool hiddenWindow) -{ - return LinuxProcess(cmdLine, workingDir, Dictionary(), true, true); -} - -int32 LinuxPlatform::RunProcess(const StringView& cmdLine, const StringView& workingDir, const Dictionary& environment, bool hiddenWindow) -{ - return LinuxProcess(cmdLine, workingDir, environment, true, true); -} - void* LinuxPlatform::LoadLibrary(const Char* filename) { const StringAsANSI<> filenameANSI(filename); diff --git a/Source/Engine/Platform/Linux/LinuxPlatform.h b/Source/Engine/Platform/Linux/LinuxPlatform.h index 89afe9292..032ea4c6b 100644 --- a/Source/Engine/Platform/Linux/LinuxPlatform.h +++ b/Source/Engine/Platform/Linux/LinuxPlatform.h @@ -134,9 +134,7 @@ public: static void GetEnvironmentVariables(Dictionary& 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& 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); diff --git a/Source/Engine/Platform/Mac/MacPlatform.cpp b/Source/Engine/Platform/Mac/MacPlatform.cpp index 72d8c6952..c54e80e72 100644 --- a/Source/Engine/Platform/Mac/MacPlatform.cpp +++ b/Source/Engine/Platform/Mac/MacPlatform.cpp @@ -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(settings); } -int32 MacProcess(const StringView& cmdLine, const StringView& workingDir, const Dictionary& 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(), 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(), true, true); -} + // TODO: environment -int32 MacPlatform::RunProcess(const StringView& cmdLine, const StringView& workingDir, const Dictionary& 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 diff --git a/Source/Engine/Platform/Mac/MacPlatform.h b/Source/Engine/Platform/Mac/MacPlatform.h index 98fa48c67..c2098675e 100644 --- a/Source/Engine/Platform/Mac/MacPlatform.h +++ b/Source/Engine/Platform/Mac/MacPlatform.h @@ -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& environment, bool hiddenWindow = true); + static int32 CreateProcess(CreateProcessSettings& settings); }; #endif diff --git a/Source/Engine/Platform/Win32/IncludeWindowsHeaders.h b/Source/Engine/Platform/Win32/IncludeWindowsHeaders.h index c2494c474..3f2b8d36e 100644 --- a/Source/Engine/Platform/Win32/IncludeWindowsHeaders.h +++ b/Source/Engine/Platform/Win32/IncludeWindowsHeaders.h @@ -55,6 +55,7 @@ #undef MessageBox #undef GetCommandLine #undef CreateWindow +#undef CreateProcess #undef SetWindowText #undef DrawText #undef CreateFont diff --git a/Source/Engine/Platform/Windows/WindowsFileSystem.cpp b/Source/Engine/Platform/Windows/WindowsFileSystem.cpp index 04f757bc0..83f042730 100644 --- a/Source/Engine/Platform/Windows/WindowsFileSystem.cpp +++ b/Source/Engine/Platform/Windows/WindowsFileSystem.cpp @@ -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 #include #include +#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 diff --git a/Source/Engine/Platform/Windows/WindowsPlatform.cpp b/Source/Engine/Platform/Windows/WindowsPlatform.cpp index e9a8c5c19..8c99bff21 100644 --- a/Source/Engine/Platform/Windows/WindowsPlatform.cpp +++ b/Source/Engine/Platform/Windows/WindowsPlatform.cpp @@ -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 #include #include +#undef ShellExecute #if CRASH_LOG_ENABLE #include #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(), hiddenWindow); -} - bool IsProcRunning(HANDLE handle) { return WaitForSingleObject(handle, 0) == WAIT_TIMEOUT; } -void ReadPipe(HANDLE pipe, Array& rawData, Array& logData, LogType logType) +void ReadPipe(HANDLE pipe, Array& rawData, Array& 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& rawData, Array& 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& 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(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(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(GetLastError())); - goto ERROR_EXIT; - } - - if (stdOutRead != nullptr) - { - // Keep reading std output and std error streams until process is running - Array rawData; - Array 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 rawData; + Array 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; } diff --git a/Source/Engine/Platform/Windows/WindowsPlatform.h b/Source/Engine/Platform/Windows/WindowsPlatform.h index 7441d0812..cc85a6d1e 100644 --- a/Source/Engine/Platform/Windows/WindowsPlatform.h +++ b/Source/Engine/Platform/Windows/WindowsPlatform.h @@ -77,9 +77,7 @@ public: static void GetEnvironmentVariables(Dictionary& 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& environment, bool hiddenWindow = true); + static int32 CreateProcess(CreateProcessSettings& settings); static Window* CreateWindow(const CreateWindowSettings& settings); static void* LoadLibrary(const Char* filename); static Array GetStackFrames(int32 skipCount = 0, int32 maxDepth = 60, void* context = nullptr);