|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|