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

时间:2012-01-12 13:45:47

标签: c# c++ dll constants 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);

问题在于它将是一个可安装的项目,因此用户的文件夹将不同(例如:皮埃尔,保罗,杰克,妈妈,爸爸......),具体取决于计算机/会话的运行情况上。

所以我希望我的代码更加通用,比如:

/* 
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”参数。

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

8 个答案:

答案 0 :(得分:159)

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

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

事实上,它甚至不是DllImport来处理它。这是管理事物的本机Win32 DLL加载规则,无论您是否使用方便的托管包装器(P / Invoke marshaller只调用LoadLibrary)。这些规则非常详细地列举here,但重要的是在这里摘录:

  

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

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

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

     
      
  1. 加载应用程序的目录。
  2.   
  3. 系统目录。使用GetSystemDirectory函数获取此目录的路径。
  4.   
  5. 16位系统目录。没有函数可以获取此目录的路径,但会搜索它。
  6.   
  7. Windows目录。使用GetWindowsDirectory函数获取此目录的路径。
  8.   
  9. 当前目录。
  10.   
  11. PATH环境变量中列出的目录。请注意,这不包括App Paths注册表项指定的每个应用程序路径。计算DLL搜索路径时不使用App Paths键。
  12.   

因此,除非您将DLL命名为系统DLL(在任何情况下您显然都不应该这样做),否则默认搜索顺序将从您的应用程序所在的目录开始查找加载。如果您在安装过程中将DLL放在那里,它将被找到。如果你只是使用相对路径,那么所有复杂的问题就会消失。

只需写下:

[DllImport("MyAppDll.dll")] // relative path; just give the DLL's name
static extern bool MyGreatFunction(int myFirstParam, int mySecondParam);

但是如果 因任何原因不能工作,并且您需要强制应用程序查找DLL的其他目录,则可以使用{{3}修改默认搜索路径}}。
请注意,根据文档:

  

调用SetDllDirectory后,标准DLL搜索路径为:

     
      
  1. 加载应用程序的目录。
  2.   
  3. lpPathName参数指定的目录。
  4.   
  5. 系统目录。使用GetSystemDirectory函数获取此目录的路径。
  6.   
  7. 16位系统目录。没有函数可以获取此目录的路径,但会搜索它。
  8.   
  9. Windows目录。使用GetWindowsDirectory函数获取此目录的路径。
  10.   
  11. PATH环境变量中列出的目录。
  12.   

因此,只要在第一次调用从DLL导入的函数之前调用此函数,就可以修改用于查找DLL的默认搜索路径。当然,好处是您可以将动态值传递给在运行时计算的此函数。使用DllImport属性是不可能的,因此您仍然会使用相对路径(仅限DLL的名称),并依赖新的搜索顺序为您找到它。

你必须P / Invoke这个功能。声明如下:

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

答案 1 :(得分:31)

甚至比Ran的使用GetProcAddress的建议更好,只需在调用LoadLibrary函数之前调用DllImport(只有一个没有路径的文件名),他们会自动使用加载的模块。

我已经使用此方法在运行时选择是否加载32位或64位本机DLL而无需修改一堆P / Invoke-d函数。将加载代码粘贴到具有导入函数的类型的静态构造函数中,它们都可以正常工作。

答案 2 :(得分:24)

如果你需要一个不在路径或应用程序位置的.dll文件,那么我认为你不能这样做,因为DllImport是一个属性,属性只是元数据在类型,成员和其他语言元素上设置。

可以帮助您完成我认为您正在尝试的内容的替代方法是使用本机LoadLibrary到P / Invoke,以便从您需要的路径加载.dll,然后使用{ {1}}从.dll获取您需要的函数的引用。然后使用这些来创建一个可以调用的委托。

为了便于使用,您可以将此委托设置为类中的字段,以便使用它看起来像调用成员方法。

修改

这是一个有效的代码片段,并显示了我的意思。

GetProcAddress

注意:我没有费心使用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); } } ,因此此代码不完整。在实际应用程序中,您应该注意释放已加载的模块以避免内存泄漏。

答案 3 :(得分:4)

只要您知道在运行时可以找到C ++库的目录,这应该很简单。我可以清楚地看到你的代码就是这种情况。您的myDll.dll将出现在当前用户的临时文件夹中的myLibFolder目录中。

string str = Path.GetTempPath() + "..\\myLibFolder\\myDLL.dll"; 

现在您可以使用const字符串继续使用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路径表示的路径上,那么它将被加载,并且将通过p / invoke调用所需的函数。

答案 4 :(得分:0)

只要dll位于系统路径的某个位置,只要指定完整路径,

DllImport就可以正常工作。您可以暂时将用户的文件夹添加到路径中。

答案 5 :(得分:0)

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

答案 6 :(得分:0)

在所有其他好的答案中,在 .NET Core 3.0 之后,您可以使用 NativeLibrary。 例如在 Linux 中你没有这样的 kernerl32.dllNaiveLirary.LoadNative.SetDllImportResolver 可能是要走的路:

        static MyLib()
        {
            //Avaliable 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);
        

答案 7 :(得分:-13)

如果全部失败,只需将DLL放入windows\system32文件夹即可。编译器会找到它。 指定要加载的DLL:DllImport("user32.dll"...,设置EntryPoint = "my_unmanaged_function"以将您想要的非托管函数导入C#app:

 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