Refactor platform process startup with CreateProcessSettings
This commit is contained in:
@@ -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>();
|
||||
|
||||
@@ -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.
|
||||
|
||||
65
Source/Engine/Platform/CreateProcessSettings.h
Normal file
65
Source/Engine/Platform/CreateProcessSettings.h
Normal 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;
|
||||
};
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
#undef MessageBox
|
||||
#undef GetCommandLine
|
||||
#undef CreateWindow
|
||||
#undef CreateProcess
|
||||
#undef SetWindowText
|
||||
#undef DrawText
|
||||
#undef CreateFont
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user