2012-01-12 9 views
102

Фактически, я получил C++ (рабочую) DLL, которую я хочу импортировать в свой проект C#, чтобы вызвать его функции.Как указать путь [DllImport] во время выполнения?

Это делает работу, когда я указать полный путь к DLL, например:

string str = "C:\\Users\\userName\\AppData\\Local\\myLibFolder\\myDLL.dll"; 
[DllImport(str, CallingConvention = CallingConvention.Cdecl)] 
public static extern int DLLFunction(int Number1, int Number2); 

Проблема заключается в том, что он собирается быть устанавливаемым проектом, поэтому папка пользователя не будет таким же (например: pierre, paul, jack, mum, dad, ...) в зависимости от компьютера/сеанса, на котором он будет запущен.

Так что я хочу, чтобы мой код, чтобы быть немного более общий характер, например:

/* 
goes right to the temp folder of the user 
    "C:\\Users\\userName\\AppData\\Local\\temp" 
then go to parent folder 
    "C:\\Users\\userName\\AppData\\Local" 
and finally go to the DLL's folder 
    "C:\\Users\\userName\\AppData\\Local\\temp\\myLibFolder" 
*/ 

string str = Path.GetTempPath() + "..\\myLibFolder\\myDLL.dll"; 
[DllImport(str, CallingConvention = CallingConvention.Cdecl)] 
public static extern int DLLFunction(int Number1, int Number2); 

Большая сделка является то, что «DllImport» желание параметра «Уст строка» для каталога библиотеки DLL.

Так что мой вопрос: Что можно сделать в этом случае?

+8

Просто разверните DLL в той же папке, что и EXE, поэтому вам не нужно ничего делать, кроме указания имени DLL без пути. Другие схемы возможны, но все это хлопотно. –

+2

Дело в том, что это будет MS Office Excel Add In, поэтому я не считаю, что dll в каталоге exe будет лучшим решением ... – Jsncrdnl

+6

Ваше решение неверное. Не размещайте файлы в Windows или системных папках. Они выбрали эти имена по какой-то причине: они предназначены для системных файлов Windows. Вы не создаете один из них, потому что вы не работаете в Microsoft в команде Windows. Помните, что вы узнали в детском саду о том, как использовать вещи, которые не принадлежат вам без разрешения, и помещать ваши файлы где угодно, но там. –

ответ

139

Вопреки предложениям некоторых других ответов, использование атрибута DllImport по-прежнему является правильным.

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

Фактически, это даже не DllImport, который обрабатывает его. Это правила загрузки загружаемых файлов Win32 DLL, которые управляют вещами, независимо от того, используете ли вы удобные управляемые обертки (маршаллер P/Invoke просто вызывает LoadLibrary). Эти правила перечислены в деталях here, но важные из них взяты здесь:

перед системой поиска для DLL, он проверяет следующее:

  • Если DLL с тем же имя модуля уже загружено в память, система использует загруженную DLL, независимо от того, в какой директории она находится. Система не ищет DLL.
  • Если DLL находится в списке известных DLL для версии Windows, на которой работает приложение, система использует свою копию известной DLL (и известных DLL-зависимых библиотек DLL, если таковые имеются). Система не ищет DLL.

Если SafeDllSearchMode включена (по умолчанию), порядок поиска выглядит следующим образом:

  1. каталог, из которого загружается приложение.
  2. Системный каталог. Используйте функцию GetSystemDirectory, чтобы получить путь к этому каталогу.
  3. 16-разрядный системный каталог. Нет функции, которая получает путь к этому каталогу, но выполняется поиск.
  4. Каталог Windows. Используйте функцию GetWindowsDirectory, чтобы получить путь к этому каталогу.
  5. Текущий каталог.
  6. Каталоги, перечисленные в переменной окружения PATH. Обратите внимание, что это не включает путь для каждого приложения, указанный в разделе реестра приложений. Ключ App Paths не используется при вычислении пути поиска DLL.

Так что, если вы не именовании DLL то же самое, как системный DLL (который вы, очевидно, не следует делать, никогда, ни при каких обстоятельствах), порядок поиска по умолчанию будет начать поиск в каталоге из которого была загружена ваша заявка. Если вы разместите DLL там во время установки, он будет найден. Все сложные проблемы уходят, если вы просто используете относительные пути.

Просто написать:

[DllImport("MyAppDll.dll")] // relative path; just give the DLL's name 
static extern bool MyGreatFunction(int myFirstParam, int mySecondParam); 

Но если не работу по какой-либо причине, и вам необходимо, чтобы заставить приложение искать в другом каталоге для DLL, вы можете изменить поиск по умолчанию с использованием SetDllDirectory function.
Обратите внимание, что, согласно документации:

После вызова SetDllDirectory, стандартный поиск DLL путь:

  1. каталог, из которого загружается приложение.
  2. Адрес, заданный параметром lpPathName.
  3. Системный каталог. Используйте функцию GetSystemDirectory, чтобы получить путь к этому каталогу.
  4. 16-разрядный системный каталог. Нет функции, которая получает путь к этому каталогу, но выполняется поиск.
  5. Каталог Windows. Используйте функцию GetWindowsDirectory, чтобы получить путь к этому каталогу.
  6. Каталоги, перечисленные в переменной окружения PATH.

Так до тех пор, как вы называете эту функцию перед вызовом функции импортируемой из DLL в первый раз, вы можете изменить путь поиска, используемый по умолчанию для поиска библиотек DLL. Выгода, конечно, состоит в том, что вы можете передать значение динамическое значение этой функции, которая вычисляется во время выполнения. Это невозможно с атрибутом DllImport, поэтому вы по-прежнему будете использовать относительный путь (только имя DLL) и полагаться на новый порядок поиска, чтобы найти его для вас.

Вам нужно будет P/вызвать эту функцию. Декларация выглядит так:

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] 
static extern bool SetDllDirectory(string lpPathName); 
+12

Другим незначительным усовершенствованием в этом может быть отказ от расширения из имени DLL. Windows автоматически добавит '.dll', а другие системы добавят соответствующее расширение в Mono (например,' .so' в Linux). Это может помочь, если переносимость является проблемой. – jheddings

+4

+1 для 'SetDllDirectory'. Вы также можете просто изменить «Environment.CurrentDirectory», и все относительные пути будут оцениваться с этого пути! – GameScripting

+1

Еще до того, как это было опубликовано, ОП разъяснила, что он создает плагин, поэтому размещение DLL в файлах программ Microsoft является своего рода не стартером. Кроме того, изменение процесса DllDirectory или CWD может быть не очень хорошей идеей, они могут привести к сбою процесса. Теперь 'AddDllDirectory' с другой стороны ... –

0

DllImport будет работать нормально, если не указан полный путь до тех пор, пока dll находится где-то на пути к системе. Возможно, вы сможете временно добавить папку пользователя в этот путь.

+0

Я попытался помещать его в переменную окружения среды, но он по-прежнему считается не постоянным (логично, я думаю) – Jsncrdnl

16

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

альтернатива, которая может помочь вам сделать то, что я думаю, что вы пытаетесь, чтобы использовать родной LoadLibrary через P/Invoke для того, чтобы загрузить DLL-файл с пути вам нужно, а затем использовать GetProcAddress, чтобы получить ссылку на нужную вам функцию .dll. Затем используйте их для создания делегата, который вы можете вызвать.

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

EDIT

Вот фрагмент кода, который работает, и показывает, что я имел в виду.

class Program 
{ 
    static void Main(string[] args) 
    { 
     var a = new MyClass(); 
     var result = a.ShowMessage(); 
    } 
} 

class FunctionLoader 
{ 
    [DllImport("Kernel32.dll")] 
    private static extern IntPtr LoadLibrary(string path); 

    [DllImport("Kernel32.dll")] 
    private static extern IntPtr GetProcAddress(IntPtr hModule, string procName); 

    public static Delegate LoadFunction<T>(string dllPath, string functionName) 
    { 
     var hModule = LoadLibrary(dllPath); 
     var functionAddress = GetProcAddress(hModule, functionName); 
     return Marshal.GetDelegateForFunctionPointer(functionAddress, typeof (T)); 
    } 
} 

public class MyClass 
{ 
    static MyClass() 
    { 
     // Load functions and set them up as delegates 
     // This is just an example - you could load the .dll from any path, 
     // and you could even determine the file location at runtime. 
     MessageBox = (MessageBoxDelegate) 
      FunctionLoader.LoadFunction<MessageBoxDelegate>(
       @"c:\windows\system32\user32.dll", "MessageBoxA"); 
    } 

    private delegate int MessageBoxDelegate(
     IntPtr hwnd, string title, string message, int buttons); 

    /// <summary> 
    /// This is the dynamic P/Invoke alternative 
    /// </summary> 
    static private MessageBoxDelegate MessageBox; 

    /// <summary> 
    /// Example for a method that uses the "dynamic P/Invoke" 
    /// </summary> 
    public int ShowMessage() 
    { 
     // 3 means "yes/no/cancel" buttons, just to show that it works... 
     return MessageBox(IntPtr.Zero, "Hello world", "Loaded dynamically", 3); 
    } 
} 

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

+0

Существует управляемый экземпляр для LoadLibrary (в классе Assembly). – Luca

+0

Если бы у вас был пример кода, мне было бы легче понять! ^^ (На самом деле, это немного туманно) – Jsncrdnl

+1

@Luca Piccioni: Если вы имели в виду Assembly.LoadFrom, это загружает сборки .NET, а не родные библиотеки. Что ты имел в виду? – Ran

-12

Если все не удается, просто поместите DLL в папку windows\system32. Компилятор найдет его. Укажите DLL для загрузки из с: DllImport("user32.dll"..., установите EntryPoint = "my_unmanaged_function" импортировать нужную неуправляемую функцию к вашему C# приложение:

using System; 
using System.Runtime.InteropServices; 

class Example 
{ 
    // Use DllImport to import the Win32 MessageBox function. 

    [DllImport ("user32.dll", CharSet = CharSet.Auto)] 
    public static extern int MessageBox 
     (IntPtr hWnd, String text, String caption, uint type); 

    static void Main() 
    { 
     // Call the MessageBox function using platform invoke. 
     MessageBox (new IntPtr(0), "Hello, World!", "Hello Dialog", 0);  
    } 
} 

Источника и даже больше DllImport примеров: http://msdn.microsoft.com/en-us/library/aa288468(v=vs.71).aspx

+0

Хорошо, я согласен с вашим решением использовать папку win32 (самый простой способ сделать это), но как вы предоставляете доступ к этой папке отладчику Visual Studio (а также к скомпилированному приложению)? (За исключением ручного запуска его в качестве администратора) – Jsncrdnl

+0

Если это используется для чего-то большего, чем для отладочной помощи, оно попадает в любой обзор (безопасность или другое) в моей книге. –

+15

Это довольно ужасное решение. Системная папка предназначена для * системных * DLL. Теперь вам нужны привилегии администратора и полагаются на плохие практики только потому, что вы ленивы. – MikeP

24

Даже лучше, чем предложение Ран использования GetProcAddress, просто вызовите LoadLibrary перед любыми вызовами функций DllImport (только с именем файла без пути), и они автоматически загружают загруженный модуль.

Я использовал этот метод для выбора во время выполнения, нужно ли загружать 32-разрядную или 64-разрядную родную DLL без необходимости изменять кучу функций P/Invoke-d. Прикрепите код загрузки в статическом конструкторе для типа, который имеет импортированные функции, и все будет работать нормально.

+1

Я не уверен, что это гарантировано. Или, если это происходит только в текущей версии фреймворка. – CodesInChaos

+2

@Code: Кажется мне гарантировано: [Порядок поиска библиотеки Dynamic-Link] (http://msdn.microsoft.com/en-us/library/ms682586.aspx). В частности, «Факторы, влияющие на поиск», пункт 1. –

+0

Ницца. Ну, мое решение имеет небольшое дополнительное преимущество, так как даже имя функции не обязательно должно быть статическим и известно во время компиляции. Если у вас есть две функции с одной и той же сигнатурой и другим именем, вы можете вызвать их с помощью кода 'FunctionLoader'. – Ran

1

Пока вы знаете каталог, в котором ваши библиотеки C++ могут быть найдены во время выполнения, это должно быть простым. Я ясно вижу, что это имеет место в вашем коде. Ваш myDll.dll будет присутствовать внутри каталога myLibFolder во временной папке текущего пользователя.

string str = Path.GetTempPath() + "..\\myLibFolder\\myDLL.dll"; 

Теперь вы можете продолжать использовать оператор DllImport, используя константную строку, как показано ниже:

[DllImport("myDLL.dll", CallingConvention = CallingConvention.Cdecl)] 
public static extern int DLLFunction(int Number1, int Number2); 

Только во время выполнения перед вызовом DLLFunction функции (присутствует в C++ библиотеки) добавить строку код в C# код:

string assemblyProbeDirectory = Path.GetTempPath() + "..\\myLibFolder\\myDLL.dll"; 
Directory.SetCurrentDirectory(assemblyProbeDirectory); 

Это просто инструктирует CLR искать неуправляемые C++ библиотек на пути к каталогу, который вы получили во время выполнения вашей программы. Directory.SetCurrentDirectory вызывает задание текущего рабочего каталога приложения в указанный каталог. Если ваш myDLL.dll присутствует на пути, представленном путем assemblyProbeDirectory, тогда он будет загружен, и желаемая функция будет вызвана через p/invoke.

+2

Это сработало для меня. У меня есть папка «Модули», расположенная в каталоге «bin» моего исполняющего приложения. Там я размещаю управляемую dll и некоторые неуправляемые dll, которые требует управляемая dll. Использование этого решения И установка пути исследования в моем app.config позволяет мне динамически загружать необходимые сборки. – WBuck

+0

Для людей, использующих Azure Функции: string workingDirectory = Path.GetFullPath (Path.Combine (executeContext.FunctionDirectory, @ ".. \ bin")); –

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