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

Jsn*_*dnl 129 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);
Run Code Online (Sandbox Code Playgroud)

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

所以我希望我的代码更通用,如下所示:

/* 
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);
Run Code Online (Sandbox Code Playgroud)

最重要的是"DllImport"需要DLL目录的"const string"参数.

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

Cod*_*ray 168

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

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

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

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

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

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

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

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

写吧:

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

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

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

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

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

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

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

  • 对此的另一个小改进可能是从DLL名称中删除扩展名.Windows将自动添加`.dll`,其他系统将在Mono下添加相应的扩展名(例如Linux上的`.so`).如果担心可移植性,这可能会有所帮助. (13认同)
  • `SetDllDirectory`为+1.您也可以只更改`Environment.CurrentDirectory`,并从该路径评估所有相关的pathes! (5认同)
  • 甚至在发布之前,OP就明确表示他正在制作一个插件,因此将DLL放入微软的程序文件中并不是一件好事.此外,更改进程DllDirectory或CWD可能不是一个好主意,它们可能导致进程失败.另一方面,现在`AddDllDirectory` ...... (2认同)
  • 依赖于工作目录是一个潜在的严重安全漏洞@GameScripting,对于使用超级用户权限运行的东西尤其不明智.值得编写代码并进行设计工作以使其正确. (2认同)
  • 请注意,`DllImport`不仅仅是`LoadLibrary`的包装器.它[还考虑了程序集的目录`extern`方法定义在](https://github.com/dotnet/coreclr/blob/6bf04a47badd74646e21e70f4e9267c71b7bfd08/src/vm/dllimport.cpp#L6210).使用[`DefaultDllImportSearchPath`]可以额外限制`DllImport`搜索路径(https://msdn.microsoft.com/en-us/library/system.runtime.interopservices.defaultdllimportsearchpathsattribute(v = vs.110).aspx) . (2认同)

Mik*_*keP 35

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

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

  • @Code:似乎向我保证:[动态链接库搜索顺序](http://msdn.microsoft.com/en-us/library/ms682586.aspx).具体而言,"影响搜索的因素",第一点. (3认同)

Ran*_*Ran 24

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

另一种可以帮助你完成我认为你正在尝试的方法是LoadLibrary通过P/Invoke 使用native ,以便从你需要的路径加载.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);
    }
}
Run Code Online (Sandbox Code Playgroud)

注意:我没有打扰使用FreeLibrary,所以此代码不完整.在实际应用程序中,您应该注意释放已加载的模块以避免内存泄漏.


joe*_*joe 17

在所有其他好的答案中,在 .NET Core 3.0 之后,您可以使用NativeLibrary。例如,在 Linux 中,您没有这样的kernel32.dllNativeLibrary.Load并且Native.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);
        
Run Code Online (Sandbox Code Playgroud)

也可以看看:


RBT*_*RBT 5

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

string str = Path.GetTempPath() + "..\\myLibFolder\\myDLL.dll"; 
Run Code Online (Sandbox Code Playgroud)

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

[DllImport("myDLL.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int DLLFunction(int Number1, int Number2);
Run Code Online (Sandbox Code Playgroud)

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

string assemblyProbeDirectory = Path.GetTempPath() + "..\\myLibFolder\\myDLL.dll"; 
Directory.SetCurrentDirectory(assemblyProbeDirectory);
Run Code Online (Sandbox Code Playgroud)

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

  • 这对我有用。我的执行应用程序的“ bin”目录中有一个文件夹“ Modules”。在那里,我放置了托管dll和托管dll所需的一些非托管dll。使用此解决方案并在我的app.config中设置探测路径,使我能够动态加载所需的程序集。 (3认同)