aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2024-03-22 12:01:53 -0400
committerLibravatar vnugent <public@vaughnnugent.com>2024-03-22 12:01:53 -0400
commit6c1667be23597513537f8190e2f55d65eb9b7c7a (patch)
tree380fe100af2b29246398237bfe95f392dc513ffe
parentebf688f2f974295beabf7b5def7e6f6f150551d0 (diff)
refactor: Overhauled native library loading and lazy init
-rw-r--r--lib/Hashing.Portable/src/Argon2/VnArgon2.cs14
-rw-r--r--lib/Hashing.Portable/src/Checksums/FNV1a.cs4
-rw-r--r--lib/Hashing.Portable/src/Native/MonoCypher/MonoCypherLibrary.cs8
-rw-r--r--lib/Net.Compression/vnlib_compress/vnlib_compress.vcxitems1
-rw-r--r--lib/Utils/src/Memory/MemoryUtil.cs37
-rw-r--r--lib/Utils/src/Native/NatveLibraryResolver.cs299
-rw-r--r--lib/Utils/src/Native/SafeLibraryHandle.cs260
-rw-r--r--lib/Utils/src/Resources/LazyInitializer.cs112
8 files changed, 550 insertions, 185 deletions
diff --git a/lib/Hashing.Portable/src/Argon2/VnArgon2.cs b/lib/Hashing.Portable/src/Argon2/VnArgon2.cs
index 9d98050..b88c232 100644
--- a/lib/Hashing.Portable/src/Argon2/VnArgon2.cs
+++ b/lib/Hashing.Portable/src/Argon2/VnArgon2.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2023 Vaughn Nugent
+* Copyright (c) 2024 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Hashing.Portable
@@ -24,7 +24,6 @@
using System;
using System.Text;
-using System.Threading;
using System.Diagnostics;
using System.Buffers.Text;
using System.Security.Cryptography;
@@ -33,6 +32,7 @@ using System.Runtime.InteropServices;
using VNLib.Utils.Memory;
using VNLib.Utils.Native;
using VNLib.Utils.Extensions;
+using VNLib.Utils.Resources;
using VNLib.Hashing.Native.MonoCypher;
namespace VNLib.Hashing
@@ -52,12 +52,12 @@ namespace VNLib.Hashing
public const string ARGON2_LIB_ENVIRONMENT_VAR_NAME = "VNLIB_ARGON2_DLL_PATH";
private static readonly Encoding LocEncoding = Encoding.Unicode;
- private static readonly Lazy<IUnmangedHeap> _heap = new (static () => MemoryUtil.InitializeNewHeapForProcess(true), LazyThreadSafetyMode.PublicationOnly);
- private static readonly Lazy<IArgon2Library> _nativeLibrary = new(LoadSharedLibInternal, LazyThreadSafetyMode.PublicationOnly);
+ private static readonly LazyInitializer<IUnmangedHeap> _heap = new (static () => MemoryUtil.InitializeNewHeapForProcess(true));
+ private static readonly LazyInitializer<IArgon2Library> _nativeLibrary = new(LoadSharedLibInternal);
//Private heap initialized to 10k size, and all allocated buffers will be zeroed when allocated
- private static IUnmangedHeap PwHeap => _heap.Value;
+ private static IUnmangedHeap PwHeap => _heap.Instance;
private static IArgon2Library LoadSharedLibInternal()
{
@@ -70,7 +70,7 @@ namespace VNLib.Hashing
Trace.WriteLine("Using the native MonoCypher library for Argon2 password hashing", "VnArgon2");
//Load shared monocyphter argon2 library
- return MonoCypherLibrary.Shared.Argon2CreateLibrary(_heap.Value);
+ return MonoCypherLibrary.Shared.Argon2CreateLibrary(_heap.Instance);
}
else
{
@@ -90,7 +90,7 @@ namespace VNLib.Hashing
/// </summary>
/// <returns>The shared library instance</returns>
/// <exception cref="DllNotFoundException"></exception>
- public static IArgon2Library GetOrLoadSharedLib() => _nativeLibrary.Value;
+ public static IArgon2Library GetOrLoadSharedLib() => _nativeLibrary.Instance;
/// <summary>
/// Loads a native Argon2 shared library from the specified path
diff --git a/lib/Hashing.Portable/src/Checksums/FNV1a.cs b/lib/Hashing.Portable/src/Checksums/FNV1a.cs
index 5bac86f..f150638 100644
--- a/lib/Hashing.Portable/src/Checksums/FNV1a.cs
+++ b/lib/Hashing.Portable/src/Checksums/FNV1a.cs
@@ -95,8 +95,8 @@ namespace VNLib.Hashing.Checksums
public static ulong Compute64(ref byte data, nuint length) => Update64(FNV_OFFSET_BASIS, ref data, length);
/// <summary>
- /// Provides a managed software implementation of the FNV-1a 64-bit
- /// non cryptographic hash algorithm
+ /// Computes the next 64-bit FNV-1a hash value using the current hash
+ /// value and the next byte of data.
/// </summary>
/// <param name="data">A span structure pointing to the memory block to compute the digest of</param>
/// <returns>The 64bit unsigned integer representng the message sum or digest</returns>
diff --git a/lib/Hashing.Portable/src/Native/MonoCypher/MonoCypherLibrary.cs b/lib/Hashing.Portable/src/Native/MonoCypher/MonoCypherLibrary.cs
index 11e524b..4c6f405 100644
--- a/lib/Hashing.Portable/src/Native/MonoCypher/MonoCypherLibrary.cs
+++ b/lib/Hashing.Portable/src/Native/MonoCypher/MonoCypherLibrary.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2023 Vaughn Nugent
+* Copyright (c) 2024 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Hashing.Portable
@@ -25,10 +25,10 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
-using System.Threading;
using VNLib.Utils;
using VNLib.Utils.Native;
+using VNLib.Utils.Resources;
using VNLib.Utils.Extensions;
namespace VNLib.Hashing.Native.MonoCypher
@@ -50,7 +50,7 @@ namespace VNLib.Hashing.Native.MonoCypher
/// <returns>true if the user enabled the default library, false otherwise</returns>
public static bool CanLoadDefaultLibrary() => string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable(MONOCYPHER_LIB_ENVIRONMENT_VAR_NAME)) == false;
- private static readonly Lazy<MonoCypherLibrary> _defaultLib = new (LoadDefaultLibraryInternal, LazyThreadSafetyMode.PublicationOnly);
+ private static readonly LazyInitializer<MonoCypherLibrary> _defaultLib = new (LoadDefaultLibraryInternal);
/// <summary>
/// Gets the default MonoCypher library for the current process
@@ -59,7 +59,7 @@ namespace VNLib.Hashing.Native.MonoCypher
/// this property to ensure that the default library can be loaded
/// </para>
/// </summary>
- public static MonoCypherLibrary Shared => _defaultLib.Value;
+ public static MonoCypherLibrary Shared => _defaultLib.Instance;
/// <summary>
/// Loads a new instance of the MonoCypher library with environment defaults
diff --git a/lib/Net.Compression/vnlib_compress/vnlib_compress.vcxitems b/lib/Net.Compression/vnlib_compress/vnlib_compress.vcxitems
index c9664ca..0bfdcbf 100644
--- a/lib/Net.Compression/vnlib_compress/vnlib_compress.vcxitems
+++ b/lib/Net.Compression/vnlib_compress/vnlib_compress.vcxitems
@@ -21,7 +21,6 @@
<ItemGroup>
<ClInclude Include="$(MSBuildThisFileDirectory)feature_brotli.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)compression.h" />
- <ClInclude Include="$(MSBuildThisFileDirectory)memory.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)util.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)feature_zlib.h" />
</ItemGroup>
diff --git a/lib/Utils/src/Memory/MemoryUtil.cs b/lib/Utils/src/Memory/MemoryUtil.cs
index 6efd5ba..b6eadbc 100644
--- a/lib/Utils/src/Memory/MemoryUtil.cs
+++ b/lib/Utils/src/Memory/MemoryUtil.cs
@@ -25,7 +25,6 @@
using System;
using System.Buffers;
using System.Security;
-using System.Threading;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.InteropServices;
@@ -33,6 +32,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics.X86;
using VNLib.Utils.Memory.Diagnostics;
+using VNLib.Utils.Resources;
namespace VNLib.Utils.Memory
{
@@ -102,13 +102,13 @@ namespace VNLib.Utils.Memory
/// The backing heap
/// is determined by the OS type and process environment varibles.
/// </remarks>
- public static IUnmangedHeap Shared => _sharedHeap.Value;
+ public static IUnmangedHeap Shared => _lazyHeap.Instance;
-
- private static readonly Lazy<IUnmangedHeap> _sharedHeap = InitHeapInternal();
+
+ private static readonly LazyInitializer<IUnmangedHeap> _lazyHeap = InitHeapInternal();
//Avoiding static initializer
- private static Lazy<IUnmangedHeap> InitHeapInternal()
+ private static LazyInitializer<IUnmangedHeap> InitHeapInternal()
{
//Get env for heap diag
_ = ERRNO.TryParse(Environment.GetEnvironmentVariable(SHARED_HEAP_ENABLE_DIAGNOISTICS_ENV), out ERRNO diagEnable);
@@ -116,22 +116,17 @@ namespace VNLib.Utils.Memory
Trace.WriteLineIf(diagEnable, "Shared heap diagnostics enabled");
Trace.WriteLineIf(globalZero, "Shared heap global zero enabled");
-
- Lazy<IUnmangedHeap> heap = new (() => InitHeapInternal(true, diagEnable, globalZero), LazyThreadSafetyMode.PublicationOnly);
-
- //Cleanup the heap on process exit
- AppDomain.CurrentDomain.DomainUnload += DomainUnloaded;
-
- return heap;
- }
- private static void DomainUnloaded(object? sender, EventArgs e)
- {
- //Dispose the heap if allocated
- if (_sharedHeap.IsValueCreated)
+ return new(() =>
{
- _sharedHeap.Value.Dispose();
- }
+ //Init shared heap instance
+ IUnmangedHeap heap = InitHeapInternal(true, diagEnable, globalZero);
+
+ //Register domain unload event
+ AppDomain.CurrentDomain.DomainUnload += (_, _) => heap.Dispose();
+
+ return heap;
+ });
}
/// <summary>
@@ -147,7 +142,7 @@ namespace VNLib.Utils.Memory
* If heap is allocated and the heap type is a tracked heap,
* get the heap's stats, otherwise return an empty handle
*/
- return _sharedHeap.IsValueCreated && _sharedHeap.Value is TrackedHeapWrapper h
+ return _lazyHeap.IsLoaded && _lazyHeap.Instance is TrackedHeapWrapper h
? h.GetCurrentStats() : default;
}
@@ -1610,4 +1605,4 @@ namespace VNLib.Utils.Memory
public readonly void Validate(nuint count) => CheckBounds(handle, offset, count);
}
}
-} \ No newline at end of file
+}
diff --git a/lib/Utils/src/Native/NatveLibraryResolver.cs b/lib/Utils/src/Native/NatveLibraryResolver.cs
new file mode 100644
index 0000000..b888f42
--- /dev/null
+++ b/lib/Utils/src/Native/NatveLibraryResolver.cs
@@ -0,0 +1,299 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: NatveLibraryResolver.cs
+*
+* NatveLibraryResolver.cs is part of VNLib.Utils which is part of
+* the larger VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Diagnostics;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Diagnostics.CodeAnalysis;
+
+namespace VNLib.Utils.Native
+{
+ /// <summary>
+ /// Uses a supplied library state to resolve and load a platform native library
+ /// into the process memory space.
+ /// </summary>
+ /// <param name="suppliedLibraryPath">The raw caller-supplied library path to resolve</param>
+ /// <param name="assembly">A assembly loading the desired library</param>
+ /// <param name="searchPath">The dll loader search path requirements</param>
+ internal readonly struct NatveLibraryResolver(string suppliedLibraryPath, Assembly assembly, DllImportSearchPath searchPath)
+ {
+ private readonly string _libFileName = Path.GetFileName(suppliedLibraryPath);
+ private readonly string? _relativeDir = Path.GetDirectoryName(suppliedLibraryPath);
+
+ internal readonly bool HasRelativeDir => _relativeDir != null;
+
+ internal readonly bool IsFullPath => Path.IsPathRooted(suppliedLibraryPath);
+
+ /// <summary>
+ /// Resolves and attempts to load the current library into the current process.
+ /// </summary>
+ /// <param name="library">The <see cref="SafeLibraryHandle"/> if the library was successfully resolved</param>
+ /// <returns>True if the library was resolved and loaded into the process, false otherwise</returns>
+ internal readonly bool ResolveAndLoadLibrary([NotNullWhen(true)] out SafeLibraryHandle? library)
+ {
+ //Try naive load if the path is an absolute path
+ if (IsFullPath && _tryLoad(suppliedLibraryPath, out library))
+ {
+ return true;
+ }
+
+ //Try to load the library from the relative directory
+ if (HasRelativeDir && TryLoadRelativeToWorkingDir(out library))
+ {
+ return true;
+ }
+
+ //Path is just a file name, so search for prefixes and extensions loading directly
+ if (TryNaiveLoadLibrary(out library))
+ {
+ return true;
+ }
+
+ //Try searching for the file in directories
+ return TryLoadFromSafeDirs(out library);
+ }
+
+ /*
+ * Naive load builds file names that are platform dependent
+ * and probes for the library using the NativeLibrary.TryLoad rules
+ * to load the library.
+ *
+ * If the file has an extension, it will attempt to load the library
+ * directly using the file name. Otherwise, it will continue to probe
+ */
+ private readonly bool TryNaiveLoadLibrary([NotNullWhen(true)] out SafeLibraryHandle? library)
+ {
+ foreach (string probingFileName in GetProbingFileNames(_libFileName))
+ {
+ if (_tryLoad(probingFileName, out library))
+ {
+ return true;
+ }
+ }
+
+ library = null;
+ return false;
+ }
+
+ /*
+ * Attempts to probe library files names that are located inside safe directories
+ * specified by the caller using the DllImportSearchPath enum.
+ */
+ private readonly bool TryLoadFromSafeDirs([NotNullWhen(true)] out SafeLibraryHandle? library)
+ {
+ //Try enumerating safe directories
+ if (searchPath.HasFlag(DllImportSearchPath.SafeDirectories))
+ {
+ foreach (string dir in GetSpecialDirPaths())
+ {
+ if (TryLoadInDirectory(dir, out library))
+ {
+ return true;
+ }
+ }
+ }
+
+ //Check application directory first (including subdirectories)
+ if (searchPath.HasFlag(DllImportSearchPath.ApplicationDirectory))
+ {
+ //get the current directory
+ if (TryLoadInDirectory(Directory.GetCurrentDirectory(), out library))
+ {
+ return true;
+ }
+ }
+
+ //See if search in the calling assembly directory
+ if (searchPath.HasFlag(DllImportSearchPath.AssemblyDirectory))
+ {
+ //Get the calling assmblies directory
+ string libDir = Assembly.GetCallingAssembly().Location;
+ Debug.WriteLine("Native library searching for calling assembly location:{0} ", libDir);
+ if (TryLoadInDirectory(libDir, out library))
+ {
+ return true;
+ }
+ }
+
+ //Search system32 dir
+ if (searchPath.HasFlag(DllImportSearchPath.System32))
+ {
+ string sys32Dir = Environment.GetFolderPath(Environment.SpecialFolder.SystemX86);
+
+ //Get the system directory
+ if (TryLoadInDirectory(sys32Dir, out library))
+ {
+ return true;
+ }
+ }
+
+ library = null;
+ return false;
+ }
+
+ /*
+ * Users may specify realtive directories to search for the library
+ * in the current working directory, so this function attempts to load
+ * the library from the relative directory if the user has specified one
+ */
+ private readonly bool TryLoadRelativeToWorkingDir([NotNullWhen(true)] out SafeLibraryHandle? library)
+ {
+ string libDir = Directory.GetCurrentDirectory();
+ return TryLoadInDirectory(Path.Combine(libDir, _relativeDir!), out library);
+ }
+
+ /*
+ * Attempts to load libraries that are located in the specified directory
+ * by probing for the library file name in the directory path using
+ * prefixes and extensions
+ */
+ private readonly bool TryLoadInDirectory(string baseDir, [NotNullWhen(true)] out SafeLibraryHandle? library)
+ {
+ IEnumerable<string> fullProbingPaths = GetProbingFileNames(_libFileName).Select(p => Path.Combine(baseDir, p));
+
+ foreach (string probingFilePath in fullProbingPaths)
+ {
+ if (_tryLoad(probingFilePath, out library))
+ {
+ return true;
+ }
+ }
+
+ library = null;
+ return false;
+ }
+
+ /*
+ * core load function
+ */
+ internal readonly bool _tryLoad(string filePath, [NotNullWhen(true)] out SafeLibraryHandle? library)
+ {
+ //Attempt a naive load
+ if (NativeLibrary.TryLoad(filePath, assembly, searchPath, out IntPtr libHandle))
+ {
+ library = SafeLibraryHandle.FromExisting(libHandle, true);
+ return true;
+ }
+
+ library = null;
+ return false;
+ }
+
+ private static readonly Environment.SpecialFolder[] SafeDirs =
+ [
+ Environment.SpecialFolder.SystemX86,
+ Environment.SpecialFolder.System,
+ Environment.SpecialFolder.Windows,
+ Environment.SpecialFolder.ProgramFilesX86,
+ Environment.SpecialFolder.ProgramFiles
+ ];
+
+ private static IEnumerable<string> GetSpecialDirPaths() => SafeDirs.Select(Environment.GetFolderPath);
+
+ private static IEnumerable<string> GetProbingFileNames(string libraryFileName)
+ {
+ //Inlcude the library file name if it has an extension
+ if (Path.HasExtension(libraryFileName))
+ {
+ yield return libraryFileName;
+ }
+
+ foreach (string prefix in GetNativeLibPrefixs())
+ {
+ foreach (string libExtension in GetNativeLibExtension())
+ {
+ yield return GetLibraryFileName(prefix, libraryFileName, libExtension);
+ }
+ }
+
+ static string GetLibraryFileName(string prefix, string libPath, string extension)
+ {
+ //Get dir name from the lib path if it has one
+
+
+ libPath = Path.Combine(prefix, libPath);
+
+ //If the library path already has an extension, just search for the file
+ if (!Path.HasExtension(libPath))
+ {
+ //slice the lib to its file name
+ libPath = Path.GetFileName(libPath);
+
+ if (extension.Length > 0)
+ {
+ libPath = Path.ChangeExtension(libPath, extension);
+ }
+ }
+
+ return libPath;
+ }
+
+ static string[] GetNativeLibPrefixs()
+ {
+ if (OperatingSystem.IsWindows())
+ {
+ return [""];
+ }
+ else if (OperatingSystem.IsMacOS())
+ {
+ return ["", "lib"];
+ }
+ else if (OperatingSystem.IsLinux())
+ {
+ return ["", "lib_", "lib"];
+ }
+ else
+ {
+ Debug.Fail("Unknown OS type");
+ return [];
+ }
+ }
+
+ static string[] GetNativeLibExtension()
+ {
+ if (OperatingSystem.IsWindows())
+ {
+ return ["", ".dll"];
+ }
+ else if (OperatingSystem.IsMacOS())
+ {
+ return ["", ".dylib"];
+ }
+ else if (OperatingSystem.IsLinux())
+ {
+ return ["", ".so", ".so.1"];
+ }
+ else
+ {
+ Debug.Fail("Unknown OS type");
+ return [];
+ }
+ }
+ }
+ }
+}
diff --git a/lib/Utils/src/Native/SafeLibraryHandle.cs b/lib/Utils/src/Native/SafeLibraryHandle.cs
index ed9dd16..5fb2283 100644
--- a/lib/Utils/src/Native/SafeLibraryHandle.cs
+++ b/lib/Utils/src/Native/SafeLibraryHandle.cs
@@ -23,10 +23,7 @@
*/
using System;
-using System.IO;
-using System.Linq;
using System.Reflection;
-using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Diagnostics.CodeAnalysis;
@@ -42,128 +39,7 @@ namespace VNLib.Utils.Native
///<inheritdoc/>
public override bool IsInvalid => handle == IntPtr.Zero;
- private SafeLibraryHandle(IntPtr libHandle) : base(IntPtr.Zero, true)
- {
- //Init handle
- SetHandle(libHandle);
- }
-
- /// <summary>
- /// Finds and loads the specified native libary into the current process by its name at runtime
- /// </summary>
- /// <param name="libPath">The path (or name of libary) to search for</param>
- /// <param name="searchPath">
- /// The <see cref="DllImportSearchPath"/> used to search for libaries
- /// within the current filesystem
- /// </param>
- /// <returns>The loaded <see cref="SafeLibraryHandle"/></returns>
- /// <exception cref="ArgumentNullException"></exception>
- /// <exception cref="DllNotFoundException"></exception>
- public static SafeLibraryHandle LoadLibrary(string libPath, DllImportSearchPath searchPath = DllImportSearchPath.ApplicationDirectory)
- {
- ArgumentNullException.ThrowIfNull(libPath);
- //See if the path includes a file extension
- return TryLoadLibrary(libPath, searchPath, out SafeLibraryHandle? lib)
- ? lib
- : throw new DllNotFoundException($"The library {libPath} or one of its dependencies could not be found");
- }
-
- /// <summary>
- /// Attempts to load the specified native libary into the current process by its name at runtime
- /// </summary>
- ///<param name="libPath">The path (or name of libary) to search for</param>
- /// <param name="searchPath">
- /// The <see cref="DllImportSearchPath"/> used to search for libaries
- /// within the current filesystem
- /// </param>
- /// <param name="lib">The handle to the libary if successfully loaded</param>
- /// <returns>True if the libary was found and loaded into the current process</returns>
- public static bool TryLoadLibrary(string libPath, DllImportSearchPath searchPath, [NotNullWhen(true)] out SafeLibraryHandle? lib)
- {
- lib = null;
- //Allow full rooted paths
- if (Path.IsPathRooted(libPath))
- {
- //Attempt a native load
- if (NativeLibrary.TryLoad(libPath, out IntPtr libHandle))
- {
- lib = new(libHandle);
- return true;
- }
- return false;
- }
- //Check application directory first (including subdirectories)
- if ((searchPath & DllImportSearchPath.ApplicationDirectory) > 0)
- {
- //get the current directory
- string libDir = Directory.GetCurrentDirectory();
- if (TryLoadLibraryInternal(libDir, libPath, SearchOption.TopDirectoryOnly, out lib))
- {
- return true;
- }
- }
- //See if search in the calling assembly directory
- if ((searchPath & DllImportSearchPath.AssemblyDirectory) > 0)
- {
- //Get the calling assmblies directory
- string libDir = Assembly.GetCallingAssembly().Location;
- Debug.WriteLine("Native library searching for calling assembly location:{0} ", libDir);
- if (TryLoadLibraryInternal(libDir, libPath, SearchOption.TopDirectoryOnly, out lib))
- {
- return true;
- }
- }
- //Search system32 dir
- if ((searchPath & DllImportSearchPath.System32) > 0)
- {
- //Get the system directory
- string libDir = Environment.GetFolderPath(Environment.SpecialFolder.SystemX86);
- if (TryLoadLibraryInternal(libDir, libPath, SearchOption.TopDirectoryOnly, out lib))
- {
- return true;
- }
- }
- //Attempt a native load
- {
- if (NativeLibrary.TryLoad(libPath, out IntPtr libHandle))
- {
- lib = new(libHandle);
- return true;
- }
- return false;
- }
- }
-
- private static bool TryLoadLibraryInternal(string libDir, string libPath, SearchOption dirSearchOptions, [NotNullWhen(true)] out SafeLibraryHandle? libary)
- {
- //Try to find the libary file
- string? libFile = GetLibraryFile(libDir, libPath, dirSearchOptions);
- //Load libary
- if (libFile != null && NativeLibrary.TryLoad(libFile, out IntPtr libHandle))
- {
- libary = new SafeLibraryHandle(libHandle);
- return true;
- }
- libary = null;
- return false;
- }
-
- private static string? GetLibraryFile(string dirPath, string libPath, SearchOption search)
- {
- //If the library path already has an extension, just search for the file
- if (Path.HasExtension(libPath))
- {
- return Directory.EnumerateFiles(dirPath, libPath, search).FirstOrDefault();
- }
- else
- {
- //slice the lib to its file name
- libPath = Path.GetFileName(libPath);
- libPath = Path.ChangeExtension(libPath, OperatingSystem.IsWindows() ? ".dll" : ".so");
- //Select the first file that matches the name
- return Directory.EnumerateFiles(dirPath, libPath, search).FirstOrDefault();
- }
- }
+ private SafeLibraryHandle(IntPtr libHandle, bool ownsHandle) : base(IntPtr.Zero, ownsHandle) => SetHandle(libHandle);
/// <summary>
/// Loads a native function pointer from the library of the specified name and
@@ -179,7 +55,7 @@ namespace VNLib.Utils.Native
{
//Increment handle count before obtaining a method
bool success = false;
- DangerousAddRef(ref success);
+ DangerousAddRef(ref success);
ObjectDisposedException.ThrowIf(success == false, this);
@@ -218,40 +94,124 @@ namespace VNLib.Utils.Native
return Marshal.GetDelegateForFunctionPointer<T>(nativeMethod);
}
+ ///<inheritdoc/>
+ protected override bool ReleaseHandle()
+ {
+ //Free the library and set the handle as invalid
+ NativeLibrary.Free(handle);
+ SetHandleAsInvalid();
+ return true;
+ }
+
/// <summary>
- /// Loads a native method from the library of the specified name and managed delegate
+ /// Finds and loads the specified native libary into the current process by its name at runtime.
+ /// This function defaults to the executing assembly
/// </summary>
- /// <typeparam name="T">The native method delegate type</typeparam>
- /// <param name="methodName">The name of the native method</param>
- /// <returns>A wapper handle around the native method delegate</returns>
+ /// <param name="libPath">The path (or name of libary) to search for</param>
+ /// <param name="searchPath">
+ /// The <see cref="DllImportSearchPath"/> used to search for libaries
+ /// within the current filesystem
+ /// </param>
+ /// <returns>The loaded <see cref="SafeLibraryHandle"/></returns>
/// <exception cref="ArgumentNullException"></exception>
- /// <exception cref="ObjectDisposedException">If the handle is closed or invalid</exception>
- /// <exception cref="EntryPointNotFoundException">When the specified entrypoint could not be found</exception>
- [Obsolete("Updated naming, use GetFunction<T>() instead")]
- public SafeMethodHandle<T> GetMethod<T>(string methodName) where T : Delegate => GetFunction<T>(methodName);
+ /// <exception cref="DllNotFoundException"></exception>
+ public static SafeLibraryHandle LoadLibrary(string libPath, DllImportSearchPath searchPath = DllImportSearchPath.ApplicationDirectory)
+ {
+ //See if the path includes a file extension
+ return TryLoadLibrary(libPath, searchPath, out SafeLibraryHandle? lib)
+ ? lib
+ : throw new DllNotFoundException($"The library '{libPath}' or one of its dependencies could not be found");
+ }
/// <summary>
- /// Gets an delegate wrapper for the specified method without tracking its referrence.
- /// The caller must manage the <see cref="SafeLibraryHandle"/> referrence count in order
- /// to not leak resources or cause process corruption
+ /// Finds and loads the specified native libary into the current process by its name at runtime
/// </summary>
- /// <typeparam name="T">The native method delegate type</typeparam>
- /// <param name="methodName">The name of the native method</param>
- /// <returns>A the delegate wrapper on the native method</returns>
+ /// <param name="libPath">The path (or name of libary) to search for</param>
+ /// <param name="searchPath">
+ /// The <see cref="DllImportSearchPath"/> used to search for libaries
+ /// within the current filesystem
+ /// </param>
+ /// <param name="assembly">The assembly loading the native library</param>
+ /// <returns>The loaded <see cref="SafeLibraryHandle"/></returns>
/// <exception cref="ArgumentNullException"></exception>
- /// <exception cref="ObjectDisposedException">If the handle is closed or invalid</exception>
- /// <exception cref="EntryPointNotFoundException">When the specified entrypoint could not be found</exception>
- [Obsolete("Updated naming, use DangerousGetFunction<T>() instead")]
- public T DangerousGetMethod<T>(string methodName) where T : Delegate => DangerousGetFunction<T>(methodName);
+ /// <exception cref="DllNotFoundException"></exception>
+ public static SafeLibraryHandle LoadLibrary(
+ string libPath,
+ Assembly assembly,
+ DllImportSearchPath searchPath = DllImportSearchPath.ApplicationDirectory
+ )
+ {
+ //See if the path includes a file extension
+ return TryLoadLibrary(libPath, assembly, searchPath, out SafeLibraryHandle? lib)
+ ? lib
+ : throw new DllNotFoundException($"The library '{libPath}' or one of its dependencies could not be found");
+ }
+ /// <summary>
+ /// Creates a new <see cref="SafeLibraryHandle"/> from an existing library pointer.
+ /// </summary>
+ /// <param name="libHandle">A pointer to the existing (and loaded) library</param>
+ /// <param name="ownsHandle">A value that specifies whether the wrapper owns the library handle now</param>
+ /// <returns>A safe library wrapper around the existing library pointer</returns>
+ /// <exception cref="ArgumentNullException"></exception>
+ public unsafe static SafeLibraryHandle FromExisting(nint libHandle, bool ownsHandle)
+ {
+ ArgumentNullException.ThrowIfNull(libHandle.ToPointer(), nameof(libHandle));
+ return new(libHandle, ownsHandle);
+ }
- ///<inheritdoc/>
- protected override bool ReleaseHandle()
+ /// <summary>
+ /// Attempts to load the specified native libary into the current process by its name at runtime.
+ /// This function defaults to the executing assembly
+ /// </summary>
+ ///<param name="libPath">The path (or name of libary) to search for</param>
+ /// <param name="searchPath">
+ /// The <see cref="DllImportSearchPath"/> used to search for libaries
+ /// within the current filesystem
+ /// </param>
+ /// <param name="library">The handle to the libary if successfully loaded</param>
+ /// <returns>True if the libary was found and loaded into the current process</returns>
+ public static bool TryLoadLibrary(
+ string libPath,
+ DllImportSearchPath searchPath,
+ [NotNullWhen(true)] out SafeLibraryHandle? library
+ )
{
- //Free the library and set the handle as invalid
- NativeLibrary.Free(handle);
- SetHandleAsInvalid();
- return true;
+ return TryLoadLibrary(
+ libPath,
+ Assembly.GetExecutingAssembly(), //Use the executing assembly as the default loading assembly
+ searchPath,
+ out library
+ );
+ }
+
+
+
+ /// <summary>
+ /// Attempts to load the specified native libary into the current process by its name at runtime
+ /// </summary>
+ ///<param name="libPath">The path (or name of libary) to search for</param>
+ /// <param name="searchPath">
+ /// The <see cref="DllImportSearchPath"/> used to search for libaries
+ /// within the current filesystem
+ /// </param>
+ /// <param name="library">The handle to the libary if successfully loaded</param>
+ /// <param name="assembly">The assembly loading the native library</param>
+ /// <returns>True if the libary was found and loaded into the current process</returns>
+ /// <exception cref="ArgumentNullException"></exception>
+ public static bool TryLoadLibrary(
+ string libPath,
+ Assembly assembly,
+ DllImportSearchPath searchPath,
+ [NotNullWhen(true)] out SafeLibraryHandle? library
+ )
+ {
+ ArgumentNullException.ThrowIfNull(libPath);
+ ArgumentNullException.ThrowIfNull(assembly);
+
+ NatveLibraryResolver resolver = new(libPath, assembly, searchPath);
+
+ return resolver.ResolveAndLoadLibrary(out library);
}
}
}
diff --git a/lib/Utils/src/Resources/LazyInitializer.cs b/lib/Utils/src/Resources/LazyInitializer.cs
new file mode 100644
index 0000000..5de43e0
--- /dev/null
+++ b/lib/Utils/src/Resources/LazyInitializer.cs
@@ -0,0 +1,112 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: LazyInitializer.cs
+*
+* LazyInitializer.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Threading;
+using System.Diagnostics;
+
+namespace VNLib.Utils.Resources
+{
+ /// <summary>
+ /// A lazy initializer that creates a single instance of a type
+ /// and shares it across all threads. This class simply guarantees
+ /// that the instance is only created once and shared across all
+ /// threads as efficiently as possible for long running processes.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="initalizer">The callback function that initializes the instance</param>
+ public sealed class LazyInitializer<T>(Func<T> initalizer)
+ {
+ private readonly object _lock = new();
+ private readonly Func<T> initalizer = initalizer ?? throw new ArgumentNullException(nameof(initalizer));
+
+ private T? _instance;
+ private bool _isLoaded;
+
+ /// <summary>
+ /// A value indicating if the instance has ben loaded
+ /// </summary>
+ public bool IsLoaded => _isLoaded;
+
+ /// <summary>
+ /// Gets or creates the instance only once and returns
+ /// the shared instance
+ /// </summary>
+ /// <remarks>
+ /// NOTE:
+ /// Accessing this property may block the calling thread
+ /// if the instance has not yet been loaded. Only one thread
+ /// will create the instance, all other threads will wait
+ /// for the instance to be created.
+ /// </remarks>
+ public T Instance
+ {
+ get
+ {
+ //See if instance is already loaded (this read is atomic in .NET)
+ if (_isLoaded)
+ {
+ return _instance!;
+ }
+
+ /*
+ * Instance has not yet been loaded. Only one thread
+ * must load the object, all other threads must wait
+ * for the object to be loaded.
+ */
+
+ if (Monitor.TryEnter(_lock, 0))
+ {
+ try
+ {
+ /*
+ * Lock was entered without waiting (lock was available), this will now be
+ * the thread that invokes the load function
+ */
+
+ _instance = initalizer();
+
+ //Finally set the load state
+ _isLoaded = true;
+ }
+ finally
+ {
+ Monitor.Exit(_lock);
+ }
+ }
+ else
+ {
+ //wait for lock to be released, when it is, the object should be loaded
+ Monitor.Enter(_lock);
+ Monitor.Exit(_lock);
+
+ //object instance should now be available to non-creating threads
+ Debug.Assert(_isLoaded);
+ }
+
+ return _instance!;
+ }
+ }
+ }
+} \ No newline at end of file