Mono环境下将程序集加载至独立AppDomain时触发FileNotFoundException
解决Mono环境下Discord机器人插件AppDomain加载依赖缺失问题
我明白你在macOS上用Mono开发Discord机器人插件系统时遇到的头疼问题——明明主域已经加载了依赖,独立AppDomain却死活找不到这些程序集,各种加载方式都触发FileNotFoundException,还没法实现插件卸载重载。我之前也在类似场景踩过坑,给你几个针对性的解决方案:
1. 给独立AppDomain配置程序集解析回调
这是最关键的一步:Mono下的AppDomain不会自动继承主域的已加载程序集,你需要手动告诉它去哪里找依赖。可以创建一个继承自MarshalByRefObject的程序集解析器,在独立域里注册AppDomain.AssemblyResolve事件,让它优先从主域已加载的程序集中查找,再去指定目录加载:
public class AssemblyResolver : MarshalByRefObject { public void SetupResolver() { AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => { // 先尝试从主域已加载的程序集中匹配 var currentDomainAssemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (var asm in currentDomainAssemblies) { if (asm.FullName == args.Name) { return asm; } } // 主域没有的话,尝试从主程序目录加载 string assemblyPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, new AssemblyName(args.Name).Name + ".dll"); if (File.Exists(assemblyPath)) { return Assembly.LoadFrom(assemblyPath); } // 最后尝试插件目录 string pluginDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "modules"); assemblyPath = Path.Combine(pluginDir, new AssemblyName(args.Name).Name + ".dll"); if (File.Exists(assemblyPath)) { return Assembly.LoadFrom(assemblyPath); } return null; }; } }
2. 修正你的AppDomain加载流程
你当前直接用domain.Load(byte[])的方式有本质问题:这种加载方式不会触发程序集解析回调,而且Mono下对字节流加载的程序集依赖处理有特殊限制。正确的做法是:
- 创建AppDomain时指定基础目录和私有bin路径,让它知道去哪里找依赖
- 在新域中实例化解析器并注册回调
- 通过程序集名称或路径加载插件,而不是直接传入字节流
修改后的SetupAppDomain方法示例:
private void SetupAppDomain() { string pluginPath = Path.Combine(Directory.GetCurrentDirectory(), "modules"); Console.WriteLine(pluginPath); List<string> installedModules = new List<string>(); foreach(string modulePath in Directory.GetFiles(pluginPath, "*.dll")) { try { // 配置AppDomain的基础路径和私有bin目录 AppDomainSetup setup = new AppDomainSetup(); setup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory; // 指向主程序目录,依赖都在这里 setup.PrivateBinPath = pluginPath; // 让域也能扫描插件目录 setup.ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile; // 创建独立AppDomain AppDomain domain = AppDomain.CreateDomain( Path.GetFileNameWithoutExtension(modulePath), AppDomain.CurrentDomain.Evidence, setup ); // 在新域中初始化程序集解析器(必须用CreateInstanceAndUnwrap跨域调用) var resolver = (AssemblyResolver)domain.CreateInstanceAndUnwrap( typeof(AssemblyResolver).Assembly.FullName, typeof(AssemblyResolver).FullName ); resolver.SetupResolver(); // 通过程序集名称加载插件,触发解析回调处理依赖 Assembly pluginAssembly = domain.Load(AssemblyName.GetAssemblyName(modulePath)); installedModules.Add(pluginAssembly.GetName().Name); // 后续可以通过CreateInstanceAndUnwrap创建插件实例,实现跨域交互 // 比如 IPlugin plugin = (IPlugin)domain.CreateInstanceAndUnwrap(pluginAssembly.FullName, "YourPluginClassName"); } catch(Exception ex) { Console.WriteLine($"加载插件失败: {modulePath}, 错误详情: {ex.Message}\n{ex.StackTrace}"); // 不要直接throw,避免中断其他插件的加载流程 } } }
3. Mono环境下的额外注意事项
- 确保你加载的依赖程序集版本和主域完全一致,比如Newtonsoft.Json的版本要匹配,否则会出现版本冲突
- 避免使用
Assembly.Load(byte[])加载依赖,Mono下这种方式会导致程序集无法被解析回调识别 - 如果你有原生依赖(比如DSharpPlus的某些原生库),需要确保它们的路径能被Mono找到,可以提前设置
DYLD_LIBRARY_PATH环境变量指向原生库目录
为什么你的原有方法不行?
你之前尝试的几种方式都没触碰到核心问题:
- 独立AppDomain和主域是完全隔离的,不会自动共享已加载的程序集
- 直接加载字节流不会触发
AssemblyResolve事件,依赖无法被自动解析 - 手动按顺序加载时,Mono下加载程序集会严格检查上下文,单独加载Newtonsoft.Json时,它的系统级依赖可能在新域中没有正确初始化
内容的提问来源于stack exchange,提问作者MrMiketheripper
相关产品推荐
相关产品推荐

