如何用AppDomain动态加载卸载外部DLL并解决文件找不到异常
解决跨AppDomain加载外部DLL并实现卸载的问题
我明白你遇到的困境——用Assembly.LoadFile能加载但没法卸载,换成AppDomain又遇到文件找不到的错误,还得复制DLL到项目目录才行。这其实是两个核心问题:AppDomain的程序集搜索路径配置错误,以及跨域传递Assembly对象导致的意外加载。下面给你完整的解决方案:
问题根源分析
- ApplicationBase配置错误:你之前把新AppDomain的
ApplicationBase设为当前项目目录,CLR会默认在这个目录下查找程序集,而你的目标DLL在其他路径,自然找不到。 - 跨域对象传递的陷阱:直接把新AppDomain里加载的
Assembly对象返回到当前域,CLR会自动把这个DLL加载到当前AppDomain,导致即使卸载新域,当前域仍持有DLL引用,根本没法真正卸载。
完整解决方案
我们需要把所有对目标DLL的操作都封装在一个继承MarshalByRefObject的代理类中,让逻辑完全在新AppDomain内执行,同时正确配置AppDomain的搜索路径。
步骤1:创建跨域代理类
这个类负责在新AppDomain内加载DLL、创建实例并调用方法,所有操作都不会泄露到当前域:
using System; using System.Linq; using System.Reflection; public class DomainProxy : MarshalByRefObject { private object _targetInstance; private Type _targetType; // 初始化:加载DLL并创建目标类实例 public void Initialize(string dllPath, string targetClassName) { try { // 因为AppDomain的ApplicationBase已设为DLL所在目录,LoadFrom可以直接找到 Assembly assembly = Assembly.LoadFrom(dllPath); _targetType = assembly.GetExportedTypes().First(t => t.Name == targetClassName); _targetInstance = Activator.CreateInstance(_targetType); } catch (Exception ex) { throw new InvalidOperationException($"初始化目标类失败:{ex.Message}", ex); } } // 调用目标方法 public void InvokeTargetMethod(string methodName, object[] parameters) { if (_targetInstance == null || _targetType == null) { throw new InvalidOperationException("代理未初始化,请先调用Initialize方法"); } try { _targetType.InvokeMember( methodName, BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance, null, _targetInstance, parameters ); } catch (Exception ex) { throw new InvalidOperationException($"调用方法失败:{ex.Message}", ex); } } // 重写生命周期方法,让代理对象在AppDomain卸载前一直存活 public override object InitializeLifetimeService() { return null; // 无限期存活,避免被GC回收 } }
步骤2:修改主程序调用逻辑
正确配置AppDomain的搜索路径,并通过代理类执行操作:
using System; using System.IO; using System.Windows.Forms; private void BuildButton_Click(object sender, EventArgs e) { string dllPath = @"D:\YourExternalDllPath\Target.dll"; // 替换为你的DLL路径 string targetClassName = "targetClassName"; string methodName = "myMethod"; object[] methodParams = new object[] { "Params" }; string dllDir = Path.GetDirectoryName(dllPath); // 配置新AppDomain的搜索路径 AppDomainSetup setup = new AppDomainSetup(); setup.ApplicationBase = dllDir; // 关键:把应用基目录设为DLL所在文件夹 // 如果DLL有依赖在子目录,可添加PrivateBinPath(相对于ApplicationBase的路径) // setup.PrivateBinPath = "dependencies;libs"; // 创建新的AppDomain AppDomain newDomain = AppDomain.CreateDomain("ExternalDllDomain", null, setup); try { // 在新AppDomain中创建代理实例 DomainProxy proxy = (DomainProxy)newDomain.CreateInstanceAndUnwrap( typeof(DomainProxy).Assembly.FullName, typeof(DomainProxy).FullName ); // 初始化代理:加载DLL并创建目标类 proxy.Initialize(dllPath, targetClassName); // 调用目标方法 proxy.InvokeTargetMethod(methodName, methodParams); // 保存newDomain引用,后续需要卸载时调用AppDomain.Unload(newDomain) // 注意:卸载后proxy对象就不能再使用了 } catch (Exception ex) { MessageBox.Show($"错误:{ex.Message}"); } }
关键说明
- 无需复制DLL:通过把
ApplicationBase设为DLL所在目录,CLR会直接在该目录下查找DLL及其依赖项,不用复制到项目bin目录。 - 真正实现卸载:所有对目标DLL的操作都在新AppDomain内完成,没有任何对象泄露到当前域,后续调用
AppDomain.Unload(newDomain)就能完全卸载DLL。 - 依赖项处理:如果目标DLL有依赖,只要依赖项在DLL所在目录(或
PrivateBinPath指定的子目录),CLR会自动加载它们。
内容的提问来源于stack exchange,提问作者Islam El-gendy
相关产品推荐
相关产品推荐

