关于c#:如何在运行时指定[DllImport]路径?

How can I specify a [DllImport] path at runtime?

实际上,我有一个C ++(工作中)的DLL,我想导入到我的C#项目中以调用它的功能。

当我指定DLL的完整路径时,它确实可以工作,如下所示:

1
2
3
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等),取决于要在其上运行的计算机/会话。

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

1
2
3
4
5
6
7
8
9
10
11
12
/*
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"参数。

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


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

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

实际上,甚至不是DllImport来处理它。无论您是否使用方便的托管包装器(P / Invoke编组器仅调用LoadLibrary),这都是用于控制事务的本机Win32 DLL加载规则。这些规则在此处详细列出,但重要的规则在此处摘录:

Before the system searches for a DLL, it checks the following:

  • If a DLL with the same module name is already loaded in memory, the system uses the loaded DLL, no matter which directory it is in. The system does not search for the DLL.
  • If the DLL is on the list of known DLLs for the version of Windows on which the application is running, the system uses its copy of the known DLL (and the known DLL's dependent DLLs, if any). The system does not search for the DLL.

If SafeDllSearchMode is enabled (the default), the search order is as follows:

  • The directory from which the application loaded.
  • The system directory. Use the GetSystemDirectory function to get the path of this directory.
  • The 16-bit system directory. There is no function that obtains the path of this directory, but it is searched.
  • The Windows directory. Use the GetWindowsDirectory function to get the path of this directory.
  • The current directory.
  • The directories that are listed in the PATH environment variable. Note that this does not include the per-application path specified by the App Paths registry key. The App Paths key is not used when computing the DLL search path.
  • 因此,除非您为DLL命名与系统DLL相同(除非在任何情况下都不应该这样做),否则默认搜索顺序将开始在加载应用程序的目录中查找。如果在安装过程中将DLL放在此处,则会找到它。如果仅使用相对路径,所有复杂的问题都会消失。

    写就好了:

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

    但是,如果由于某种原因该方法不起作用,并且您需要强制应用程序在DLL的其他目录中查找,则可以使用SetDllDirectory函数修改默认的搜索路径。
    请注意,根据文档:

    After calling SetDllDirectory, the standard DLL search path is:

  • The directory from which the application loaded.
  • The directory specified by the lpPathName parameter.
  • The system directory. Use the GetSystemDirectory function to get the path of this directory.
  • The 16-bit system directory. There is no function that obtains the path of this directory, but it is searched.
  • The Windows directory. Use the GetWindowsDirectory function to get the path of this directory.
  • The directories that are listed in the PATH environment variable.
  • 因此,只要您在第一次调用从DLL导入的函数之前调用此函数,就可以修改用于定位DLL的默认搜索路径。当然,这样做的好处是您可以将动态值传递给在运行时计算的该函数。使用DllImport属性是不可能的,因此您仍将在此处使用相对路径(仅DLL的名称),并依靠新的搜索顺序为您找到它。

    您必须P /调用此功能。声明看起来像这样:

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


    比Ran建议使用GetProcAddress更好,只需在对DllImport函数的调用(仅具有路径名的文件名)之前先调用LoadLibrary,它们将自动使用已加载的模块。

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


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

    另一种可以帮助您完成我认为正在尝试的操作的替代方法是通过P / Invoke使用本机LoadLibrary,以便从所需的路径加载.dll,然后使用GetProcAddress来获取从该.dll中引用所需的功能。然后使用它们创建可以调用的委托。

    为了使其易于使用,您可以将该委托设置为类中的一个字段,以便使用它就像调用成员方法一样。

    编辑

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    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目录中。

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

    现在,您可以使用const字符串继续使用DllImport语句,如下所示:

    1
    2
    [DllImport("myDLL.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern int DLLFunction(int Number1, int Number2);

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

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

    这只是指示CLR在程序运行时获得的目录路径中查找非托管C ++库。 Directory.SetCurrentDirectory调用将应用程序的当前工作目录设置为指定目录。如果您的myDll.dll存在于assemblyProbeDirectory路径表示的路径中,则它将被加载并且所需的函数将通过p / invoke调用。


    在配置文件中设置dll路径

    1
     

    在您的应用中调用dll之前,请执行以下操作

    1
    2
    3
    string dllPath= ConfigurationManager.AppSettings["dllPath"];    
       string appDirectory = Path.GetDirectoryName(dllPath);
       Directory.SetCurrentDirectory(appDirectory);

    然后调用dll,您可以像下面这样使用

    1
    2
     [DllImport("myDLL.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern int DLLFunction(int Number1, int Number2);

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


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
     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/zh-cn/library/aa288468(v=vs.71).aspx