2012-06-01 3 views
52

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

Существует статическая переменная, содержащая имя файла 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 определяется время компиляции, а не во время выполнения, так что правая длл не загружена в соответствии с контекстом исполнения.

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

+1

В чем разница между 64-битной и 32-разрядной dll? Есть ли что-то, что 32-битное не может сделать на 64? Если так, я бы просто использовал 32. –

+0

На 64-битной ОС решение о выполнении кода в чистом 64-битном или WOW64 (32-разрядном эмулированном) определяется при выполнении программы. Если программа выполняется в 32-битном режиме, она должна использовать dll, основанные на C, которые были скомпилированы соответственно на 32 и 64 бит. – Gilad

+1

Если вы действительно этого хотите, вам нужно обойти атрибут 'DllImport' и загрузить DLL самостоятельно, используя функции LoadLibrary', GetProcAddess' и' FreeLibrary'. Этот метод обсуждается [здесь] (http://stackoverflow.com/questions/2818011/set-dllimport-attribute-dynamically). Однако это довольно честная работа, и довольно просто ошибиться. Предоставление механизма P/Invoke для вас намного проще. Как отмечали другие, вероятно, этого не стоит, если вы можете просто вернуться к 32-разрядной DLL все время как самый низкий общий знаменатель. –

ответ

47

Я нашел самый простой способ сделать это, чтобы импортировать два метода с разными именами и вызвать правильный. 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); 
} 

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

+25

Для максимальной читаемости я бы предложил использовать свойство ['Environment.Is64BitProcess'] (http://msdn.microsoft.com/en-us/library/system.environment.is64bitprocess) вместо проверки размера' IntPtr типа. Конечно, это свойство было введено с .NET 4.0, поэтому, если вы ориентируетесь на более старую версию, она не будет доступна. –

+0

Это именно то, что я хотел сделать. Хотя в кодировании есть некоторые накладные расходы, он работает и выполняет эту работу. – Gilad

7

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

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

Что было бы правильным способом решить эту проблему?

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

Если есть необходимость для x64, вы можете:

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

  • Есть две отдельные сборки в целом, одна для x86 и одна для x64.

+1

Согласовано, это, безусловно, самое простое решение проблемы. Подавляющее большинство приложений, в частности бизнес-приложений, не получают преимуществ от 64-битной версии. Даже если могут быть некоторые незначительные улучшения производительности, увеличение накладных расходов на 64-битные файлы, как правило, отменяет их. Другое предложение, которое вы используете для использования одинаковых имен для библиотек DLL и развертывания правильной версии в зависимости от архитектуры системы, - это именно то, что Microsoft делает с помощью системных DLL. Это тоже хорошая ставка. –

+0

+1 для добавления dll во время установки! –

2

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

Here вы можете найти прохождение игры именно для вашего сценария (.NET DLL оберточной C++/CLI DLL, ссылающегося на родной DLL).

РЕКОМЕНДАЦИЯ:

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

+1

Мне не нужны два приложения. Это soooo 90s. – Gilad

+0

@ Gilad затем следуют приведенным ссылкам, они показывают некоторые параметры, которые, возможно, больше похожи на то, что вы хотите (ОЖИДАЙТЕ: он становится очень сложным) ... – Yahia

+0

@ Gilad BTW вы можете построить 2 (x86 и x64) и когда программа установки запускается он может определить, какой из них установить - пользователь получает ровно одно приложение ... – Yahia

0

Я использовал один из подходов meantioned по vcsjones:

«Изменение библиотеки DLL, чтобы иметь такое же имя, как MyDLL.dll, и при установке/развертывании времени, поставить право один на месте «.

Этот подход требует сохранения двух платформ сборки, хотя эта ссылка для получения более подробной информации: https://stackoverflow.com/a/6446638/38368

39

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

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

Хитрость заключается в том, чтобы вручную загрузить DLL с LoadLibrary перед CLR делает это. Затем он увидит, что 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 включена.

+1

Это самый худший ответ, наиболее подходящий для вас. Я успешно принял это для проекта FreeImage .NET Wrapper! Удивительная идея. Вместо этого я использовал 'Environment.Is64BitProcess' и разрешил путь для сборки, как это предлагает Kisdeds. Благодарю вас! – JanW

+0

Вы также можете вставлять как 32-разрядные, так и 64-разрядные библиотеки DLL в качестве ресурсов внутри .NET-библиотеки DLL и извлекать нужную/загружаемую LoadLibrary, если хотите, чтобы все было упаковано. – Matt

14

В этом случае я должен делать, как это (сделать 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); 
} 
1

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

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); 
} 
+0

Необходимо скопировать dll, чтобы приложение тратило больше времени. – lindexi

Смежные вопросы