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

如何用AppDomain动态加载卸载外部DLL并解决文件找不到异常

解决跨AppDomain加载外部DLL并实现卸载的问题

我明白你遇到的困境——用Assembly.LoadFile能加载但没法卸载,换成AppDomain又遇到文件找不到的错误,还得复制DLL到项目目录才行。这其实是两个核心问题:AppDomain的程序集搜索路径配置错误,以及跨域传递Assembly对象导致的意外加载。下面给你完整的解决方案:

问题根源分析

  1. ApplicationBase配置错误:你之前把新AppDomain的ApplicationBase设为当前项目目录,CLR会默认在这个目录下查找程序集,而你的目标DLL在其他路径,自然找不到。
  2. 跨域对象传递的陷阱:直接把新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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 08:03:01