File: src\libraries\System.Private.CoreLib\src\System\Runtime\InteropServices\NativeLibrary.cs
Web Access
Project: src\src\coreclr\System.Private.CoreLib\System.Private.CoreLib.csproj (System.Private.CoreLib)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.ComponentModel;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
 
namespace System.Runtime.InteropServices
{
    /// <summary>
    /// A delegate used to resolve native libraries via callback.
    /// </summary>
    /// <param name="libraryName">The native library to resolve.</param>
    /// <param name="assembly">The assembly requesting the resolution.</param>
    /// <param name="searchPath">
    ///     The DllImportSearchPathsAttribute on the PInvoke, if any.
    ///     Otherwise, the DllImportSearchPathsAttribute on the assembly, if any.
    ///     Otherwise null.
    /// </param>
    /// <returns>The handle for the loaded native library on success, null on failure.</returns>
    public delegate IntPtr DllImportResolver(string libraryName,
                                             Assembly assembly,
                                             DllImportSearchPath? searchPath);
 
    /// <summary>
    /// APIs for managing Native Libraries
    /// </summary>
    public static partial class NativeLibrary
    {
        /// <summary>
        /// NativeLibrary Loader: Simple API
        /// This method is a wrapper around OS loader, using "default" flags.
        /// </summary>
        /// <param name="libraryPath">The name of the native library to be loaded.</param>
        /// <returns>The handle for the loaded native library.</returns>
        /// <exception cref="ArgumentNullException">If libraryPath is null</exception>
        /// <exception cref="DllNotFoundException ">If the library can't be found.</exception>
        /// <exception cref="BadImageFormatException">If the library is not valid.</exception>
        public static IntPtr Load(string libraryPath)
        {
            ArgumentNullException.ThrowIfNull(libraryPath);
 
            return LoadFromPath(libraryPath, throwOnError: true);
        }
 
        /// <summary>
        /// NativeLibrary Loader: Simple API that doesn't throw
        /// </summary>
        /// <param name="libraryPath">The name of the native library to be loaded.</param>
        /// <param name="handle">The out-parameter for the loaded native library handle.</param>
        /// <returns>True on successful load, false otherwise.</returns>
        /// <exception cref="ArgumentNullException">If libraryPath is null</exception>
        public static bool TryLoad(string libraryPath, out IntPtr handle)
        {
            ArgumentNullException.ThrowIfNull(libraryPath);
 
            handle = LoadFromPath(libraryPath, throwOnError: false);
            return handle != IntPtr.Zero;
        }
 
        /// <summary>
        /// NativeLibrary Loader: High-level API
        /// Given a library name, this function searches specific paths based on the
        /// runtime configuration, input parameters, and attributes of the calling assembly.
        /// If DllImportSearchPath parameter is non-null, the flags in this enumeration are used.
        /// Otherwise, the flags specified by the DefaultDllImportSearchPaths attribute on the
        /// calling assembly (if any) are used.
        /// This method follows the native library resolution for the AssemblyLoadContext of the
        /// specified assembly. It will invoke the managed extension points:
        /// * AssemblyLoadContext.LoadUnmanagedDll()
        /// * AssemblyLoadContext.ResolvingUnmanagedDllEvent
        /// It does not invoke extension points that are not tied to the AssemblyLoadContext:
        /// * The per-assembly registered DllImportResolver callback
        /// </summary>
        /// <param name="libraryName">The name of the native library to be loaded.</param>
        /// <param name="assembly">The assembly loading the native library.</param>
        /// <param name="searchPath">The search path.</param>
        /// <returns>The handle for the loaded library.</returns>
        /// <exception cref="ArgumentNullException">If libraryPath or assembly is null</exception>
        /// <exception cref="ArgumentException">If assembly is not a RuntimeAssembly</exception>
        /// <exception cref="DllNotFoundException">If the library can't be found.</exception>
        /// <exception cref="BadImageFormatException">If the library is not valid.</exception>
        public static IntPtr Load(string libraryName, Assembly assembly, DllImportSearchPath? searchPath)
        {
            ArgumentNullException.ThrowIfNull(libraryName);
            ArgumentNullException.ThrowIfNull(assembly);
 
            if (assembly is not RuntimeAssembly)
                throw new ArgumentException(SR.Argument_MustBeRuntimeAssembly);
 
            return LoadLibraryByName(libraryName,
                              assembly,
                              searchPath,
                              throwOnError: true);
        }
 
        /// <summary>
        /// NativeLibrary Loader: High-level API that doesn't throw.
        /// Given a library name, this function searches specific paths based on the
        /// runtime configuration, input parameters, and attributes of the calling assembly.
        /// If DllImportSearchPath parameter is non-null, the flags in this enumeration are used.
        /// Otherwise, the flags specified by the DefaultDllImportSearchPaths attribute on the
        /// calling assembly (if any) are used.
        /// This method follows the native library resolution for the AssemblyLoadContext of the
        /// specified assembly. It will invoke the managed extension points:
        /// * AssemblyLoadContext.LoadUnmanagedDll()
        /// * AssemblyLoadContext.ResolvingUnmanagedDllEvent
        /// It does not invoke extension points that are not tied to the AssemblyLoadContext:
        /// * The per-assembly registered DllImportResolver callback
        /// </summary>
        /// <param name="libraryName">The name of the native library to be loaded.</param>
        /// <param name="assembly">The assembly loading the native library.</param>
        /// <param name="searchPath">The search path.</param>
        /// <param name="handle">The out-parameter for the loaded native library handle.</param>
        /// <returns>True on successful load, false otherwise.</returns>
        /// <exception cref="ArgumentNullException">If libraryPath or assembly is null</exception>
        /// <exception cref="ArgumentException">If assembly is not a RuntimeAssembly</exception>
        public static bool TryLoad(string libraryName, Assembly assembly, DllImportSearchPath? searchPath, out IntPtr handle)
        {
            ArgumentNullException.ThrowIfNull(libraryName);
            ArgumentNullException.ThrowIfNull(assembly);
 
            if (assembly is not RuntimeAssembly)
                throw new ArgumentException(SR.Argument_MustBeRuntimeAssembly);
 
            handle = LoadLibraryByName(libraryName,
                                assembly,
                                searchPath,
                                throwOnError: false);
            return handle != IntPtr.Zero;
        }
 
        /// <summary>
        /// Free a loaded library
        /// Given a library handle, free it.
        /// No action if the input handle is null.
        /// </summary>
        /// <param name="handle">The native library handle to be freed.</param>
        public static void Free(IntPtr handle)
        {
            if (handle == IntPtr.Zero)
                return;
            FreeLib(handle);
        }
 
        /// <summary>
        /// Get the address of an exported Symbol
        /// This is a simple wrapper around OS calls, and does not perform any name mangling.
        /// </summary>
        /// <param name="handle">The native library handle.</param>
        /// <param name="name">The name of the exported symbol.</param>
        /// <returns>The address of the symbol.</returns>
        /// <exception cref="ArgumentNullException">If handle or name is null</exception>
        /// <exception cref="EntryPointNotFoundException">If the symbol is not found</exception>
        public static IntPtr GetExport(IntPtr handle, string name)
        {
            ArgumentNullException.ThrowIfNull(handle);
            ArgumentNullException.ThrowIfNull(name);
 
            return GetSymbol(handle, name, throwOnError: true);
        }
 
        /// <summary>
        /// Get the address of an exported Symbol, but do not throw
        /// </summary>
        /// <param name="handle">The  native library handle.</param>
        /// <param name="name">The name of the exported symbol.</param>
        /// <param name="address"> The out-parameter for the symbol address, if it exists.</param>
        /// <returns>True on success, false otherwise.</returns>
        /// <exception cref="ArgumentNullException">If handle or name is null</exception>
        public static bool TryGetExport(IntPtr handle, string name, out IntPtr address)
        {
            ArgumentNullException.ThrowIfNull(handle);
            ArgumentNullException.ThrowIfNull(name);
 
            address = GetSymbol(handle, name, throwOnError: false);
            return address != IntPtr.Zero;
        }
 
        /// <summary>
        /// Map from assembly to native-library resolver.
        /// Interop specific fields and properties are generally not added to Assembly class.
        /// Therefore, this table uses weak assembly pointers to indirectly achieve
        /// similar behavior.
        /// </summary>
        private static ConditionalWeakTable<Assembly, DllImportResolver>? s_nativeDllResolveMap;
 
        /// <summary>
        /// Set a callback for resolving native library imports from an assembly.
        /// This per-assembly resolver is the first attempt to resolve native library loads
        /// initiated by this assembly.
        ///
        /// Only one resolver can be registered per assembly.
        /// Trying to register a second resolver fails with InvalidOperationException.
        /// </summary>
        /// <param name="assembly">The assembly for which the resolver is registered.</param>
        /// <param name="resolver">The resolver callback to register.</param>
        /// <exception cref="ArgumentNullException">If assembly or resolver is null</exception>
        /// <exception cref="ArgumentException">If a resolver is already set for this assembly</exception>
        public static void SetDllImportResolver(Assembly assembly, DllImportResolver resolver)
        {
            ArgumentNullException.ThrowIfNull(assembly);
            ArgumentNullException.ThrowIfNull(resolver);
 
            if (assembly is not RuntimeAssembly)
                throw new ArgumentException(SR.Argument_MustBeRuntimeAssembly);
 
            if (s_nativeDllResolveMap == null)
            {
                Interlocked.CompareExchange(ref s_nativeDllResolveMap,
                    new ConditionalWeakTable<Assembly, DllImportResolver>(), null);
            }
 
            if (!s_nativeDllResolveMap.TryAdd(assembly, resolver))
            {
                throw new InvalidOperationException(SR.InvalidOperation_CannotRegisterSecondResolver);
            }
        }
 
        /// <summary>
        /// The helper function that calls the per-assembly native-library resolver
        /// if one is registered for this assembly.
        /// </summary>
        /// <param name="libraryName">The native library to load.</param>
        /// <param name="assembly">The assembly trying load the native library.</param>
        /// <param name="hasDllImportSearchPathFlags">If the pInvoke has DefaultDllImportSearchPathAttribute.</param>
        /// <param name="dllImportSearchPathFlags">If hasdllImportSearchPathFlags is true, the flags in
        ///                                       DefaultDllImportSearchPathAttribute; meaningless otherwise </param>
        /// <returns>The handle for the loaded library on success. Null on failure.</returns>
        internal static IntPtr LoadLibraryCallbackStub(string libraryName, Assembly assembly,
                                                       bool hasDllImportSearchPathFlags, uint dllImportSearchPathFlags)
        {
            if (s_nativeDllResolveMap == null)
            {
                return IntPtr.Zero;
            }
 
            if (!s_nativeDllResolveMap.TryGetValue(assembly, out DllImportResolver? resolver))
            {
                return IntPtr.Zero;
            }
 
            return resolver(libraryName, assembly, hasDllImportSearchPathFlags ? (DllImportSearchPath?)dllImportSearchPathFlags : null);
        }
 
        /// <summary>
        /// Get a handle that can be used with <see cref="GetExport" /> or <see cref="TryGetExport" /> to resolve exports from the entry point module.
        /// </summary>
        /// <returns> The handle that can be used to resolve exports from the entry point module.</returns>
        public static IntPtr GetMainProgramHandle()
        {
            IntPtr result = IntPtr.Zero;
#if TARGET_WINDOWS
            result = Interop.Kernel32.GetModuleHandle(null);
#else
            result = Interop.Sys.GetDefaultSearchOrderPseudoHandle();
#endif
            // I don't know when a failure case can occur here, but checking for it and throwing an exception
            // if we encounter it.
            if (result == IntPtr.Zero)
            {
                throw new Win32Exception(Marshal.GetLastPInvokeError());
            }
            return result;
        }
    }
}