Как указать путь [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