# 简介

在 .NET Framework 开发中,“DLL Hell”一直是开发者的噩梦,尤其是在为复杂软件如 Revit 开发插件时更是如此。当多个依赖库需要不同版本的同一 DLL 时,就会引发版本冲突,而这类问题往往难以调试。最近,我在为 Revit 2020 插件集成遇到了这样的冲突——Revit 的预加载依赖与所需的依赖不兼容。这就是经典的 DLL 冲突问题:不同库对同一 DLL 的版本需求不一致,而宿主应用优先加载自己的版本,导致插件运行失败。

本文将分享我从碰壁到解决问题的完整过程,并介绍如何利用 AppDomain 实现依赖隔离,优雅化解 DLL 冲突。


常规方法尝试及失败

在尝试解决问题的过程中,我首先尝试了几个常见的解决方案,但都未能奏效:

  1. AppDomain.AssemblyResolve 事件
    通过 AssemblyResolve 事件绑定正确的 DLL 通常是第一选择。然而在 Revit 环境下,这种方法失效,因为 Revit 的依赖加载优先级高于事件绑定机制。
  2. 加载顺序控制
    我尝试在插件加载时通过 Assembly.Load 强制加载正确的 DLL 版本,但 Revit 已经提前锁定了其依赖版本,导致无效。
  3. 配置文件的绑定重定向
    修改 Revit.config 或为插件配置 App.config 文件,添加绑定重定向规则,也无法绕过 Revit 的 DLL 处理逻辑。

显然,要解决这个问题,必须跳出现有框架限制,寻找一种全新的方式。


解决方案:使用 AppDomain 实现依赖隔离

灵感来源:鱼缸中的塑料袋
使用 AppDomain 就像把一条鱼(冲突的 DLL)放进一个装满水的透明塑料袋(独立的 AppDomain),然后再放入大鱼缸(默认 AppDomain)。这样,鱼可以在自己的水域里自由活动,而不会影响鱼缸中的其他鱼。

通过创建一个独立的 AppDomain,可以将其依赖库隔离到一个单独的运行环境中,使其不再受 Revit 内部依赖的影响。

解决过程:步骤详解

步骤 1:将逻辑封装到独立 DLL

首先,将所有相关逻辑单独封装到一个独立的库中,让它只负责处理身份验证,并返回 JSON 格式的结果。这样可以确保逻辑模块化,降低与 Revit 核心功能的耦合度。

步骤 2:创建和配置新的 AppDomain

通过配置新的 AppDomain,可以在该域内加载特定版本的 DLL,而不干扰 Revit 的运行环境。以下是代码实现:

public T LoadAssemblyViaAppDomain<T>(string targetDll) where T : AssemblySandbox{    var domSetup = new AppDomainSetup    {        ShadowCopyFiles = "true",        ApplicationBase = Path.GetDirepublic T LoadAssemblyViaAppDomain<T>(string targetDll) where T : AssemblySandbox
{
    var domSetup = new AppDomainSetup
    {
        ShadowCopyFiles = "true",
        ApplicationBase = Path.GetDirectoryName(targetDll)
        };

    AppDomain domain = AppDomain.CreateDomain("IsolatedDomain", null, domSetup);

    Type assemblySandboxType = typeof(T);

    var assemblySandbox = (T)domain.CreateInstanceAndUnwrap(
        assemblySandboxType.Assembly.FullName, 
        assemblySandboxType.ToString()
    );

    return assemblySandbox;
}

这个代码通过 AppDomainSetup 启用了影子复制,并指定了目标 DLL 的加载路径,以确保新域可以独立加载所需的依赖。

步骤 3:实现 Sandbox 类

为了安全地跨域通信,我们创建了一个继承自 MarshalByRefObject 的 Sandbox 类:

public abstract class AssemblySandbox : MarshalByRefObject
{
    public abstract void LoadAssembly(string assemblyPath);

    // 更多方法...
}

该类负责在新域内加载 DLL 并提供统一的操作接口,从而简化主程序与隔离域之间的交互。


 

步骤 4:加载并调用库

最终,我们在新域中加载库,调用认证方法并返回结果:

public class AuthSandbox : AssemblySandbox
{
    public string ExecuteLogin()
    {
        var result = _authService.Login();
        return result;
    }

    public override void LoadAssembly(string assemblyPath)
    {
        var assembly = Domain.Load(File.ReadAllBytes(assemblyPath));
        var type = assembly.GetType("SharpBIM.AuthLogin.GoogleAuth");
        _authService = (IAuthService)Activator.CreateInstance(type);
    }
}

通过 Activator.CreateInstance 在新域中动态创建登录服务实例,从而实现认证逻辑的隔离运行。

跨域通信的注意事项

AppDomain 的对象跨域通信要求对象要么是可序列化的,要么继承自 MarshalByRefObject。为了简化数据交换,我采用 JSON 格式化结果,避免了直接依赖库的跨域传递。


关键点总结与注意事项

  1. AppDomain 的适用性
    AppDomain 是 .NET Framework 的特性,适用于 Revit 等基于旧版 .NET 架构的应用。但在 .NET Core 和 .NET 5+ 中,AppDomain 被移除,因此需要其他方案(如 AssemblyLoadContext)。


关于 AssemblyLoadContext - .NET | Microsoft Learn


与 UI 库的兼容

  1. 对于涉及 UI 的库,可以将 UI 操作也隔离到独立域中完成,再通过跨域通信将结果返回到主程序域中,以确保 Revit 环境的稳定性。


总结

使用 AppDomain 隔离依赖,不仅有效解决了 Revit 插件开发中的 DLL 冲突问题,还为插件开发提供了更灵活的架构设计。虽然 AppDomain 的使用会增加一些复杂度,但其强大的隔离能力可以为开发者提供更多可能性。

如果你也在 Revit 插件开发中遇到类似问题,希望本文能为你提供启发!


 

欢迎留言讨论,或分享您的开发经验!

# 写在最后 #


粉丝Free提需求!!如果你正在寻找提效的工具,希望这个免费的功能商店能帮到你

如果你对插件开发感兴趣,欢迎与我们交流一起探讨更多Revit使用技巧

更多AI、MCP功能等你呦~


欢迎评论区留言交流

Free功能百宝box不断更新中,欢迎粉丝提需求跟建议~


❤-------❤若有收获,就点个关注吧 -------❤
您的“关注”“点赞”“分享”"留言"对我们是一份鼓励!

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐