如何在C#插件中卸载DllImport导入的C++ DLL以支持重编译
解决C++ DLL被进程占用导致无法重新编译的问题
核心问题分析
通过[DllImport]导入的非托管C++ DLL会被加载到第三方应用的进程空间,CLR不会自动卸载它——哪怕你的C#插件DLL被卸载,非托管DLL仍会保持加载状态,导致编译器无法覆盖文件。要解决这个问题,必须手动控制非托管DLL的加载与卸载生命周期。
具体实现方案
1. 替换[DllImport]为手动加载/卸载逻辑
使用Windows原生APILoadLibrary、GetProcAddress和FreeLibrary来手动管理C++ DLL的加载与释放,替代[DllImport]的自动机制:
using System; using System.Runtime.InteropServices; public class CppDllWrapper { // 导入Windows内核API [DllImport("kernel32.dll", SetLastError = true)] private static extern IntPtr LoadLibrary(string lpFileName); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool FreeLibrary(IntPtr hModule); [DllImport("kernel32.dll", SetLastError = true)] private static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName); private IntPtr _dllHandle; // 定义与C++函数匹配的委托类型 private delegate int SampleCppFuncDelegate(int input); private SampleCppFuncDelegate _sampleFunc; // 加载C++ DLL并绑定目标函数 public bool Initialize(string dllPath) { _dllHandle = LoadLibrary(dllPath); if (_dllHandle == IntPtr.Zero) { // 可添加错误日志逻辑 return false; } // 获取函数地址并转换为委托 IntPtr funcPtr = GetProcAddress(_dllHandle, "SampleCppFunction"); if (funcPtr == IntPtr.Zero) { FreeLibrary(_dllHandle); return false; } _sampleFunc = Marshal.GetDelegateForFunctionPointer<SampleCppFuncDelegate>(funcPtr); return true; } // 包装C++函数调用 public int CallSampleFunction(int input) { return _sampleFunc?.Invoke(input) ?? throw new InvalidOperationException("C++ DLL未加载"); } // 卸载C++ DLL public void Cleanup() { if (_dllHandle != IntPtr.Zero) { FreeLibrary(_dllHandle); _dllHandle = IntPtr.Zero; _sampleFunc = null; } } }
2. 在插件卸载时触发DLL释放
确保你的C#插件实现第三方应用提供的卸载回调接口(比如IPlugin.Unload),在回调中调用Cleanup方法:
public class YourCSharpPlugin : IPlugin { private CppDllWrapper _cppWrapper; public void OnLoad() { _cppWrapper = new CppDllWrapper(); _cppWrapper.Initialize(@"C:\Path\To\Your\cpp.dll"); } public void OnUnload() { _cppWrapper?.Cleanup(); // 其他插件资源清理逻辑 } }
3. 关键注意事项
- 清除所有委托引用:如果代码中还有其他地方持有C++函数的委托引用,
FreeLibrary可能无法真正释放DLL,需确保所有相关委托在卸载前被置为null。 - 检查
FreeLibrary返回值:若返回false,说明DLL的引用计数仍不为0,可能存在其他组件占用,可通过Marshal.GetLastWin32Error()获取错误码排查。 - 避免静态引用:不要在静态类或静态字段中保存DLL句柄或委托,否则静态引用会阻止GC清理,导致
FreeLibrary失效。
备选方案:加载临时副本规避文件占用
如果手动管理句柄仍有问题,可在插件加载时将C++ DLL复制到临时目录,加载临时副本。这样原文件不会被占用,编译时可直接覆盖:
public bool InitializeFromTemp(string originalDllPath) { string tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); Directory.CreateDirectory(tempDir); string tempDllPath = Path.Combine(tempDir, Path.GetFileName(originalDllPath)); File.Copy(originalDllPath, tempDllPath, overwrite: true); return LoadLibrary(tempDllPath) != IntPtr.Zero; }
注意插件卸载时需删除临时目录,避免冗余文件堆积。
内容的提问来源于stack exchange,提问作者chrisk
相关产品推荐
相关产品推荐

