P/Invoke 不会自动知道你的自定义 DLL 放在哪里**,它遵循一套严格的**DLL 搜索顺序**
一、默认搜索顺序(为什么有时"不用管"也能找到?)
当你写下:
csharp
[DllImport("MyNativeLib.dll")]
public static extern int MyFunction();
CLR 会按照以下顺序搜索 MyNativeLib.dll:
-
进程的当前工作目录 (通常是你的
.exe启动目录)。 -
系统目录 (
C:\Windows\System32)。 -
16位系统目录 (
C:\Windows\System)。 -
Windows 目录 (
C:\Windows)。 -
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 放在 System32 或 PATH 路径 | ❌ 不需要 | 直接 [DllImport] |
| DLL 放在自定义目录,且位置固定 | ✅ 需要 | 方案3(绝对路径)或方案4(LoadLibrary) |
| DLL 路径由配置/用户指定 | ✅ 必须处理 | 方案4(LoadLibrary) |
| 你在写 NuGet 包,需打包本地DLL | ⚠️ 需特殊处理 | 使用 .targets 文件将 DLL 复制到输出目录 |