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

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

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

string str = "C:UsersuserNameAppDataLocalmyLibFoldermyDLL.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:UsersuserNameAppDataLocaltemp"
then go to parent folder
    "C:UsersuserNameAppDataLocal"
and finally go to the DLL's folder
    "C:UsersuserNameAppDataLocaltempmyLibFolder"
*/

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

дело в том, что" DllImport "желает параметр" const string " для каталога DLL.

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

6 ответов


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

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

на самом деле, это даже не DllImport, обрабатывает его. Это собственные правила загрузки Win32 DLL, которые управляют вещами, независимо от того, используете ли вы удобные управляемые оболочки (P/Invoke marshaller просто вызывает 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. каталог, указанный в

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

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


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

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


пока вы знаете каталог, в котором можно найти библиотеки C++ во время выполнения, это должно быть просто. Я ясно вижу, что это так в вашем коде. Ваш 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 функция (присутствует в C++ библиотека) добавить эту строку кода в C# код:

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

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


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


если все не удается, просто поместите DLL в windows\system32 папка . Компилятор найдет его. Укажите DLL для загрузки с помощью:DllImport("user32.dll"..., set 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=против 71).aspx