using System; using System.Diagnostics; using System.Runtime.InteropServices; #if EXE_PROJECT namespace EXE_PROJECT // Use a different namespace so the executable can still use this class' members without an implicit dependency on the BizHawk.Common library, and without resorting to code duplication. #else namespace BizHawk.Common #endif { public static class OSTailoredCode { /// macOS doesn't use PlatformID.MacOSX public static readonly DistinctOS CurrentOS = Environment.OSVersion.Platform == PlatformID.Unix ? SimpleSubshell("uname", "-s", "Can't determine OS") == "Darwin" ? DistinctOS.macOS : DistinctOS.Linux : DistinctOS.Windows; private static readonly Lazy _LinkedLibManager = new Lazy(() => { switch (CurrentOS) { case DistinctOS.Linux: case DistinctOS.macOS: return new UnixMonoLLManager(); case DistinctOS.Windows: return new WindowsLLManager(); default: throw new ArgumentOutOfRangeException(); } }); public static ILinkedLibManager LinkedLibManager => _LinkedLibManager.Value; public interface ILinkedLibManager { IntPtr LoadPlatformSpecific(string dllToLoad); IntPtr GetProcAddr(IntPtr hModule, string procName); int FreePlatformSpecific(IntPtr hModule); } /// This class is copied from a tutorial, so don't git blame and then email me expecting insight. private class UnixMonoLLManager : ILinkedLibManager { private const int RTLD_NOW = 2; [DllImport("libdl.so.2")] private static extern IntPtr dlopen(string fileName, int flags); [DllImport("libdl.so.2")] private static extern IntPtr dlerror(); [DllImport("libdl.so.2")] private static extern IntPtr dlsym(IntPtr handle, string symbol); [DllImport("libdl.so.2")] private static extern int dlclose(IntPtr handle); public IntPtr LoadPlatformSpecific(string dllToLoad) => dlopen(dllToLoad, RTLD_NOW); public IntPtr GetProcAddr(IntPtr hModule, string procName) { dlerror(); var res = dlsym(hModule, procName); var errPtr = dlerror(); if (errPtr != IntPtr.Zero) throw new InvalidOperationException($"error in dlsym: {Marshal.PtrToStringAnsi(errPtr)}"); return res; } public int FreePlatformSpecific(IntPtr hModule) => dlclose(hModule); } private class WindowsLLManager : ILinkedLibManager { // comments reference extern functions removed from SevenZip.NativeMethods [DllImport("kernel32.dll")] private static extern uint GetLastError(); [DllImport("kernel32.dll")] // had BestFitMapping = false, ThrowOnUnmappableChar = true private static extern IntPtr LoadLibrary(string dllToLoad); // param dllToLoad was annotated `[MarshalAs(UnmanagedType.LPStr)]` [DllImport("kernel32.dll")] // had BestFitMapping = false, ThrowOnUnmappableChar = true private static extern IntPtr GetProcAddress(IntPtr hModule, string procName); // param procName was annotated `[MarshalAs(UnmanagedType.LPStr)]` [DllImport("kernel32.dll")] private static extern bool FreeLibrary(IntPtr hModule); // return type was annotated MarshalAs(UnmanagedType.Bool) public IntPtr LoadPlatformSpecific(string dllToLoad) { var p = LoadLibrary(dllToLoad); if (p == IntPtr.Zero) throw new InvalidOperationException($"got null pointer, error code {GetLastError()}"); return p; } public IntPtr GetProcAddr(IntPtr hModule, string procName) => GetProcAddress(hModule, procName); public int FreePlatformSpecific(IntPtr hModule) => FreeLibrary(hModule) ? 1 : 0; } public enum DistinctOS : byte { Linux, macOS, Windows } /// POSIX $0 /// POSIX $* (space-delimited) /// stdout is discarded if false /// stderr is discarded if false /// OS is implicit and needs to be checked at callsite, returned has not been started public static Process ConstructSubshell(string cmd, string args, bool checkStdout = true, bool checkStderr = false) => new Process { StartInfo = new ProcessStartInfo { Arguments = args, CreateNoWindow = true, FileName = cmd, RedirectStandardError = checkStderr, RedirectStandardOutput = checkStdout, UseShellExecute = false } }; /// POSIX $0 /// POSIX $* (space-delimited) /// used in exception /// first line of stdout /// thrown if stdout is empty /// OS is implicit and needs to be checked at callsite public static string SimpleSubshell(string cmd, string args, string noOutputMsg) { using (var proc = ConstructSubshell(cmd, args)) { proc.Start(); var stdout = proc.StandardOutput; if (stdout.EndOfStream) throw new Exception($"{noOutputMsg} ({cmd} wrote nothing to stdout)"); return stdout.ReadLine(); } } } }