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

Использование 32-битной или 64-битной DLL в С# DllImport

Вот ситуация, я использую dll на основе C в своем приложении dot.net. Есть 2 библиотеки DLL, один из которых 32 бит называется MyDll32.dll, а другой - 64-битная версия под названием MyDll64.dll.

Существует статическая переменная, содержащая имя DLL файла: string DLL_FILE_NAME.

и используется следующим образом:

[DllImport(DLL_FILE_NAME, CallingConvention=CallingConvention.Cdecl, EntryPoint=Func1")]
private static extern int is_Func1(int var1, int var2);

Простой до сих пор.

Как вы можете себе представить, программное обеспечение скомпилировано с включенным "Any CPU".

У меня также есть следующий код, чтобы определить, должна ли система использовать 64-битный файл или 32-битный файл.

#if WIN64
        public const string DLL_FILE_NAME = "MyDll64.dll";
#else
        public const string DLL_FILE_NAME = "MyDll32.dll";        
#endif

Теперь вы должны увидеть проблему. DLL_FILE_NAME определяется во время компиляции, а не во время выполнения, поэтому правильная dll не загружается в соответствии с контекстом выполнения.

Каким будет правильный способ справиться с этой проблемой? Я не хочу два файла исполнения (один для 32-битного, а другой для 64-битного)? Как установить DLL_FILE_NAME, прежде чем он будет использоваться в инструкции DllImport?

4b9b3361

Ответ 1

Я нашел самый простой способ сделать это - импортировать два метода с разными именами и вызвать правильный. DLL не будет загружаться до тех пор, пока вызов не будет выполнен, так что это нормально:

[DllImport("MyDll32.dll", EntryPoint = "Func1", CallingConvention = CallingConvention.Cdecl)]
private static extern int Func1_32(int var1, int var2);

[DllImport("MyDll64.dll", EntryPoint = "Func1", CallingConvention = CallingConvention.Cdecl)]
private static extern int Func1_64(int var1, int var2);

public static int Func1(int var1, int var2) {
    return IntPtr.Size == 8 /* 64bit */ ? Func1_64(var1, var2) : Func1_32(var1, var2);
}

Конечно, если у вас много импорта, это может стать довольно громоздким для поддержания вручную.

Ответ 2

Вот еще одна альтернатива, требующая, чтобы две библиотеки DLL имели одно и то же имя и размещались в разных папках. Например:

  • win32/MyDll.dll
  • win64/MyDll.dll

Хитрость заключается в том, чтобы вручную загрузить DLL с помощью LoadLibrary до того, как CLR сделает это. Затем он увидит, что a MyDll.dll уже загружен и использует его.

Это можно легко сделать в статическом конструкторе родительского класса.

static class MyDll
{
    static MyDll()
    {            
        var myPath = new Uri(typeof(MyDll).Assembly.CodeBase).LocalPath;
        var myFolder = Path.GetDirectoryName(myPath);

        var is64 = IntPtr.Size == 8;
        var subfolder = is64 ? "\\win64\\" : "\\win32\\";

        LoadLibrary(myFolder + subfolder + "MyDll.dll");
    }

    [DllImport("kernel32.dll")]
    private static extern IntPtr LoadLibrary(string dllToLoad);

    [DllImport("MyDll.dll")]
    public static extern int MyFunction(int var1, int var2);
}

EDIT 2017/02/01: используйте Assembly.CodeBase, чтобы он работал, даже если Shadow Copying включен.

Ответ 3

В этом случае я должен сделать это (сделайте 2 папки, x64 и x86 + поместите соответствующую DLL, С ИМЯ ИМЕНИ, в обе папки):

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

class Program {
    static void Main(string[] args) {
        var path = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
        path = Path.Combine(path, IntPtr.Size == 8 ? "x64" : "x86");
        bool ok = SetDllDirectory(path);
        if (!ok) throw new System.ComponentModel.Win32Exception();
    }
    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern bool SetDllDirectory(string path);
}

Ответ 4

Существует статическая переменная, содержащая имя файла DLL

Это не статическая переменная. Это постоянный, во время компиляции. Вы не можете изменить постоянную времени компиляции во время выполнения.

Каким будет правильный способ справиться с этой проблемой?

Честно говоря, я бы порекомендовал только таргетинг на x86 и забыл о 64-битной версии все вместе и разрешил вашему приложению работать на WOW64, если у вашего приложения не было убедительной необходимости работать как x64.

Если требуется x64, вы можете:

  • Измените DLL файлы с тем же именем, например MyDll.dll, и при установке/развертывании, установите правильный. (Если ОС x64, разверните 64-разрядную версию DLL, в противном случае - версию x86).

  • У вас есть две отдельные сборки: одна для x86 и одна для x64.

Ответ 5

То, что вы описываете, называется "бок о бок сборке" (две версии одной и той же сборки, одна 32 и другая 64 бит)... Я думаю, вы найдете их полезными:

Здесь вы можете найти пошаговое руководство для своего сценария (перенос DLL в .NET DLL, связанный с локальной библиотекой DLL).

Рекомендация:

Просто создайте его как x86 и сделайте с ним... или у вас есть 2 сборки (один x86 и один x64)... так как вышеупомянутые методы довольно сложны...

Ответ 6

альтернативный подход может быть

public static class Sample
{
    public Sample()
    {

        string StartupDirEndingWithSlash = System.IO.Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName) + "\\";
        string ResolvedDomainTimeFileName = StartupDirEndingWithSlash + "ABCLib_Resolved.dll";
        if (!File.Exists(ResolvedDomainTimeFileName))
        {
            if (Environment.Is64BitProcess)
            {
                if (File.Exists(StartupDirEndingWithSlash + "ABCLib_64.dll"))
                    File.Copy(StartupDirEndingWithSlash + "ABCLib_64.dll", ResolvedDomainTimeFileName);
            }
            else
            {
                if (File.Exists(StartupDirEndingWithSlash + "ABCLib_32.dll"))
                    File.Copy(StartupDirEndingWithSlash + "ABCLib_32.dll", ResolvedDomainTimeFileName);
            }
        }
    }

    [DllImport("ABCLib__Resolved.dll")]
    private static extern bool SomeFunctionName(ref int FT);
}

Ответ 7

Я использовал один из подходов, обозначаемый vcsjones:

"Измените DLL файлы с тем же именем, как MyDll.dll, и при установке/развертывании, установите правильный вариант.

Этот подход требует поддержки двух платформ построения, хотя см. эту ссылку для более подробной информации: fooobar.com/questions/110154/...

Ответ 8

Хитрость, которую я использую для V8.Net, заключается в следующем:

  1. Создайте новый проект "интерфейс прокси" С# со всеми определениями для переключения между различными архитектурами. В моем случае проект был назван V8.Net-ProxyInterface; пример:
 public unsafe static class V8NetProxy
    {
    #if x86
            [DllImport("V8_Net_Proxy_x86")]
    #elif x64
            [DllImport("V8_Net_Proxy_x64")]
    #else
            [DllImport("V8_Net_Proxy")] // (dummy - NOT USED!)
    #endif
            public static extern NativeV8EngineProxy* CreateV8EngineProxy(bool enableDebugging, void* debugMessageDispatcher, int debugPort);

Это проект, на который вы будете ссылаться. НЕ ссылаться на следующие два:

  1. Создайте еще два проекта для генерации x64 и x86 версий библиотеки. Это ОЧЕНЬ ПРОСТО: просто скопируйте и вставьте, чтобы дублировать файл .csproj в той же папке и переименовать их. В моем случае файл проекта был переименован в V8.Net-ProxyInterface-x64 и V8.Net-ProxyInterface-x86, затем я добавил проекты в свое решение. Откройте параметры проекта для каждого из них в Visual Studio и убедитесь, что в имени Assembly Name указано Assembly Name x64 или x86. На данный момент у вас есть 3 проекта: первый проект-заполнитель и два архитектурно-ориентированных проекта. Для 2 новых проектов:

    a) Откройте настройки проекта интерфейса x64, перейдите на вкладку " Build ", выберите " All Platforms для Platform вверху, затем введите x64 в Conditional compilation symbols.

    б) Откройте настройки проекта интерфейса x86, перейдите на вкладку " Build ", выберите " All Platforms для Platform вверху, затем введите x86 в Conditional compilation symbols.

  2. Откройте Build->Configuration Manager... и убедитесь, что x64 выбран в качестве платформы для проектов x64, а x86 выбран для проектов x86, для ОБА конфигурации Debug AND Release.

  3. Убедитесь, что 2 новых проекта интерфейса (для x64 и x86) выводятся в одно и то же место вашего хост-проекта (см. настройку проекта Build->Output path).

  4. Последнее волшебство: в статическом конструкторе для моего движка я быстро присоединяюсь к распознавателю сборки:

static V8Engine()
{
    AppDomain.CurrentDomain.AssemblyResolve += Resolver;
}

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

var currentExecPath = Assembly.GetExecutingAssembly().Location;
var platform = Environment.Is64BitProcess ? "x64" : "x86";
var filename = "V8.Net.Proxy.Interface." + platform + ".dll"
return Assembly.LoadFrom(Path.Combine(currentExecPath , fileName));

Наконец, перейдите к своему хост-проекту в обозревателе решений, разверните References, выберите первый фиктивный проект, созданный на шаге 1, щелкните его правой кнопкой мыши, чтобы открыть свойства, и установите для параметра " Copy Local значение false. Это позволяет вам разрабатывать с ОДНЫМ именем для каждой функции P/Invoke, используя при этом распознаватель, чтобы выяснить, какую из них на самом деле загрузить.

Обратите внимание, что сборочный загрузчик запускается только при необходимости. Он запускается (в моем случае) автоматически системой CLR при первом доступе к классу двигателя. То, как это сработает, зависит от того, как разработан ваш хост-проект.

Ответ 9

На всякий случай, если кто-то это прочитает. Я хочу оставить здесь свое решение: у меня была та же проблема, и я в конечном итоге использовал Fody Costura

Файлы DLL будут поставляться как встроенные ресурсы, а библиотека заботится о разрядности.

Вы можете найти пример для SQLite здесь

Проблема, с которой я столкнулся, заключалась в том, что ваше приложение должно иметь доступ к папке Windows Temp для создания сборок из ресурса. Если вам это не нужно, вы можете отключить его с помощью параметра конфигурации.

Ответ 10

У меня была такая же проблема, и в итоге я использовал Fody Costura

Файлы DLL будут поставляться как встроенные ресурсы, а библиотека заботится о разрядности.

Вы можете найти пример для SQLite здесь

Проблема, с которой я столкнулся, заключалась в том, что ваше приложение должно иметь доступ к папке Windows Temp для создания сборок из ресурса. Если вам это не нужно, вы можете отключить его с помощью параметра конфигурации.