You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Zero/ZeroLevel/Services/Windows/WindowsLibraryLoader.cs

287 lines
11 KiB

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
namespace ZeroLevel.Services.Windows
{
public sealed class WindowsLibraryLoader
{
#region Fields
private const string ProcessorArchitecture = "PROCESSOR_ARCHITECTURE";
private const string DllFileExtension = ".dll";
private const string DllDirectory = "dll";
private readonly Dictionary<string, int> _ProcessorArchitectureAddressWidthPlatforms = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
{
{"x86", 4},
{"AMD64", 8},
{"IA64", 8},
{"ARM", 4}
};
private readonly Dictionary<string, string> _ProcessorArchitecturePlatforms = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{"x86", "x86"},
{"AMD64", "x64"},
{"IA64", "Itanium"},
{"ARM", "WinCE"}
};
private readonly object _SyncLock = new object();
private static readonly IDictionary<string, IntPtr> LoadedLibraries = new Dictionary<string, IntPtr>();
[System.Runtime.InteropServices.DllImport("kernel32", EntryPoint = "LoadLibrary", CallingConvention = System.Runtime.InteropServices.CallingConvention.Winapi, SetLastError = true, BestFitMapping = false, ThrowOnUnmappableChar = true)]
private static extern IntPtr Win32LoadLibrary(string dllPath);
#endregion
#region Properties
public static bool IsCurrentPlatformSupported()
{
return RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows);
}
public static bool IsWindows()
{
return Environment.OSVersion.Platform == PlatformID.Win32NT ||
Environment.OSVersion.Platform == PlatformID.Win32S ||
Environment.OSVersion.Platform == PlatformID.Win32Windows ||
Environment.OSVersion.Platform == PlatformID.WinCE;
}
#endregion
#region Methods
#region Helpers
private static string FixUpDllFileName(string fileName)
{
if (string.IsNullOrEmpty(fileName))
return fileName;
if (!System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return fileName;
if (!fileName.EndsWith(DllFileExtension, StringComparison.OrdinalIgnoreCase))
return $"{fileName}{DllFileExtension}";
return fileName;
}
private ProcessArchitectureInfo GetProcessArchitecture()
{
// BUG: Will this always be reliable?
var processArchitecture = Environment.GetEnvironmentVariable(ProcessorArchitecture);
var processInfo = new ProcessArchitectureInfo();
if (!string.IsNullOrEmpty(processArchitecture))
{
// Sanity check
processInfo.Architecture = processArchitecture;
}
else
{
processInfo.AddWarning("Failed to detect processor architecture, falling back to x86.");
processInfo.Architecture = (IntPtr.Size == 8) ? "x64" : "x86";
}
var addressWidth = this._ProcessorArchitectureAddressWidthPlatforms[processInfo.Architecture];
if (addressWidth != IntPtr.Size)
{
if (String.Equals(processInfo.Architecture, "AMD64", StringComparison.OrdinalIgnoreCase) && IntPtr.Size == 4)
{
// fall back to x86 if detected x64 but has an address width of 32 bits.
processInfo.Architecture = "x86";
processInfo.AddWarning("Expected the detected processing architecture of {0} to have an address width of {1} Bytes but was {2} Bytes, falling back to x86.", processInfo.Architecture, addressWidth, IntPtr.Size);
}
else
{
// no fallback possible
processInfo.AddWarning("Expected the detected processing architecture of {0} to have an address width of {1} Bytes but was {2} Bytes.", processInfo.Architecture, addressWidth, IntPtr.Size);
}
}
return processInfo;
}
private string GetPlatformName(string processorArchitecture)
{
if (String.IsNullOrEmpty(processorArchitecture))
return null;
string platformName;
if (this._ProcessorArchitecturePlatforms.TryGetValue(processorArchitecture, out platformName))
{
return platformName;
}
return null;
}
public void LoadLibraries(IEnumerable<string> dlls)
{
if (!IsWindows())
return;
foreach (var dll in dlls)
LoadLibrary(dll);
}
private void LoadLibrary(string dllName)
{
if (!IsCurrentPlatformSupported())
return;
try
{
lock (this._SyncLock)
{
if (LoadedLibraries.ContainsKey(dllName))
return;
var processArch = GetProcessArchitecture();
IntPtr dllHandle;
// Try loading from executing assembly domain
var executingAssembly = GetType().GetTypeInfo().Assembly;
var baseDirectory = Path.GetDirectoryName(executingAssembly.Location);
dllHandle = LoadLibraryInternal(dllName, baseDirectory, processArch);
if (dllHandle != IntPtr.Zero) return;
// Gets the pathname of the base directory that the assembly resolver uses to probe for assemblies.
// https://github.com/dotnet/corefx/issues/2221
baseDirectory = AppContext.BaseDirectory;
dllHandle = LoadLibraryInternal(dllName, baseDirectory, processArch);
if (dllHandle != IntPtr.Zero) return;
// Finally try the working directory
baseDirectory = Path.GetFullPath(Directory.GetCurrentDirectory());
dllHandle = LoadLibraryInternal(dllName, baseDirectory, processArch);
if (dllHandle != IntPtr.Zero) return;
var errorMessage = new StringBuilder();
errorMessage.Append($"Failed to find dll \"{dllName}\", for processor architecture {processArch.Architecture}.");
if (processArch.HasWarnings)
{
// include process detection warnings
errorMessage.Append($"\r\nWarnings: \r\n{processArch.WarningText()}");
}
throw new Exception(errorMessage.ToString());
}
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine(e.Message);
}
}
private IntPtr LoadLibraryInternal(string dllName, string baseDirectory, ProcessArchitectureInfo processArchInfo)
{
var platformName = GetPlatformName(processArchInfo.Architecture);
var expectedDllDirectory = Path.Combine(
Path.Combine(baseDirectory, DllDirectory), platformName);
return this.LoadLibraryRaw(dllName, expectedDllDirectory);
}
private IntPtr LoadLibraryRaw(string dllName, string baseDirectory)
{
var libraryHandle = IntPtr.Zero;
var fileName = FixUpDllFileName(Path.Combine(baseDirectory, dllName));
// Show where we're trying to load the file from
Debug.WriteLine($"Trying to load native library \"{fileName}\"...");
if (File.Exists(fileName))
{
// Attempt to load dll
try
{
libraryHandle = Win32LoadLibrary(fileName);
if (libraryHandle != IntPtr.Zero)
{
// library has been loaded
Debug.WriteLine($"Successfully loaded native library \"{fileName}\".");
LoadedLibraries.Add(dllName, libraryHandle);
}
else
{
Debug.WriteLine($"Failed to load native library \"{fileName}\".\r\nCheck windows event log.");
}
}
catch (Exception e)
{
var lastError = Marshal.GetLastWin32Error();
Debug.WriteLine($"Failed to load native library \"{fileName}\".\r\nLast Error:{lastError}\r\nCheck inner exception and\\or windows event log.\r\nInner Exception: {e}");
}
}
else
{
Debug.WriteLine(string.Format(System.Globalization.CultureInfo.CurrentCulture, "The native library \"{0}\" does not exist.", fileName));
}
return libraryHandle;
}
#endregion
#endregion
private class ProcessArchitectureInfo
{
#region Constructors
public ProcessArchitectureInfo()
{
this.Warnings = new List<string>();
}
#endregion
#region Properties
public string Architecture
{
get; set;
}
private List<string> Warnings
{
get;
}
#endregion
#region Methods
public void AddWarning(string format, params object[] args)
{
Warnings.Add(String.Format(format, args));
}
public bool HasWarnings => Warnings.Count > 0;
public string WarningText()
{
return string.Join("\r\n", Warnings.ToArray());
}
#endregion
}
}
}

Powered by TurnKey Linux.