using System; using System.Diagnostics; using System.Runtime.InteropServices; //put in a different namespace for EXE so we can have an instance of this type (by linking to this file rather than copying it) built-in to the exe //so the exe doesnt implicitly depend on the dll #if EXE_PROJECT namespace EXE_PROJECT #else namespace BizHawk.Common #endif { public sealed class OSTailoredCode { /// macOS doesn't use PlatformID.MacOSX public static readonly DistinctOS CurrentOS = Environment.OSVersion.Platform == PlatformID.Unix ? currentIsMacOS() ? DistinctOS.macOS : DistinctOS.Linux : DistinctOS.Windows; private static readonly Lazy lazy = 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 => lazy.Value; private static bool currentIsMacOS() { var proc = new Process { StartInfo = new ProcessStartInfo { Arguments = "-s", CreateNoWindow = true, FileName = "uname", RedirectStandardOutput = true, UseShellExecute = false } }; proc.Start(); if (proc.StandardOutput.EndOfStream) throw new Exception("Can't determine OS (uname wrote nothing to stdout)!"); return proc.StandardOutput.ReadLine() == "Darwin"; } private OSTailoredCode() {} 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) { return 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) { return dlclose(hModule); } } private class WindowsLLManager : ILinkedLibManager { [DllImport("kernel32.dll")] private static extern uint GetLastError(); // was annotated `[DllImport("kernel32.dll", BestFitMapping = false, ThrowOnUnmappableChar = true)]` in SevenZip.NativeMethods // param dllToLoad was annotated `[MarshalAs(UnmanagedType.LPStr)]` in SevenZip.NativeMethods [DllImport("kernel32.dll")] private static extern IntPtr LoadLibrary(string dllToLoad); // was annotated `[DllImport("kernel32.dll", BestFitMapping = false, ThrowOnUnmappableChar = true)]` in SevenZip.NativeMethods // param procName was annotated `[MarshalAs(UnmanagedType.LPStr)]` in SevenZip.NativeMethods [DllImport("kernel32.dll")] private static extern IntPtr GetProcAddress(IntPtr hModule, string procName); // was annotated `[return: MarshalAs(UnmanagedType.Bool)]` in SevenZip.NativeMethods [DllImport("kernel32.dll")] private static extern bool FreeLibrary(IntPtr hModule); 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) { return GetProcAddress(hModule, procName); } public int FreePlatformSpecific(IntPtr hModule) { return FreeLibrary(hModule) ? 1 : 0; } } public enum DistinctOS : byte { Linux, macOS, Windows } } }