Partager via


Chargement de la bibliothèque native

Cet article explique les chemins que le runtime recherche lors du chargement de bibliothèques natives par le biais de P/Invoke. Il montre également comment utiliser SetDllImportResolver.

Variations des noms de bibliothèque

Pour simplifier le code P/Invoke multiplateforme, le runtime ajoute l’extension de bibliothèque partagée canonique (.dll, .so ou .dylib) aux noms de bibliothèque native. Sur les plateformes Unix, le moteur d’exécution essaiera également de faire précéder lib. Ces variations des noms de bibliothèque sont automatiquement recherchées lorsque vous utilisez des API qui chargent des bibliothèques natives, comme DllImportAttribute.

Remarque

Les chemins absolus dans les noms de bibliothèque (par exemple, /usr/lib/libc.so) sont traités en l’état et aucune variation ne sera recherchée.

Prenons l’exemple suivant d’utilisation de P/Invoke :

[DllImport("nativedep")]
static extern int ExportedFunction();

Lors d’une exécution sur Windows, la DLL est recherchée dans l’ordre suivant :

  1. nativedep
  2. nativedep.dll (si le nom de la bibliothèque ne se termine pas déjà par .dll ou .exe)

Lors d’une exécution sur Linux ou macOS, le runtime essaie de préfixer lib et d’ajouter l’extension de bibliothèque partagée canonique. Sur ces systèmes d’exploitation, les variations des noms de bibliothèque sont essayées dans l’ordre suivant :

  1. nativedep.so / nativedep.dylib
  2. libnativedep.so / libnativedep.dylib 1
  3. nativedep
  4. libnativedep 1

Sur Linux, l’ordre de recherche est différent si le nom de la bibliothèque se termine par .so ou contient .so. (notez le . de fin). Prenons l’exemple suivant :

[DllImport("nativedep.so.6")]
static extern int ExportedFunction();

Dans ce cas, les variations des noms de bibliothèque sont essayées dans l’ordre suivant :

  1. nativedep.so.6
  2. libnativedep.so.6 1
  3. nativedep.so.6.so
  4. libnativedep.so.6.so 1

1 Le chemin est vérifié uniquement si le nom de la bibliothèque ne contient pas de caractère séparateur de répertoire (/).

Résolveur d’importation personnalisé

Dans des scénarios plus complexes, vous pouvez utiliser SetDllImportResolver pour résoudre des importations de DLL au moment de l’exécution. Dans l’exemple suivant, nativedep est résolu en nativedep_avx2 si le processeur le prend en charge.

Astuce

Cette fonctionnalité n’est disponible que dans .NET Core 3.1 et .NET 5+.

using System;
using System.Reflection;
using System.Runtime.InteropServices;

namespace PInvokeSamples
{
    public static partial class Program
    {
        [LibraryImport("nativedep")]
        private static partial int ExportedFunction();

        public static void Main(string[] args)
        {
            // Register the import resolver before calling the imported function.
            // Only one import resolver can be set for a given assembly.
            NativeLibrary.SetDllImportResolver(Assembly.GetExecutingAssembly(), DllImportResolver);

            int value = ExportedFunction();
            Console.WriteLine(value);
        }

        private static IntPtr DllImportResolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath)
        {
            if (libraryName == "nativedep")
            {
                // On systems with AVX2 support, load a different library.
                if (System.Runtime.Intrinsics.X86.Avx2.IsSupported)
                {
                    return NativeLibrary.Load("nativedep_avx2", assembly, searchPath);
                }
            }

            // Otherwise, fallback to default import resolver.
            return IntPtr.Zero;
        }
    }
}

Voir aussi