Files
libremetaverse/LibreMetaverse/WindowsLibraryLoader.cs
2021-07-24 12:56:24 -05:00

308 lines
11 KiB
C#

/*
* 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<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>();
[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<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)
{
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<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
}
}
}