/* * Copyright (c) 2021, Sjofn LLC. * All rights reserved. * * - Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * - Neither the name of the openmetaverse.co nor the names * of its contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.InteropServices; using System.Text; namespace LibreMetaverse { internal sealed class WindowsLibraryLoader { #region Fields private const string ProcessorArchitecture = "PROCESSOR_ARCHITECTURE"; private const string DllFileExtension = ".dll"; private const string DllDirectory = "dll"; private readonly Dictionary _ProcessorArchitectureAddressWidthPlatforms = new Dictionary(StringComparer.OrdinalIgnoreCase) { {"x86", 4}, {"AMD64", 8}, {"IA64", 8}, {"ARM", 4} }; private readonly Dictionary _ProcessorArchitecturePlatforms = new Dictionary(StringComparer.OrdinalIgnoreCase) { {"x86", "x86"}, {"AMD64", "x64"}, {"IA64", "Itanium"}, {"ARM", "WinCE"} }; private readonly object _SyncLock = new object(); private static readonly IDictionary LoadedLibraries = new Dictionary(); [DllImport("kernel32", EntryPoint = "LoadLibrary", CallingConvention = 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(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 (!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 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) { 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(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(); } #endregion #region Properties public string Architecture { get; set; } private List 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 } } }