Подтвердить что ты не робот

Загрузить x64 или x86 DLL в зависимости от платформы?

У меня есть приложение, построенное как "Любой процессор", и у него есть две сторонние библиотеки DLL той же библиотеки, на которые настроены x86 и x64. Я хотел бы включить одну из этих библиотек во время выполнения в зависимости от платформы, которая будет работать на клиентской машине. Какой был бы лучший способ сделать это?

4b9b3361

Ответ 1

Если мы говорим о неуправляемых DLL, объявляйте p/invokes следующим образом:

[DllImport("DllName.dll")]
static extern foo();

Обратите внимание, что мы не указываем путь к DLL, просто его имя, которое, как я полагаю, одинаково для 32- и 64-разрядных версий.

Затем, прежде чем вы вызовете любой из ваших p/invokes, загрузите библиотеку в свой процесс. Сделайте это с помощью p/invoking для функции API LoadLibrary. На этом этапе вы определите, будет ли ваш процесс 32 или 64 бит и соответствующим образом построить полный путь к DLL. Этот полный путь - это то, что вы переходите к LoadLibrary.

Теперь, когда вы вызываете свои p/invokes для библиотеки, они будут разрешены модулем, который вы только что загрузили.

Для управляемых сборок вы можете использовать Assembly.LoadFile для указания пути сборки. Это может быть немного сложным для оркестровки, но в этой замечательной статье показано, как: Автоматически выбирать 32 или 64 битные библиотеки смешанного режима. Есть много деталей, связанных с смешанным режимом и родными зависимостями DLL, которые, вероятно, не имеют отношения к вам. Ключ - это обработчик события AppDomain.CurrentDomain.AssemblyResolve.

Ответ 2

Мое полное решение моей проблемы состояло в использовании второй ссылки, предоставленной Дэвидом Хеффернаном. То, что я сделал, было 1. Ссылка на фиктивную dll в проекте. 2. Указано два события предварительной сборки.

xcopy /y "$(SolutionDir)\Assemblies\Lib\x86\(Assembly name)*" "$(TargetDir)"
xcopy /y "$(SolutionDir)\Assemblies\Lib\x64\(Assemble name)*" "$(TargetDir)"

3. и при запуске приложения в событии разрешения сборки изменилась соответствующая сборка в зависимости от платформы.

        var currentDomain = AppDomain.CurrentDomain;
        var location = Assembly.GetExecutingAssembly().Location;
        var assemblyDir = Path.GetDirectoryName(location);

        if (assemblyDir != null && (File.Exists(Path.Combine(assemblyDir, "(Assembly name).proxy.dll"))
                                    || !File.Exists(Path.Combine(assemblyDir, "(Assembly name).x86.dll"))
                                    || !File.Exists(Path.Combine(assemblyDir, "(Assembly name).x64.dll"))))
        {
            throw new InvalidOperationException("Found (Assembly name).proxy.dll which cannot exist. "
                + "Must instead have (Assembly name).x86.dll and (Assembly name).x64.dll. Check your build settings.");
        }

        currentDomain.AssemblyResolve += (sender, arg) =>
        {
            if (arg.Name.StartsWith("(Assembly name),", StringComparison.OrdinalIgnoreCase))
            {
                string fileName = Path.Combine(assemblyDir,
                    string.Format("(Assembly).{0}.dll", (IntPtr.Size == 4) ? "x86" : "x64"));
                return Assembly.LoadFile(fileName);
            }
            return null;
        };

Ответ 3

На самом деле я очень переживаю по этой теме, поэтому я решил опубликовать ответ в соответствии с тем, как я использовал в Pencil.Gaming. Во-первых, вам нужно "DllImport" две функции: одну из 32-разрядной dll и одну из 64-разрядной dll (или так, или dylib, независимо от используемой платформы).

static class Foo32 {
    [DllImport("32bitdll.dll")]
    internal static extern void Foo();
}
static class Foo64 {
    [DllImport("64bitdll.dll")]
    internal static extern void Foo();
}

Затем вам нужен промежуточный класс, содержащий делегаты, и импортировать их из 32 или 64-битного взаимодействия в зависимости от размера IntPtr (я не использую Environment.Is64BitProcess, так как это функция .NET 4)

internal delegate void FooDelegate();
static class FooDelegates {
    internal static FooDelegate Foo;

    static FooDelegates() {
        Type interop = (IntPtr.Size == 8) ? typeof(Foo64) : typeof(Foo32);
        FieldInfo[] fields = typeof(FooDelegates).GetFields(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
        foreach (FieldInfo fi in fields) {
            MethodInfo mi = glfwInterop.GetMethod(fi.Name, BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
            Delegate function = Delegate.CreateDelegate(fi.FieldType, mi);
            fi.SetValue(null, function);
        }
    }
}

И тогда я обычно использую "настоящий" класс, содержащий функцию, которую вы импортировали (хотя это технически не требуется):

public static class FooApi {
    public static void Foo() {
        FooDelegates.Foo();
    }
}

Это настоящая боль, если вам нужны только одна или две функции, но способ импорта делегатов действительно эффективен для библиотек/приложений большего масштаба. Возможно, вы захотите проверить Pencil.Gaming на github, так как он довольно широко использует этот метод (здесь является примером того, что он используется много).

Другим преимуществом этого метода является то, что он 100% кросс-платформенный и не зависит от каких-либо функций WinAPI.