Фактически, у меня есть 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.
Итак, мой вопрос: что можно сделать в этом случае?
Ответы:
Вопреки предложениям некоторых других ответов, использование
DllImport
атрибута по-прежнему является правильным подходом.Я, честно говоря, не понимаю, почему вы не можете поступить так же, как все в мире, и указать относительный путь к своей DLL. Да, путь, по которому ваше приложение будет установлено, различается на компьютерах разных людей, но в основном это универсальное правило, когда дело доходит до развертывания.
DllImport
Механизм разработан с учетом этого.На самом деле, дело даже не в
DllImport
этом. Это собственные правила загрузки Win32 DLL, которые управляют вещами, независимо от того, используете ли вы удобные управляемые оболочки (маршаллер P / Invoke просто вызываетLoadLibrary
). Эти правила перечислены здесь очень подробно , но здесь приведены выдержки из наиболее важных:Итак, если вы не называете свою DLL так же, как системная DLL (чего, очевидно, не следует делать ни при каких обстоятельствах), порядок поиска по умолчанию начнет поиск в каталоге, из которого было загружено ваше приложение. Если вы поместите туда DLL во время установки, она будет найдена. Все сложные проблемы исчезнут, если вы просто используете относительные пути.
Просто пиши:
[DllImport("MyAppDll.dll")] // relative path; just give the DLL's name static extern bool MyGreatFunction(int myFirstParam, int mySecondParam);
Но если это не сработает по какой-либо причине и вам нужно заставить приложение искать DLL в другом каталоге, вы можете изменить путь поиска по умолчанию с помощью этой
SetDllDirectory
функции .Обратите внимание, что согласно документации:
Итак, если вы вызываете эту функцию перед вызовом функции, импортированной из DLL, в первый раз, вы можете изменить путь поиска по умолчанию, используемый для поиска DLL. Преимущество, конечно же, в том, что вы можете передать этой функции динамическое значение, которое вычисляется во время выполнения. Это невозможно с
DllImport
атрибутом, поэтому вы все равно будете использовать относительный путь (только имя DLL) и полагаться на новый порядок поиска, чтобы найти его за вас.Вам нужно будет P / Invoke этой функции. Объявление выглядит так:
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] static extern bool SetDllDirectory(string lpPathName);
источник
.dll
а другие системы добавят соответствующее расширение в Mono (например,.so
в Linux). Это может помочь, если переносимость вызывает беспокойство.SetDllDirectory
. Вы также можете просто изменить,Environment.CurrentDirectory
и все относительные пути будут оцениваться с этого пути!AddDllDirectory
с другой стороны ...DllImport
это больше, чем просто оболочкаLoadLibrary
. Также учитывается каталог сборки, вextern
которой определен метод . НаDllImport
пути поиска может быть дополнительно ограничен с помощьюDefaultDllImportSearchPath
.Даже лучше, чем предложение Рана об использовании
GetProcAddress
, просто сделайте вызовLoadLibrary
перед любыми вызовамиDllImport
функций (только с именем файла без пути), и они будут использовать загруженный модуль автоматически.Я использовал этот метод, чтобы выбрать во время выполнения, загружать ли 32-разрядную или 64-разрядную собственную DLL без необходимости изменять кучу функций P / Invoke-d. Вставьте код загрузки в статический конструктор для типа, который имеет импортированные функции, и все будет работать нормально.
источник
FunctionLoader
код.Если вам нужен файл .dll, которого нет на пути или в расположении приложения, то я не думаю, что вы можете это сделать, потому что
DllImport
это атрибут, а атрибуты - это только метаданные, которые установлены для типов, членов и других языковые элементы.Альтернатива, которая может помочь вам выполнить то, что, как я думаю, вы пытаетесь, - использовать собственный
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
, поэтому этот код не полный. В реальном приложении вы должны освободить загруженные модули, чтобы избежать утечки памяти.источник
Если вы знаете каталог, в котором можно найти ваши библиотеки 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
call устанавливает текущий рабочий каталог приложения в указанный каталог. Если вашmyDLL.dll
присутствует на пути, представленномassemblyProbeDirectory
path, он будет загружен, и нужная функция будет вызвана через p / invoke.источник
установить путь к 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);
источник
DllImport будет работать нормально без указания полного пути, если dll находится где-то на системном пути. Вы можете временно добавить в путь папку пользователя.
источник
Если все не удается, просто поместите 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источник