P/Invok执行时的搜索顺序

P/Invoke 不会自动知道你的自定义 DLL 放在哪里**,它遵循一套严格的**DLL 搜索顺序**

一、默认搜索顺序(为什么有时"不用管"也能找到?)

当你写下:

csharp

复制代码
 [DllImport("MyNativeLib.dll")]
 public static extern int MyFunction();

CLR 会按照以下顺序搜索 MyNativeLib.dll

  1. 进程的当前工作目录 (通常是你的 .exe 启动目录)。

  2. 系统目录C:\Windows\System32)。

  3. 16位系统目录C:\Windows\System)。

  4. Windows 目录C:\Windows)。

  5. PATH 环境变量中列出的所有目录

所以,如果你把自定义 DLL 放在以下位置,确实可以"不用管路径":

  • ✅ 与 .exe 同目录(最常见)。

  • ✅ 系统 PATH 包含的目录。

  • ✅ 已提前加载到进程中(例如其他组件已加载)。

但如果你把 DLL 放在任意自定义位置(如 D:\MyLibs\),默认就找不到了,会抛出 DllNotFoundException

二、四种解决方案(从简单到强大)

方案1:直接放对位置(最简单,推荐)

适用场景:你完全控制部署环境。

直接把 .dll 放在输出目录 (与 .exe 同级),无需任何额外代码。这是绝大多数桌面应用的做法。

text

复制代码
 C:\MyApp\
   ├── MyApp.exe          (你的托管程序)
   └── MyNativeLib.dll    (自定义库)

方案2:运行时改变当前目录(临时方案)

适用场景 :DLL 相对固定,但不想污染 .exe 目录。

csharp

cs 复制代码
 string dllDir = @"D:\MyLibraries";
 Environment.CurrentDirectory = dllDir; // ⚠️ 影响整个进程
 ​
 // 然后正常调用 DllImport

缺点:会修改整个进程的当前目录,可能导致其他文件操作混乱。不推荐。

方案3:使用 [DllImport] 的绝对路径(直接指定)

适用场景:DLL 位置完全固定,甚至在不同机器路径不同(可通过配置读取)。

cs 复制代码
 [DllImport(@"D:\MyLibraries\MyNativeLib.dll")]
 public static extern int MyFunction();

✅ 优点:简单暴力,直接指定完整路径。 ❌ 缺点:硬编码路径,缺乏灵活性。且路径字符串必须与部署时完全一致。

方案4:手动加载 DLL + 函数指针(最灵活,推荐用于插件/动态库)

适用场景:DLL 路径由配置文件决定、或需要动态切换、或库名随版本变化。

核心思想 :用 LoadLibrary(kernel32)提前把 DLL 加载进进程,P/Invoke 会复用已加载的模块,从而自动找到路径。

csharp

cs 复制代码
 using System;
 using System.Runtime.InteropServices;
 ​
 public class NativeMethods
 {
     [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
     public static extern IntPtr LoadLibrary(string lpFileName);
 ​
     [DllImport("kernel32.dll")]
     public static extern bool FreeLibrary(IntPtr hModule);
 ​
     // 声明函数,但不指定 DllImport 路径(运行时已加载)
     [DllImport("MyNativeLib.dll")]
     public static extern int MyFunction();
 }
 ​
 // 使用
 IntPtr handle = NativeMethods.LoadLibrary(@"D:\MyLibs\MyNativeLib.dll");
 if (handle == IntPtr.Zero) throw new Exception("加载失败");
 try
 {
     int result = NativeMethods.MyFunction(); // ✅ 现在能找到
 }
 finally
 {
     NativeMethods.FreeLibrary(handle);
 }

✅ 优点:路径完全可控 ,支持运行时决定。 ⚠️ 注意:[DllImport] 声明的 DLL 名称必须与 LoadLibrary 传入的文件名逻辑匹配(不需要完整路径,但基本名必须一致)。CLR 发现同名模块已加载,就不会再去搜索。

🔧 方案4增强版:使用 AssemblyLoadContext(.NET Core 3+)

如果你在使用 .NET Core/.NET 5+,官方推荐用 AssemblyLoadContext 加载非托管 DLL,实现更精细的隔离和路径控制。

csharp

cs 复制代码
 using System.Runtime.Loader;
 ​
 public class NativeLibraryResolver : AssemblyLoadContext
 {
     protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
     {
         string path = Path.Combine(@"D:\MyLibs", unmanagedDllName + ".dll");
         return LoadUnmanagedDllFromPath(path);
     }
 }
 ​
 // 使用时
 var resolver = new NativeLibraryResolver();
 resolver.LoadUnmanagedDll("MyNativeLib"); // 触发自定义路径解析

适用场景:大型应用、插件系统、需要动态卸载库。

三、总结:到底要不要管路径?

你的情况 是否需要手动处理路径? 推荐方案
DLL 放在 exe 同目录 ❌ 不需要 直接 [DllImport]
DLL 放在 System32PATH 路径 ❌ 不需要 直接 [DllImport]
DLL 放在自定义目录,且位置固定 ✅ 需要 方案3(绝对路径)或方案4(LoadLibrary)
DLL 路径由配置/用户指定 ✅ 必须处理 方案4(LoadLibrary)
你在写 NuGet 包,需打包本地DLL ⚠️ 需特殊处理 使用 .targets 文件将 DLL 复制到输出目录
相关推荐
用户298698530142 小时前
C# Word自动化:轻松插入特殊符号,告别手动烦恼!
后端·c#·.net
光泽雨2 小时前
C#库文件调用逻辑
开发语言·c#
kylezhao20194 小时前
C# 中的类型转换详解
c#
游乐码4 小时前
c#冒泡排序
c#·排序算法
玩c#的小杜同学15 小时前
源代码保卫战:给C# 程序(混淆、加壳与反逆向实战)
开发语言·笔记·c#
游乐码20 小时前
c#递归函数
算法·c#
柒儿吖1 天前
DDlog 高性能异步日志库在 OpenHarmony 的 lycium 适配与分步测试
c++·c#·openharmony
柒儿吖1 天前
基于 lycium 在 OpenHarmony 上交叉编译 utfcpp 完整实践
c++·c#·harmonyos
柒儿吖1 天前
基于 lycium 在 OpenHarmony 上交叉编译 komrad36-CRC 完整实践
c++·c#·harmonyos