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-08-12 10:53:55 +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-08-12 10:53:55 +00:00
private static readonly Lazy < ILinkedLibManager > _LinkedLibManager = new Lazy < ILinkedLibManager > ( ( ) = >
2019-05-18 05:30:29 +00:00
{
switch ( CurrentOS )
{
case DistinctOS . Linux :
case DistinctOS . macOS :
2019-05-18 05:38:51 +00:00
return new UnixMonoLLManager ( ) ;
2019-05-18 05:30:29 +00:00
case DistinctOS . Windows :
2019-05-18 05:38:51 +00:00
return new WindowsLLManager ( ) ;
2019-05-18 05:30:29 +00:00
default :
throw new ArgumentOutOfRangeException ( ) ;
}
} ) ;
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-10-13 05:23:14 +00:00
IntPtr ? LoadOrNull ( string dllToLoad ) ;
IntPtr LoadOrThrow ( string dllToLoad ) ;
IntPtr GetProcAddr ( IntPtr hModule , string procName ) ; //TODO also split into nullable and throwing?
int FreeByPtr ( IntPtr hModule ) ;
2019-01-03 22:50:55 +00:00
}
2019-05-18 05:38:51 +00:00
/// <remarks>This class is copied from a tutorial, so don't git blame and then email me expecting insight.</remarks>
private class UnixMonoLLManager : ILinkedLibManager
2019-01-03 22:50:55 +00:00
{
2019-05-18 05:38:51 +00:00
private const int RTLD_NOW = 2 ;
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-01-03 22:50:55 +00:00
[DllImport("libdl.so.2")]
private static extern IntPtr dlerror ( ) ;
[DllImport("libdl.so.2")]
2019-10-13 05:23:14 +00:00
private static extern IntPtr dlopen ( string fileName , int flags ) ;
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-01-03 22:50:55 +00:00
public IntPtr GetProcAddr ( IntPtr hModule , string procName )
{
dlerror ( ) ;
var res = dlsym ( hModule , procName ) ;
var errPtr = dlerror ( ) ;
2019-10-13 05:23:14 +00:00
if ( errPtr ! = IntPtr . Zero ) throw new InvalidOperationException ( $"error in {nameof(dlsym)}: {Marshal.PtrToStringAnsi(errPtr)}" ) ;
2019-01-03 22:50:55 +00:00
return res ;
}
2019-10-13 05:23:14 +00:00
public int FreeByPtr ( IntPtr hModule ) = > dlclose ( hModule ) ;
public IntPtr ? LoadOrNull ( string dllToLoad )
{
var p = dlopen ( dllToLoad , RTLD_NOW ) ;
return p = = IntPtr . Zero ? default ( IntPtr ? ) : p ;
}
public IntPtr LoadOrThrow ( string dllToLoad )
{
var p = LoadOrNull ( dllToLoad ) ;
if ( ! p . HasValue ) throw new InvalidOperationException ( $"got null pointer from {nameof(dlopen)}, error: {Marshal.PtrToStringAnsi(dlerror())}" ) ;
return p . Value ;
}
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-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)
[DllImport("kernel32.dll")]
2019-05-18 05:38:51 +00:00
private static extern uint GetLastError ( ) ;
2019-08-12 10:53:55 +00:00
[DllImport("kernel32.dll")] // had BestFitMapping = false, ThrowOnUnmappableChar = true
private static extern IntPtr GetProcAddress ( IntPtr hModule , string procName ) ; // param procName was annotated `[MarshalAs(UnmanagedType.LPStr)]`
2019-10-13 05:23:14 +00:00
[DllImport("kernel32.dll")] // had BestFitMapping = false, ThrowOnUnmappableChar = true
private static extern IntPtr LoadLibrary ( string dllToLoad ) ; // param dllToLoad was annotated `[MarshalAs(UnmanagedType.LPStr)]`
2019-08-12 10:53:55 +00:00
public IntPtr GetProcAddr ( IntPtr hModule , string procName ) = > GetProcAddress ( hModule , procName ) ;
2019-10-13 05:23:14 +00:00
public int FreeByPtr ( IntPtr hModule ) = > FreeLibrary ( hModule ) ? 1 : 0 ;
public IntPtr ? LoadOrNull ( string dllToLoad )
{
var p = LoadLibrary ( dllToLoad ) ;
return p = = IntPtr . Zero ? default ( IntPtr ? ) : p ;
}
public IntPtr LoadOrThrow ( string dllToLoad )
{
var p = LoadOrNull ( dllToLoad ) ;
if ( ! p . HasValue ) throw new InvalidOperationException ( $"got null pointer from {nameof(LoadLibrary)}, error code: {GetLastError()}" ) ;
return p . Value ;
}
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 )
{
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-01-03 22:50:55 +00:00
}
2019-11-03 13:11:29 +00:00
}