Вопрос по .net, 64bit, c# – Использование параллельных сборок для загрузки x64 или x32 версии DLL

58

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

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

Ваш Ответ

5   ответов
2

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

23

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

  • Works for ALL DLLs that were not found
  • Can be turned on and off
  • AppDomain.CurrentDomain.SetupInformation.ApplicationBase is used instead of Path.GetFullPath() because the current directory might be different, e.g. in hosting scenarios, Excel might load your plugin but the current directory will not be set to your DLL.

  • Environment.Is64BitProcess is used instead of PROCESSOR_ARCHITECTURE, as we should not depend on what the OS is, rather how this process was started - it could have been x86 process on a x64 OS. Before .NET 4, use IntPtr.Size == 8 instead.

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

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;
    }
}
"it could have been x86 process on a x64 OS" - это бессмысленно. Конечно, единственное, что определяет, как вы, как программист, выбираете компилировать свое собственное приложение? Если вы не компилируете как «какой-либо процессор», то какой смысл использовать в первую очередь?
Кто-нибудь знает, работает ли это для неуправляемых сборок? Я не могу получитьAssemblyResolve событие для неуправляемых сборок.
Это библиотека. Может использоваться разными приложениями. Если кто-то, кто создает настоящий exe-файл, решает не использовать «какой-либо процессор», мы должны с этим справиться. Следовательно, как правило, всегда используйте разрядность процесса, а не хост. Предположения злые :)
PROCESSOR_ARCHITECTURE фактически отражает процесс, а не машину; пытатьсяStart->RunИНГ%SYSTEMROOT%\SysWOW64\cmd /V:ON /q /c "echo This process is: !PROCESSOR_ARCHITECTURE! && pause"
1

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

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

3

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

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

Отличие от решений, использующих Resolver, состоит в том, что в моем случае с SPSS сама сборка упаковывается и загружает 3 библиотеки DLL, содержащие фактический код SPSS. Cd, 11, где были скомпилированы для x86 или x64 (таким образом, сборка x86 и x64 имела свой собственный набор из 3 Cllll). Здесь резолвер не будет работать (он выберет только правильную упаковочную сборку, но не будет работать с dll для сборочных упаковок).
63

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

  1. Make sure default .NET assembly loading mechanism ("Fusion" engine) can't find either x86 or x64 version of the platform-specific assembly
  2. Before the main application attempts loading the platform-specific assembly, install a custom assembly resolver in the current AppDomain
  3. Now when the main application needs the platform-specific assembly, Fusion engine will give up (because of step 1) and call our custom resolver (because of step 2); in the custom resolver we determine current platform and use directory-based lookup to load appropriate DLL.

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

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

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

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

Step 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();
        }
    }
}

Step 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

Step 3: Основная программа разделена на две части. & Quot; Загрузчик & Quot; содержит основную точку входа для исполняемого файла и регистрирует собственный преобразователь сборок в текущем домене приложения:

// 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;
        }
    }
}

& Quot; программа & Quot; является "реальным" реализация приложения (обратите внимание, что 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();
        }
    }
}

Step 4: Скомпилируйте основное приложение в командной строке:

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

Step 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 / *.

PROCESSOR_ARCHITECTURE правильно - это на самом деле отражает процесс, а не машина.
Мы используем аналогичный подход, но событие присоединяется в статическом конструкторе - таким образом, в некоторых случаях вложение происходит до того, как .NET пытается загрузить другую сборку.
Кто-нибудь знает, работает ли это для неуправляемых сборок? Я не могу получитьAssemblyResolve событие для неуправляемых сборок.
Пожалуйста, обновите, чтобы использовать Environment.Is64BitProcess - он может отличаться от процессора на машине. В противном случае - очень хорошо ответил - мы используем что-то подобное.

Похожие вопросы