2019-01-03 22:50:55 +00:00
using System ;
2019-05-18 05:30:29 +00:00
using System.Diagnostics ;
using System.Runtime.InteropServices ;
2019-01-04 22:41:59 +00:00
#if EXE_PROJECT
2019-08-12 10:53:55 +00:00
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.
2019-01-04 22:41:59 +00:00
#else
2019-01-03 22:50:55 +00:00
namespace BizHawk.Common
2019-01-04 22:41:59 +00:00
#endif
2019-05-18 05:30:29 +00:00
{
2019-08-12 10:53:55 +00:00
public static class OSTailoredCode
2019-01-03 22:50:55 +00:00
{
2019-12-19 04:16:42 +00:00
/// <remarks>macOS doesn't use <see cref="PlatformID.MacOSX">PlatformID.MacOSX</see></remarks>
2019-05-18 05:30:29 +00:00
public static readonly DistinctOS CurrentOS = Environment . OSVersion . Platform = = PlatformID . Unix
2019-08-12 10:53:55 +00:00
? SimpleSubshell ( "uname" , "-s" , "Can't determine OS" ) = = "Darwin" ? DistinctOS . macOS : DistinctOS . Linux
2019-05-18 05:30:29 +00:00
: DistinctOS . Windows ;
2019-11-04 04:30:05 +00:00
public static readonly bool IsUnixHost = CurrentOS ! = DistinctOS . Windows ;
2019-11-03 22:04:42 +00:00
2019-12-19 04:16:42 +00:00
private static readonly Lazy < ILinkedLibManager > _LinkedLibManager = new Lazy < ILinkedLibManager > ( ( ) = > CurrentOS switch
2019-05-18 05:30:29 +00:00
{
2019-12-19 04:16:42 +00:00
DistinctOS . Linux = > ( ILinkedLibManager ) new UnixMonoLLManager ( ) ,
DistinctOS . macOS = > new UnixMonoLLManager ( ) ,
DistinctOS . Windows = > new WindowsLLManager ( ) ,
_ = > throw new ArgumentOutOfRangeException ( )
2019-05-18 05:30:29 +00:00
} ) ;
2019-01-03 22:50:55 +00:00
2019-08-12 10:53:55 +00:00
public static ILinkedLibManager LinkedLibManager = > _LinkedLibManager . Value ;
2019-01-03 22:50:55 +00:00
2019-10-13 05:23:14 +00:00
/// <remarks>this interface's inheritors hide OS-specific implementation details</remarks>
2019-05-18 05:38:51 +00:00
public interface ILinkedLibManager
2019-01-03 22:50:55 +00:00
{
2019-12-19 04:16:42 +00:00
int FreeByPtr ( IntPtr hModule ) ;
IntPtr ? GetProcAddrOrNull ( IntPtr hModule , string procName ) ;
IntPtr GetProcAddrOrThrow ( IntPtr hModule , string procName ) ;
2019-10-13 05:23:14 +00:00
IntPtr ? LoadOrNull ( string dllToLoad ) ;
IntPtr LoadOrThrow ( string dllToLoad ) ;
2019-01-03 22:50:55 +00:00
}
2019-05-18 05:38:51 +00:00
private class UnixMonoLLManager : ILinkedLibManager
2019-01-03 22:50:55 +00:00
{
[DllImport("libdl.so.2")]
2019-10-13 05:23:14 +00:00
private static extern int dlclose ( IntPtr handle ) ;
2019-12-19 04:16:42 +00:00
2019-01-03 22:50:55 +00:00
[DllImport("libdl.so.2")]
private static extern IntPtr dlerror ( ) ;
2019-12-19 04:16:42 +00:00
2019-01-03 22:50:55 +00:00
[DllImport("libdl.so.2")]
2019-10-13 05:23:14 +00:00
private static extern IntPtr dlopen ( string fileName , int flags ) ;
2019-12-19 04:16:42 +00:00
2019-01-03 22:50:55 +00:00
[DllImport("libdl.so.2")]
2019-10-13 05:23:14 +00:00
private static extern IntPtr dlsym ( IntPtr handle , string symbol ) ;
2019-08-12 10:53:55 +00:00
2019-12-19 04:16:42 +00:00
public int FreeByPtr ( IntPtr hModule ) = > dlclose ( hModule ) ;
public IntPtr ? GetProcAddrOrNull ( IntPtr hModule , string procName )
2019-01-03 22:50:55 +00:00
{
2019-12-19 04:16:42 +00:00
var p = dlsym ( hModule , procName ) ;
return p = = IntPtr . Zero ? default : p ;
2019-01-03 22:50:55 +00:00
}
2019-10-13 05:23:14 +00:00
2019-12-19 04:16:42 +00:00
public IntPtr GetProcAddrOrThrow ( IntPtr hModule , string procName )
{
_ = dlerror ( ) ; // the Internet said to do this
var p = GetProcAddrOrNull ( hModule , procName ) ;
if ( p ! = null ) return p . Value ;
var errCharPtr = dlerror ( ) ;
throw new InvalidOperationException ( $"error in {nameof(dlsym)}{(errCharPtr == IntPtr.Zero ? string.Empty : $" : { Marshal . PtrToStringAnsi ( errCharPtr ) } ")}" ) ;
}
2019-10-13 05:23:14 +00:00
public IntPtr ? LoadOrNull ( string dllToLoad )
{
2019-12-19 04:16:42 +00:00
const int RTLD_NOW = 2 ;
2019-10-13 05:23:14 +00:00
var p = dlopen ( dllToLoad , RTLD_NOW ) ;
2019-12-19 04:16:42 +00:00
return p = = IntPtr . Zero ? default : p ;
2019-10-13 05:23:14 +00:00
}
2019-12-19 04:16:42 +00:00
public IntPtr LoadOrThrow ( string dllToLoad ) = > LoadOrNull ( dllToLoad ) ? ? throw new InvalidOperationException ( $"got null pointer from {nameof(dlopen)}, error: {Marshal.PtrToStringAnsi(dlerror())}" ) ;
2019-01-03 22:50:55 +00:00
}
2019-05-18 05:38:51 +00:00
private class WindowsLLManager : ILinkedLibManager
2019-01-03 22:50:55 +00:00
{
2019-08-12 10:53:55 +00:00
// comments reference extern functions removed from SevenZip.NativeMethods
2019-12-19 04:16:42 +00:00
2019-01-03 22:50:55 +00:00
[DllImport("kernel32.dll")]
2019-10-13 05:23:14 +00:00
private static extern bool FreeLibrary ( IntPtr hModule ) ; // return type was annotated MarshalAs(UnmanagedType.Bool)
2019-12-19 04:16:42 +00:00
2019-10-13 05:23:14 +00:00
[DllImport("kernel32.dll")]
2019-05-18 05:38:51 +00:00
private static extern uint GetLastError ( ) ;
2019-12-19 04:16:42 +00:00
[DllImport("kernel32.dll", SetLastError = true)] // had BestFitMapping = false, ThrowOnUnmappableChar = true
2019-08-12 10:53:55 +00:00
private static extern IntPtr GetProcAddress ( IntPtr hModule , string procName ) ; // param procName was annotated `[MarshalAs(UnmanagedType.LPStr)]`
2019-12-19 04:16:42 +00:00
[DllImport("kernel32.dll", SetLastError = true)] // had BestFitMapping = false, ThrowOnUnmappableChar = true
2019-10-13 05:23:14 +00:00
private static extern IntPtr LoadLibrary ( string dllToLoad ) ; // param dllToLoad was annotated `[MarshalAs(UnmanagedType.LPStr)]`
2019-08-12 10:53:55 +00:00
2019-12-19 04:16:42 +00:00
public int FreeByPtr ( IntPtr hModule ) = > FreeLibrary ( hModule ) ? 0 : 1 ;
2019-10-13 05:23:14 +00:00
2019-12-19 04:16:42 +00:00
public IntPtr ? GetProcAddrOrNull ( IntPtr hModule , string procName )
{
var p = GetProcAddress ( hModule , procName ) ;
return p = = IntPtr . Zero ? default : p ;
}
public IntPtr GetProcAddrOrThrow ( IntPtr hModule , string procName ) = > GetProcAddrOrNull ( hModule , procName ) ? ? throw new InvalidOperationException ( $"got null pointer from {nameof(GetProcAddress)}, error code: {GetLastError()}" ) ;
2019-10-13 05:23:14 +00:00
public IntPtr ? LoadOrNull ( string dllToLoad )
{
var p = LoadLibrary ( dllToLoad ) ;
2019-12-19 04:16:42 +00:00
return p = = IntPtr . Zero ? default : p ;
2019-10-13 05:23:14 +00:00
}
2019-12-19 04:16:42 +00:00
public IntPtr LoadOrThrow ( string dllToLoad ) = > LoadOrNull ( dllToLoad ) ? ? throw new InvalidOperationException ( $"got null pointer from {nameof(LoadLibrary)}, error code: {GetLastError()}" ) ;
2019-01-03 22:50:55 +00:00
}
2019-05-18 05:30:29 +00:00
public enum DistinctOS : byte
{
2019-05-18 05:38:51 +00:00
Linux ,
macOS ,
Windows
2019-05-18 05:30:29 +00:00
}
2019-08-12 10:00:42 +00:00
/// <param name="cmd">POSIX <c>$0</c></param>
/// <param name="args">POSIX <c>$*</c> (space-delimited)</param>
/// <param name="checkStdout">stdout is discarded if false</param>
/// <param name="checkStderr">stderr is discarded if false</param>
2019-10-13 05:23:14 +00:00
/// <remarks>OS is implicit and needs to be checked at callsite. Returned <see cref="Process"/> has not been started.</remarks>
2019-08-12 10:00:42 +00:00
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 ,
2019-11-03 13:11:29 +00:00
RedirectStandardInput = true ,
2019-08-12 10:00:42 +00:00
RedirectStandardOutput = checkStdout ,
UseShellExecute = false
}
} ;
/// <param name="cmd">POSIX <c>$0</c></param>
/// <param name="args">POSIX <c>$*</c> (space-delimited)</param>
/// <param name="noOutputMsg">used in exception</param>
/// <returns>first line of stdout</returns>
/// <exception cref="Exception">thrown if stdout is empty</exception>
/// <remarks>OS is implicit and needs to be checked at callsite</remarks>
public static string SimpleSubshell ( string cmd , string args , string noOutputMsg )
{
2019-11-03 22:04:42 +00:00
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 ( ) ;
2019-08-12 10:00:42 +00:00
}
2019-01-03 22:50:55 +00:00
}
2019-11-03 13:11:29 +00:00
}