Ładowanie biblioteki natywnej

W tym artykule wyjaśniono, które ścieżki środowiska uruchomieniowego wyszukują podczas ładowania bibliotek natywnych za pośrednictwem protokołu P/Invoke. Pokazano również, jak używać polecenia SetDllImportResolver.

Odmiany nazw biblioteki

Aby ułatwić prostszy międzyplatformowy kod P/Invoke, środowisko uruchomieniowe dodaje rozszerzenie biblioteki udostępnionej kanonicznej (.dll.solub .dylib) do nazw bibliotek natywnych. Na platformach opartych na systemie Unix środowisko uruchomieniowe spróbuje również wstępnie utworzyć libelement . Te odmiany nazw bibliotek są automatycznie przeszukiwane podczas korzystania z interfejsów API ładujących biblioteki natywne, takie jak DllImportAttribute.

Uwaga

Ścieżki bezwzględne w nazwach bibliotek (na przykład /usr/lib/libc.so) są traktowane jako i nie będą przeszukiwane żadne odmiany.

Rozważmy następujący przykład użycia funkcji P/Invoke:

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

Podczas uruchamiania w systemie Windows biblioteka DLL jest wyszukiwana w następującej kolejności:

  1. nativedep
  2. nativedep.dll(jeśli nazwa biblioteki nie kończy się jeszcze ciągiem .dll lub ).exe

Podczas uruchamiania w systemie Linux lub macOS środowisko uruchomieniowe spróbuje wstępnie dołączyć lib rozszerzenie biblioteki udostępnionej kanonicznej. W tych systemach operacyjnych odmiany nazw bibliotek są wypróbowane w następującej kolejności:

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

W systemie Linux kolejność wyszukiwania różni się, jeśli nazwa biblioteki kończy się ciągiem .so lub zawiera .so. (zwróć uwagę na końcowy .ciąg ). Rozważmy następujący przykład:

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

W takim przypadku odmiany nazw biblioteki są sprawdzane w następującej kolejności:

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

1 Ścieżka jest sprawdzana tylko wtedy, gdy nazwa biblioteki nie zawiera znaku separatora katalogu (/).

Niestandardowy moduł rozpoznawania importu

W bardziej złożonych scenariuszach można użyć polecenia , aby rozwiązać problemy z SetDllImportResolver importowaniem bibliotek DLL w czasie wykonywania. W poniższym przykładzie jest rozpoznawana wartość nativedep_avx2 , nativedep jeśli procesor cpu go obsługuje.

Napiwek

Ta funkcja jest dostępna tylko w programach .NET Core 3.1 i .NET 5+.

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

namespace PInvokeSamples
{
    public static class Program
    {
        [DllImport("nativedep")]
        private static extern 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;
        }
    }
}

Zobacz też