using System.IO; namespace DeviceService.Driver { /// /// 一个已隔离加载的驱动包(对应驱动根目录下的一个子文件夹)。 /// public sealed class DriverPackage { /// 子文件夹名(通常为厂商/设备名) public string Folder { get; set; } /// 驱动包所在物理目录 public string Directory { get; set; } /// 驱动包专属 AppDomain public AppDomain Domain { get; set; } /// 运行在该域内的加载器代理 public DriverLoader Loader { get; set; } /// 该包内的程序集与公开类型 public DriverAssemblyInfo[] Assemblies { get; set; } } /// /// (类型, 品牌) 查找结果。 /// public sealed class DriverFindResult { public DriverPackage Package { get; set; } public DriverTypeInfo Info { get; set; } } /// /// 驱动管理器:扫描根目录下所有子文件夹,每个子文件夹一个隔离域。 /// /// 适用于"不同厂商提供的驱动 dll 之间没有任何契约"的场景: /// - 驱动 dll 不需要实现接口、不需要引用宿主程序集; /// - 宿主只通过反射枚举类型/方法、创建实例、调用方法; /// - 每个驱动包自带自己的依赖,互不干扰(可存在同名不同版本); /// - 需要替换驱动文件时先卸载域,文件不会被锁定(影子复制)。 /// public sealed class DriverManager { private static DriverManager _instance; public static DriverManager Instance { get { if (_instance == null) { _instance = new DriverManager(); } return _instance; } } private DriverManager() { } private readonly List _packages = new List(); /// 由 DriverManager 统一登记管理的驱动实例(跨域透明代理) private readonly Dictionary _managedDrivers = new Dictionary(StringComparer.Ordinal); public IReadOnlyList Packages => _packages; /// 当前由 DriverManager 管理的全部驱动实例 public IReadOnlyCollection ManagedDrivers => _managedDrivers.Values; /// /// 扫描 rootPath 下的每个子文件夹并创建隔离域加载。 /// public void Scan(string rootPath, Action log) { if (!Directory.Exists(rootPath)) { log?.Invoke($"[错误] 目录不存在: {rootPath}"); return; } var subDirs = Directory.GetDirectories(rootPath); if (subDirs.Length == 0) { log?.Invoke($"[提示] {rootPath} 下没有子文件夹(每个子文件夹应为一个驱动包)"); return; } foreach (var dir in subDirs) { var brandDirs = Directory.GetDirectories(dir); foreach (var brandDir in brandDirs) { var name = Path.GetFileName(brandDir); try { LoadPackage(brandDir, name, log); } catch (Exception ex) { log?.Invoke($"[失败] {name}: {ex.Message}"); } } } } /// /// 列出某个类型可调用的公共方法。 /// public DriverMethodInfo[] GetMethods(DriverPackage package, string typeFullName) { return package.Loader.GetMethods(typeFullName); } /// /// 在所有已加载驱动包中按 (类型, 品牌) 查找驱动。 /// 任一字段留空视为通配;同一组合命中多个包时记录警告并返回第一个。 /// public DriverFindResult FindDriver(string driverType, string brand, Action log) { DriverFindResult first = null; foreach (var package in _packages) { var info = package.Loader.FindDriver(driverType, brand); if (info == null) { continue; } if (first == null) { first = new DriverFindResult { Package = package, Info = info }; } else { log?.Invoke($"[警告] (类型={driverType}, 品牌={brand}) 在多个驱动包中重复: " + $"{first.Package.Folder} 与 {package.Folder},返回第一个"); } } return first; } /// /// 反射调用驱动方法(实际执行发生在该驱动自己的域内)。 /// public InvokeResult Invoke(DriverPackage package, string typeFullName, int methodIndex, string[] args) { return package.Loader.Invoke(typeFullName, methodIndex, args); } /// /// 根据 (类型, 品牌) 找到对应的驱动类型,在其所属驱动包的 AppDomain 内 /// 创建一个新实例并以透明代理形式返回,由 DriverManager 统一登记管理。 /// /// 说明:驱动类型本身不可跨域封送(非 MarshalByRefObject,宿主也不引用厂商程序集), /// 因此无法用泛型 T 返回"原生类型";宿主一律通过 代理操作, /// 每次调用实际执行发生在驱动自己的域内。 /// /// 注解中的"类型",如 CAN;留空为通配 /// 注解中的"品牌",如 BA;留空为通配 /// 日志回调(可空) /// 驱动实例代理;未找到或创建失败返回 null public DriverInstance CreateDriverByTypeAndBrand(string driverType, string brand, Action log = null) { var hit = FindDriver(driverType, brand, log); if (hit == null) { log?.Invoke($"[创建] 未找到 (类型={driverType}, 品牌={brand}) 对应的驱动"); return null; } try { var instance = hit.Package.Loader.CreateInstance(hit.Info.FullName, hit.Info.DriverType, hit.Info.Brand); instance.PackageFolder = hit.Package.Folder; _managedDrivers[instance.Handle] = instance; log?.Invoke($"[创建] {hit.Info.FullName}({hit.Info.DriverType}/{hit.Info.Brand},包 {hit.Package.Folder})" + $" => 实例 {ShortHandle(instance.Handle)}"); return instance; } catch (Exception ex) { log?.Invoke($"[创建失败] {hit.Info.FullName}: {ex.Message}"); return null; } } /// /// 泛型版本:T 为宿主自定义的"鸭子接口"(方法名、参数类型与驱动方法一一对应)。 /// 返回 DispatchProxy 动态代理:接口调用按"方法名 + 参数个数"转发到驱动域内反射执行。 /// /// 驱动无需实现 T、无需引用宿主程序集——这正是动态代理的意义: /// 不同厂商的驱动可以共享宿主定义的一套接口约定。 /// /// 约束: /// - T 必须是接口(DispatchProxy 只针对接口生成代理类); /// - 接口方法的参数/返回值必须可跨域封送(基元类型、string、[Serializable] DTO); /// - 每次调用都是一次跨域反射,性能低于直接调用,适合设备控制类低频操作。 /// public T CreateDriverByTypeAndBrand(string driverType, string brand, Action log = null) where T : class { var instance = CreateDriverByTypeAndBrand(driverType, brand, log); if (instance == null) { return null; } log?.Invoke($"[代理] {instance.TypeFullName} => {typeof(T).Name} 动态代理(实例 {ShortHandle(instance.Handle)})"); return DriverDispatchProxy.Create(instance); } /// /// 释放一个由 DriverManager 管理的驱动实例(驱动实现 IDisposable 时会调用 Dispose)。 /// public void ReleaseDriver(DriverInstance instance, Action log = null) { if (instance == null) { return; } _managedDrivers.Remove(instance.Handle); try { instance.Release(); log?.Invoke($"[释放] 实例 {ShortHandle(instance.Handle)}({instance.TypeFullName})"); } catch (Exception ex) { log?.Invoke($"[释放失败] 实例 {ShortHandle(instance.Handle)}: {ex.Message}"); } } /// /// 卸载某个驱动包前,先通过代理释放其名下所有托管实例(域卸载后代理会失效)。 /// private void ReleaseInstancesOf(DriverPackage package, Action log) { var targets = _managedDrivers.Values .Where(i => i.PackageFolder == package.Folder) .ToList(); foreach (var instance in targets) { ReleaseDriver(instance, log); } } private static string ShortHandle(string handle) => handle.Substring(0, Math.Min(8, handle.Length)); /// /// 卸载所有驱动域(释放程序集、解锁 dll 文件)。 /// public void UnloadAll(Action log) { // 先通过代理释放所有托管实例,域卸载后代理将失效 foreach (var instance in _managedDrivers.Values.ToList()) { ReleaseDriver(instance, log); } var domains = _packages.Select(p => p.Domain).Distinct().ToList(); _packages.Clear(); foreach (var domain in domains) { // 注意:必须在卸载前读取 FriendlyName,域卸载后再访问其属性会抛 AppDomainUnloadedException var name = domain.FriendlyName; try { AppDomain.Unload(domain); log?.Invoke($"[卸载] {name}"); } catch (Exception ex) { log?.Invoke($"[卸载失败] {name}: {ex.Message}"); } } } private DriverPackage LoadPackage(string dir, string name, Action log) { // 加载器与 DTO 契约统一使用宿主目录中的唯一副本,不再向驱动目录复制 var loaderPath = typeof(DriverLoader).Assembly.Location; WarnIfStaleRuntimeFiles(dir, name, log); var setup = new AppDomainSetup { ApplicationName = name, // 关键:探测根固定为该驱动目录,依赖只会从自己目录解析 ApplicationBase = dir, // 影子复制:dll 加载后不锁定,可随时卸载/替换文件 ShadowCopyFiles = "true", ShadowCopyDirectories = dir, CachePath = Path.Combine(Path.GetTempPath(), "MultiPluginCache", name) }; var domain = AppDomain.CreateDomain("Driver_" + name, null, setup); try { var loader = (DriverLoader)domain.CreateInstanceFromAndUnwrap(loaderPath, typeof(DriverLoader).FullName); var assemblies = loader.LoadAssemblies(); var package = new DriverPackage { Folder = name, Directory = dir, Domain = domain, Loader = loader, Assemblies = assemblies }; _packages.Add(package); // 驱动信息持久化到 Config\Drivers.db DriverDatabase.Instance.SavePackage(package, log); var totalDrivers = assemblies.Where(a => a.Types != null).Sum(a => a.Types.Length); var failed = assemblies.Count(a => a.Error != null); log?.Invoke($"[驱动包] {name}: {assemblies.Length} 个 dll,{totalDrivers} 个驱动类型" + (failed > 0 ? $",{failed} 个无法加载" : string.Empty)); foreach (var asm in assemblies.Where(a => a.Error != null)) { log?.Invoke($" [!] {asm.FileName}: {asm.Error}"); } return package; } catch { try { AppDomain.Unload(domain); } catch { /* 忽略 */ } throw; } } /// /// 卸载指定的驱动包(释放该域中的所有程序集)。 /// public void Unload(DriverPackage package, Action log) { // 域卸载前先释放该包名下的托管实例,避免操作失效代理 ReleaseInstancesOf(package, log); _packages.Remove(package); // 同步移除该驱动包在 Config\Drivers.db 中的记录 DriverDatabase.Instance.RemovePackage(package.Folder, log); try { AppDomain.Unload(package.Domain); log?.Invoke($"[卸载] {package.Folder}"); } catch (Exception ex) { log?.Invoke($"[卸载失败] {package.Folder}: {ex.Message}"); } } /// /// 重载指定的驱动包:卸载旧域后用同一目录重新创建域。 /// 依赖影子复制,替换 dll 后无需重启宿主即可生效。 /// public DriverPackage Reload(DriverPackage package, Action log) { var dir = package.Directory; var name = package.Folder; Unload(package, log); log?.Invoke($"[重载] {name} ..."); return LoadPackage(dir, name, log); } /// /// 早期版本会把加载器/DTO 复制到每个驱动目录,此处清理残留,避免旧版本 Contracts 干扰。 /// 驱动目录本身不需要、也不应该包含宿主运行时文件。 /// private static void WarnIfStaleRuntimeFiles(string dir, string name, Action log) { foreach (var fileName in new[] { "MultiPlugin.Loader.dll", "MultiPlugin.Contracts.dll" }) { var path = Path.Combine(dir, fileName); if (!File.Exists(path)) { continue; } try { File.Delete(path); log?.Invoke($"[清理] {name}: 已删除多余的 {fileName}(运行时统一使用宿主目录版本)"); } catch (Exception ex) { log?.Invoke($"[提示] {name}: 存在多余的 {fileName},已忽略({ex.Message})"); } } } } }