Как указать путь [DllImport] во время выполнения?

На самом деле, я получил С++ (рабочую) DLL, которую я хочу импортировать в свой проект С#, чтобы вызвать его функции.

Он работает, когда я указываю полный путь к 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 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" желает параметр "const string" для каталога DLL.

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

Ответ 1

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

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

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

Прежде чем система будет искать DLL, она проверит следующее:

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

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

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

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

Просто напишите:

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

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

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

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

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

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

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

Ответ 2

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

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

Ответ 3

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

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

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

ИЗМЕНИТЬ

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

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, поэтому этот код не завершен. В реальном приложении вы должны позаботиться о выпуске загруженных модулей, чтобы избежать утечки памяти.

Ответ 4

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

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

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

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

Как раз во время выполнения перед вызовом функции DLLFunction (присутствующей в библиотеке С++) добавьте эту строку кода в код С#:

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

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

Ответ 5

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

Ответ 6

установить путь DLL в файле конфигурации

<add key="dllPath" value="C:\Users\UserName\YourApp\myLibFolder\myDLL.dll" />

перед вызовом dll в вашем приложении сделайте следующее

string dllPath= ConfigurationManager.AppSettings["dllPath"];    
   string appDirectory = Path.GetDirectoryName(dllPath);
   Directory.SetCurrentDirectory(appDirectory);

затем позвоните в DLL, и вы можете использовать, как показано ниже

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

Ответ 7

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

 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