Использование параллельных сборок для загрузки x64 или x32 версии DLL - PullRequest
58 голосов
/ 20 сентября 2008

У нас есть две версии управляемой сборки C ++, одна для x86 и одна для x64. Эта сборка вызывается приложением .net, совместимым для AnyCPU. Мы внедряем наш код с помощью установки с копированием файлов и хотели бы продолжить это.

Можно ли использовать манифест сборки Side-by-Side для загрузки сборки x86 или x64 соответственно, когда приложение динамически выбирает архитектуру своего процессора? Или есть другой способ сделать это в развертывании копии файла (например, не используя GAC)?

Ответы [ 5 ]

63 голосов
/ 01 октября 2008

Я создал простое решение, способное загружать сборку для конкретной платформы из исполняемого файла, скомпилированного как AnyCPU. Используемая техника может быть кратко изложена следующим образом:

  1. Убедитесь, что механизм загрузки сборки .NET по умолчанию (механизм "Fusion") не может найти версию сборки для платформы x86 или x64
  2. Перед тем, как основное приложение попытается загрузить сборку для конкретной платформы, установите пользовательский распознаватель сборок в текущем домене приложений
  3. Теперь, когда основному приложению требуется сборка для конкретной платформы, механизм Fusion сдается (из-за шага 1) и вызывает наш собственный распознаватель (из-за шага 2); в пользовательском преобразователе мы определяем текущую платформу и используем поиск на основе каталогов для загрузки соответствующей DLL.

Чтобы продемонстрировать эту технику, я прилагаю краткое руководство на основе командной строки. Я протестировал полученные двоичные файлы в Windows XP x86, а затем в Vista SP1 x64 (скопировав двоичные файлы, как при развертывании).

Примечание 1 : "csc.exe" - это компилятор C-sharp. В этом руководстве предполагается, что он находится на вашем пути (в моих тестах использовалось «C: \ WINDOWS \ Microsoft.NET \ Framework \ v3.5 \ csc.exe»)

Примечание 2 : я рекомендую вам создать временную папку для тестов и запустить командную строку (или powershell), чей текущий рабочий каталог настроен на это расположение, например

(cmd.exe)
C:
mkdir \TEMP\CrossPlatformTest
cd \TEMP\CrossPlatformTest

Шаг 1 : Сборка для конкретной платформы представлена ​​простой библиотекой классов C #:

// file 'library.cs' in C:\TEMP\CrossPlatformTest
namespace Cross.Platform.Library
{
    public static class Worker
    {
        public static void Run()
        {
            System.Console.WriteLine("Worker is running");
            System.Console.WriteLine("(Enter to continue)");
            System.Console.ReadLine();
        }
    }
}

Шаг 2 : Мы компилируем сборки для платформы, используя простые команды командной строки:

(cmd.exe from Note 2)
mkdir platform\x86
csc /out:platform\x86\library.dll /target:library /platform:x86 library.cs
mkdir platform\amd64
csc /out:platform\amd64\library.dll /target:library /platform:x64 library.cs

Шаг 3 : Основная программа разделена на две части. «Bootstrapper» содержит основную точку входа для исполняемого файла и регистрирует пользовательский преобразователь сборок в текущем домене приложения:

// file 'bootstrapper.cs' in C:\TEMP\CrossPlatformTest
namespace Cross.Platform.Program
{
    public static class Bootstrapper
    {
        public static void Main()
        {
            System.AppDomain.CurrentDomain.AssemblyResolve += CustomResolve;
            App.Run();
        }

        private static System.Reflection.Assembly CustomResolve(
            object sender,
            System.ResolveEventArgs args)
        {
            if (args.Name.StartsWith("library"))
            {
                string fileName = System.IO.Path.GetFullPath(
                    "platform\\"
                    + System.Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE")
                    + "\\library.dll");
                System.Console.WriteLine(fileName);
                if (System.IO.File.Exists(fileName))
                {
                    return System.Reflection.Assembly.LoadFile(fileName);
                }
            }
            return null;
        }
    }
}

«Программа» - это «реальная» реализация приложения (обратите внимание, что App.Run был вызван в конце Bootstrapper.Main):

// file 'program.cs' in C:\TEMP\CrossPlatformTest
namespace Cross.Platform.Program
{
    public static class App
    {
        public static void Run()
        {
            Cross.Platform.Library.Worker.Run();
        }
    }
}

Шаг 4 : скомпилировать главное приложение в командной строке:

(cmd.exe from Note 2)
csc /reference:platform\x86\library.dll /out:program.exe program.cs bootstrapper.cs

Шаг 5 : Теперь мы закончили. Структура созданного нами каталога должна быть следующей:

(C:\TEMP\CrossPlatformTest, root dir)
    platform (dir)
        amd64 (dir)
            library.dll
        x86 (dir)
            library.dll
    program.exe
    *.cs (source files)

Если вы теперь запустите program.exe на 32-битной платформе, будет загружена платформа \ x86 \ library.dll; если вы запустите program.exe на 64-битной платформе, будет загружена платформа \ amd64 \ library.dll. Обратите внимание, что я добавил Console.ReadLine () в конце метода Worker.Run, чтобы вы могли использовать диспетчер задач / обозреватель процесса для исследования загруженных библиотек DLL или использовать отладчик Visual Studio / Windows для подключения к процессу, чтобы увидеть стек вызовов и т. д.

При запуске program.exe наш пользовательский распознаватель сборок присоединяется к текущему домену приложения. Как только .NET начинает загружать класс Program, он видит зависимость от сборки библиотеки, поэтому он пытается загрузить ее. Однако, такая сборка не найдена (потому что мы скрыли ее в подкаталогах platform / *). К счастью, наш пользовательский распознаватель знает нашу хитрость и на основе текущей платформы пытается загрузить сборку из соответствующей подкаталога platform / *.

23 голосов
/ 31 марта 2012

Моя версия, похожая на @Milan, но с несколькими важными изменениями:

  • Работает для ВСЕХ DLL, которые не были найдены
  • Может быть включен и выключен
  • AppDomain.CurrentDomain.SetupInformation.ApplicationBase используется вместо Path.GetFullPath(), поскольку текущий каталог может быть другим, например в сценариях размещения Excel может загрузить ваш плагин, но текущая папка не будет установлена ​​на вашу DLL.

  • Environment.Is64BitProcess используется вместо PROCESSOR_ARCHITECTURE, поскольку мы не должны зависеть от того, какая ОС, а от того, как этот процесс был запущен - это мог быть процесс x86 в операционной системе x64. До .NET 4 вместо нее используйте IntPtr.Size == 8.

Вызывайте этот код в статическом конструкторе некоторого основного класса, который загружается раньше всего.

public static class MultiplatformDllLoader
{
    private static bool _isEnabled;

    public static bool Enable
    {
        get { return _isEnabled; }
        set
        {
            lock (typeof (MultiplatformDllLoader))
            {
                if (_isEnabled != value)
                {
                    if (value)
                        AppDomain.CurrentDomain.AssemblyResolve += Resolver;
                    else
                        AppDomain.CurrentDomain.AssemblyResolve -= Resolver;
                    _isEnabled = value;
                }
            }
        }
    }

    /// Will attempt to load missing assembly from either x86 or x64 subdir
    private static Assembly Resolver(object sender, ResolveEventArgs args)
    {
        string assemblyName = args.Name.Split(new[] {','}, 2)[0] + ".dll";
        string archSpecificPath = Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase,
                                               Environment.Is64BitProcess ? "x64" : "x86",
                                               assemblyName);

        return File.Exists(archSpecificPath)
                   ? Assembly.LoadFile(archSpecificPath)
                   : null;
    }
}
3 голосов
/ 04 июня 2012

Посмотрите на SetDllDirectory. Я использовал его для динамической загрузки сборки IBM spss для x64 и x86. Это также решало пути для несборочных библиотек поддержки DLL, загружаемых сборками, в моем случае это был случай с spss dll.

http://msdn.microsoft.com/en-us/library/ms686203%28VS.85%29.aspx

2 голосов
/ 20 сентября 2008

Вы можете использовать утилиту corflags , чтобы принудительно загрузить exe-файл AnyCPU в качестве исполняемого файла x86 или x64, но это не полностью соответствует требованию развертывания копии файла, если вы не выберете, какой exe-файл копировать на основе цель.

1 голос
/ 21 ноября 2016

Это решение может работать и для неуправляемых сборок. Я создал простой пример, подобный великому примеру Милана Гардиана. Пример, который я создал, динамически загружает управляемую DLL C ++ в DLL C #, скомпилированную для платформы Any CPU. В решении используется пакет nuget InjectModuleInitializer для подписки на событие AssemblyResolve до загрузки зависимостей сборки.

https://github.com/kevin-marshall/Managed.AnyCPU.git

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...