如何在运行时指定[ DllImport ]路径?

事实上,我得到了一个 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);

问题是,这将是一个可安装的项目,所以用户的文件夹将不会是相同的(例如: Pierre,Paul,Jack,Mom,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'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”希望 DLL 的目录有一个“ const string”参数。

所以我的问题是: 在这种情况下可以做些什么?

176808 次浏览

只要 dll 位于系统路径上的某个位置,DllImport 就可以在不指定完整路径的情况下正常工作。您可以将用户的文件夹临时添加到路径中。

如果你需要。Dll 文件,那么我不认为你可以这样做,因为 DllImport是一个属性,属性只是元数据设置的类型,成员和其他语言元素。

另一种可以帮助您完成我认为您正在尝试的任务的方法是通过 P/Invoke 使用本机 LoadLibrary,以便加载。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,所以这段代码不完整。在实际的应用程序中,您应该注意释放已加载的模块,以避免内存泄漏。

如果全部失败,只需将 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

比 Ran 建议的使用 GetProcAddress更好的方法是,在任何对 DllImport函数的调用之前调用 LoadLibrary(只有一个没有路径的文件名) ,它们将自动使用加载的模块。

我使用这个方法在运行时选择是加载32位还是64位本地 DLL,而不必修改一堆 P/Invoke-d 函数。将加载代码粘贴到具有导入函数的类型的静态构造函数中,这样就可以很好地工作了。

与其他一些答案的建议相反,使用 DllImport属性仍然是正确的方法。

我真的不明白为什么你不能像世界上其他人一样,为你的 DLL 指定一个 亲戚路径。是的,在不同人的计算机上安装应用程序的路径是不同的,但在部署时,这基本上是一个通用规则。DllImport机制的设计考虑到了这一点。

事实上,甚至不是 DllImport处理它。无论您是否使用方便的托管包装器(P/Invoke 编组器只调用 LoadLibrary) ,都由本机 Win32 DLL 加载规则进行管理。这些规则详细地列举了 给你,但重要的规则摘录如下:

在系统搜索 DLL 之前,它会检查以下内容:

  • 如果内存中已经加载了具有相同模块名称的 DLL,则系统将使用加载的 DLL,而不管它在哪个目录中。系统不搜索 DLL。
  • 如果该 DLL 位于运行应用程序的 Windows 版本的已知 DLL 列表中,系统将使用其已知 DLL 的副本(以及已知 DLL 的依赖 DLL (如果有的话))。系统不搜索 DLL。

如果启用了 SafeDllSearchMode(默认值) ,则搜索顺序如下:

  1. 从中加载应用程序的目录。
  2. 系统目录。使用 GetSystemDirectory函数获取此目录的路径。
  3. 16位系统目录。没有获取此目录的路径的函数,但它被搜索。
  4. Windows 目录。使用 GetWindowsDirectory函数获取此目录的路径。
  5. 工作目录。
  6. 在 ABc0环境变量中列出的目录。注意,这不包括由 AppPath 注册表项指定的每个应用程序路径。计算 DLL 搜索路径时不使用 AppPath 键。

因此,除非您将 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. 在 ABc0环境变量中列出的目录。

因此,只要在第一次调用从 DLL 导入的函数之前调用此函数,就可以修改用于定位 DLL 的默认搜索路径。当然,这样做的好处是可以向这个在运行时计算的函数传递一个 充满活力值。这对于 DllImport属性来说是不可能的,因此您仍将在那里使用相对路径(仅 DLL 的名称) ,并依赖于新的搜索顺序来为您查找它。

你必须调用这个函数,声明如下:

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

如果您知道 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);

在调用 C # 代码中的 DLLFunction函数(C + + 库中的函数)之前,请添加以下代码行:

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

这指示。NETCLR 在运行时获得的目录路径中查找非托管 C + + 库。调用 Directory.SetCurrentDirectory会将应用程序的当前工作目录设置为指定的目录。如果你的 myDLL.dll出现在由 assemblyProbeDirectory路径表示的路径,那么它将被加载。然后您可以通过 p/call 调用所需的函数。

在配置文件中设置 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);

在所有其他好的答案中,在.NET Core 3.0之后,您可以使用 本地图书馆。 例如,在 Linux 中,你没有这样的 内核32.dllNativeLibrary.LoadNative.SetDllImportResolver可以是这样的:

        static MyLib()
{
//Available for .NET Core 3+
NativeLibrary.SetDllImportResolver(typeof(MyLib).Assembly, ImportResolver);
}


private static IntPtr ImportResolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath)
{
IntPtr libHandle = IntPtr.Zero;
if (libraryName == "MyLib")
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
libHandle = NativeLibrary.Load("xxxx.dll");
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
libHandle = NativeLibrary.Load("xxxx.so");
}
}
return libHandle;
}


[DllImport("MyLib", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr foo(string name);
        

自从。NET Core 3.0-也可以使用。NET 5 & .NET 6-您可以使用 NativeLibrary.Load(string)在运行时动态加载非托管 DLL,您可以通过 P/Invoke 使用它。

有关详细信息,请参阅这里的答案: https://stackoverflow.com/a/69958827/211672

//[?] Method Sample;
[System.Runtime.InteropServices.DllImport("ramdom_Kernel32.dll")] //[!] Error Sample
public dynamic MethodWithDllImport(){
  

}


partial static Main(){
try{
//[?] Exception Cannot Be Handled over the Attribute;
//    handle where it is called;
MethodWithDllImport();
}
catch{
//[?] use overloaded\other name methods
}
}