Как я могу указать путь [DllImport] во время выполнения? - PullRequest
122 голосов
/ 12 января 2012

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

Это работает, когда я указываю полный путь к 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);

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

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

/* 
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" требует параметр "const string" для каталога DLL.

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

Ответы [ 7 ]

160 голосов
/ 14 января 2012

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

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

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

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

  • Если в память уже загружена DLL с тем же именем модуля, система использует загруженную DLL независимо от того, в каком каталоге она находится. Система не выполняет поиск DLL.
  • Если библиотека DLL находится в списке известных библиотек DLL для версии Windows, в которой запущено приложение, система использует свою копию известной библиотеки 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 функцию .
Обратите внимание, что согласно документации:

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

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

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

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

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern bool SetDllDirectory(string lpPathName);
31 голосов
/ 12 января 2012

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

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

24 голосов
/ 12 января 2012

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

5 голосов
/ 19 января 2017

Пока вы знаете каталог, где ваши библиотеки 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 path, тогда он будет загружен и нужная функция будет вызываться через p / invoke.

3 голосов
/ 16 мая 2019

установить путь к 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);
0 голосов
/ 12 января 2012

Если все не удается, просто поместите 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 голосов
/ 12 января 2012

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

...