You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET Framework 4.8程序DLL注入成功后无法正常弹出卸载问题

核心原因

.NET Framework 程序集的加载生命周期完全独立于Win32原生DLL模块管理逻辑,原生注入器的卸载接口根本无法操作CLR层面的引用:

  • 通用DLL注入器的Inject/Eject逻辑全部面向原生Win32 DLL设计:Eject方法的本质是在目标进程内调用FreeLibrary,将对应PE模块的引用计数递减至0后,从进程虚拟地址空间移除模块、解除文件占用。
  • 你注入的是基于.NET Framework 4.8开发的托管类库,这类DLL被加载到自带CLR的WPF进程后,会被直接映射到进程默认CLR AppDomain中,CLR会独立持有程序集的强引用。此时调用原生FreeLibrary仅能递减PE模块的Win32层引用计数,完全不会触发CLR释放托管程序集引用,这就是为什么Eject方法返回true(代表原生API调用成功)但DLL文件依然被进程锁定的根本原因。
  • 额外限制:目标WPF进程启动时已经初始化了默认CLR运行时,你注入的托管DLL直接复用该运行时,没有独立可销毁的CLR实例,无法通过卸载CLR的方式释放DLL。
可行实现方案

方案1:AppDomain隔离卸载(推荐,可实现内存、文件双无残留)

完全放弃注入器自带的原生Eject逻辑,在托管DLL内部自行实现CLR层面的生命周期管理:

  • 不要将核心业务逻辑直接实现在初始注入的DLL中,初始DLL只保留极小的引导逻辑:当注入器调用DLL导出入口时,引导逻辑通过AppDomain.CreateDomain创建一个配置了独立ApplicationBase路径的隔离AppDomain,所有业务代码(包括WPF相关的UI操作、事件订阅)全部加载到这个隔离AppDomain中执行,禁止任何业务对象跨域引用默认AppDomain的对象(尤其是WPF的DependencyObject、路由事件委托,跨域引用会导致后续AppDomain卸载失败)。
  • 需要卸载DLL时,先在引导逻辑中调用AppDomain.Unload释放你创建的隔离域,CLR会自动回收该域内加载的所有托管程序集,释放所有对应的文件锁。
  • 等待CLR卸载操作完成后,再调用注入器的Eject方法,释放最开始注入的引导DLL模块,此时即可完成彻底卸载,无文件锁定、无内存残留。

注意:绝对不能尝试直接卸载进程默认AppDomain,该操作会直接触发进程崩溃。

方案2:内存加载托管DLL(无需卸载逻辑,无文件锁)

如果不想实现复杂的AppDomain隔离逻辑,可以通过内存加载的方式从根源上避免文件锁定:

  • 初始注入阶段不要通过注入器直接LoadLibrary磁盘上的托管业务DLL,而是先注入一个极小的原生引导壳。
  • 引导壳在目标进程内拿到CLR入口后,直接读取磁盘上业务DLL的全量二进制字节,调用Assembly.Load(byte[] rawAssembly)重载以内存模式加载托管程序集,再通过反射调用你的业务入口(比如示例中的Spam方法)。
  • 这种加载模式下CLR不会锁定磁盘上的原始DLL文件,不需要执行任何Eject操作,随时可以修改、替换、删除磁盘上的DLL。唯一的残留是托管程序集会一直留在目标进程内存中直到进程退出,但不会产生任何文件占用问题。如果你的业务DLL有其他依赖类库,所有依赖项也需要通过字节数组方式加载,否则依然会锁定对应依赖的磁盘文件。

方案3:临时副本方案(仅调试场景使用,不推荐生产环境)

如果只是本地调试阶段需要快速迭代DLL、不想频繁重启目标进程,可以在注入前将待注入的DLL及其依赖复制到系统临时目录下的随机命名子文件夹中,用临时副本执行注入操作。这种方式下原始开发目录的DLL文件不会被锁定,可以随时编译替换,缺点是每次注入都会在进程内存中留下一份DLL残留,长期运行会造成内存泄漏,仅适合临时调试。

现有代码的问题

当前测试代码完全走原生DLL注入流程,没有任何CLR层面的生命周期处理,必然无法卸载托管DLL:

var process = Process.GetProcessesByName("ModsOptimizer").FirstOrDefault();
_injector = new Injector(process);
_injectionDllPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Injection.dll");
var address = _injector.Inject(_injectionDllPath); // 仅通过Win32 LoadLibrary加载DLL,触发CLR将托管程序集加载到默认AppDomain
_injector.CallFunction(_injectionDllPath, "Spam", default); // 业务逻辑直接运行在进程默认AppDomain,CLR持有强引用
_injector.Eject(_injectionDllPath); // 仅调用原生FreeLibrary递减Win32层模块引用计数,完全不处理CLR层的托管引用

之前尝试的FreeLibrary、FreeLibraryAndExitThread全部是Win32原生API,根本无法操作CLR内部的托管程序集引用计数,调用后自然无法释放文件锁。

内容的提问来源于stack exchange,提问作者Alex Wyler

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 04:16:20