File: System\Runtime\InteropServices\NativeLibrary.NativeAot.cs
Web Access
Project: src\src\runtime\src\coreclr\nativeaot\System.Private.CoreLib\src\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.IO;
using System.Reflection;

using LibraryNameVariation = System.Runtime.Loader.LibraryNameVariation;

namespace System.Runtime.InteropServices
{
    public static partial class NativeLibrary
    {
        // Not a public API. We expose this so that it's possible to bypass the codepath that tries to read search path
        // from custom attributes.
        internal static bool TryLoad(string libraryName, Assembly assembly, DllImportSearchPath searchPath, out IntPtr handle)
        {
            handle = LoadLibraryByName(libraryName,
                                assembly,
                                userSpecifiedSearchFlags: true,
                                searchPath,
                                throwOnError: false);
            return handle != IntPtr.Zero;
        }

        internal static IntPtr LoadLibraryByName(string libraryName, Assembly assembly, DllImportSearchPath? searchPath, bool throwOnError)
        {
            // First checks if a default dllImportSearchPathFlags was passed in, if so, use that value.
            // Otherwise checks if the assembly has the DefaultDllImportSearchPathsAttribute attribute.
            // If so, use that value.
            bool userSpecifiedSearchFlags = searchPath.HasValue;
            if (!userSpecifiedSearchFlags)
            {
                searchPath = GetDllImportSearchPath(assembly, out userSpecifiedSearchFlags);
            }
            return LoadLibraryByName(libraryName, assembly, userSpecifiedSearchFlags, searchPath!.Value, throwOnError);
        }

        private static IntPtr LoadLibraryByName(string libraryName, Assembly assembly, bool userSpecifiedSearchFlags, DllImportSearchPath searchPath, bool throwOnError)
        {
            int searchPathFlags = (int)(searchPath & ~DllImportSearchPath.AssemblyDirectory);
            bool searchAssemblyDirectory = (searchPath & DllImportSearchPath.AssemblyDirectory) != 0;

            LoadLibErrorTracker errorTracker = default;
            IntPtr ret = LoadBySearch(assembly, userSpecifiedSearchFlags, searchAssemblyDirectory, searchPathFlags, ref errorTracker, libraryName);
            if (throwOnError && ret == IntPtr.Zero)
            {
                errorTracker.Throw(libraryName);
            }

            return ret;
        }

        private static DllImportSearchPath GetDllImportSearchPath(Assembly callingAssembly, out bool userSpecifiedSearchFlags)
        {
            foreach (CustomAttributeData cad in callingAssembly.CustomAttributes)
            {
                if (cad.AttributeType == typeof(DefaultDllImportSearchPathsAttribute))
                {
                    userSpecifiedSearchFlags = true;
                    return (DllImportSearchPath)cad.ConstructorArguments[0].Value!;
                }
            }

            userSpecifiedSearchFlags = false;
            return DllImportSearchPath.AssemblyDirectory;
        }

        internal static IntPtr LoadBySearch(Assembly callingAssembly, bool userSpecifiedSearchFlags, bool searchAssemblyDirectory, int dllImportSearchPathFlags, ref LoadLibErrorTracker errorTracker, string libraryName)
        {
            IntPtr ret;

            int loadWithAlteredPathFlags = LoadWithAlteredSearchPathFlag;
            const int loadLibrarySearchFlags = (int)DllImportSearchPath.UseDllDirectoryForDependencies
                | (int)DllImportSearchPath.ApplicationDirectory
                | (int)DllImportSearchPath.UserDirectories
                | (int)DllImportSearchPath.System32
                | (int)DllImportSearchPath.SafeDirectories;
            bool libNameIsRelativePath = !Path.IsPathFullyQualified(libraryName);

            // P/Invokes are often declared with variations on the actual library name.
            // For example, it's common to leave off the extension/suffix of the library
            // even if it has one, or to leave off a prefix like "lib" even if it has one
            // (both of these are typically done to smooth over cross-platform differences).
            // We try to dlopen with such variations on the original.
            foreach (LibraryNameVariation libraryNameVariation in LibraryNameVariation.DetermineLibraryNameVariations(libraryName, libNameIsRelativePath))
            {
                string currLibNameVariation = libraryNameVariation.Prefix + libraryName + libraryNameVariation.Suffix;

                if (!libNameIsRelativePath)
                {
                    // LOAD_WITH_ALTERED_SEARCH_PATH is incompatible with LOAD_LIBRARY_SEARCH flags. Remove those flags if they are set.
                    int flags = loadWithAlteredPathFlags | (dllImportSearchPathFlags & ~loadLibrarySearchFlags);
                    ret = LoadLibraryHelper(currLibNameVariation, flags, ref errorTracker);
                    if (ret != IntPtr.Zero)
                    {
                        return ret;
                    }
                }
                else if ((callingAssembly != null) && searchAssemblyDirectory)
                {
                    // LOAD_WITH_ALTERED_SEARCH_PATH is incompatible with LOAD_LIBRARY_SEARCH flags. Remove those flags if they are set.
                    int flags = loadWithAlteredPathFlags | (dllImportSearchPathFlags & ~loadLibrarySearchFlags);

                    // Try to load the module alongside the assembly where the PInvoke was declared.
                    // For PInvokes where the DllImportSearchPath.AssemblyDirectory is specified, look next to the application.
                    ret = LoadLibraryHelper(Path.Combine(AppContext.BaseDirectory, currLibNameVariation), flags, ref errorTracker);
                    if (ret != IntPtr.Zero)
                    {
                        return ret;
                    }
                }

                // Internally, search path flags and whether or not to search the assembly directory are
                // tracked separately. However, on the API level, DllImportSearchPath represents them both.
                // When unspecified, the default is to search the assembly directory and all OS defaults,
                // which maps to searchAssemblyDirectory being true and dllImportSearchPathFlags being 0.
                // When a user specifies DllImportSearchPath.AssemblyDirectory, searchAssemblyDirectory is
                // true, dllImportSearchPathFlags is 0, and the desired logic is to only search the assembly
                // directory (handled above), so we avoid doing any additional load search in that case.
                if (!userSpecifiedSearchFlags || !searchAssemblyDirectory || dllImportSearchPathFlags != 0)
                {
                    ret = LoadLibraryHelper(currLibNameVariation, dllImportSearchPathFlags, ref errorTracker);
                    if (ret != IntPtr.Zero)
                    {
                        return ret;
                    }
                }
            }

            return IntPtr.Zero;
        }
    }
}