Поделиться через


Загрузка собственной библиотеки

В этой статье объясняется, какие пути просматривает среда выполнения при загрузке собственных библиотек с помощью P/Invoke. В нем также показано, как использовать SetDllImportResolver.

Варианты имени библиотеки

Чтобы упростить кроссплатформенный код P/Invoke, среда выполнения добавляет каноническое расширение общей библиотеки (.dll.soили.dylib) в имена собственных библиотек. На платформах под управлением Unix среда выполнения также попытается добавить в начало lib. Эти вариации имен библиотек автоматически ищутся при использовании API, которые загружают собственные библиотеки, например DllImportAttribute.

Примечание.

Абсолютные пути в именах библиотек (например, /usr/lib/libc.so) обрабатываются как есть, и никакие варианты не будут искать.

Рассмотрим следующий пример использования P/Invoke:

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

При запуске в Windows библиотека DLL ищется в следующем порядке поиска:

  1. nativedep
  2. nativedep.dll (если имя библиотеки еще не заканчивается .dll или .exe)

При запуске в Linux или macOS среда выполнения попытается добавить lib перед строкой и присоединить каноническое расширение общей библиотеки. В этих операционных системах проверяются варианты имени библиотеки в следующем порядке:

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

В Linux порядок поиска отличается, если имя библиотеки заканчивается .so или содержится .so. (обратите внимание на конечный .). Рассмотрим следующий пример:

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

В этом случае варианты имени библиотеки проверяются в следующем порядке:

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

1 Путь проверяется только в том случае, если имя библиотеки не содержит символ разделителя каталогов (/).

Настраиваемый резолвер импорта

В более сложных сценариях можно использовать SetDllImportResolver для разрешения импорта DLL во время выполнения. В следующем примере nativedep заменяется на nativedep_avx2, если процессор это поддерживает.

Совет

Эта функция доступна только в .NET Core 3.1 и .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;
        }
    }
}

См. также