using System.IO;
|
|
namespace DeviceService.Driver
|
{
|
/// <summary>
|
/// 一个已隔离加载的驱动包(对应驱动根目录下的一个子文件夹)。
|
/// </summary>
|
public sealed class DriverPackage
|
{
|
/// <summary>子文件夹名(通常为厂商/设备名)</summary>
|
public string Folder { get; set; }
|
|
/// <summary>驱动包所在物理目录</summary>
|
public string Directory { get; set; }
|
|
/// <summary>驱动包专属 AppDomain</summary>
|
public AppDomain Domain { get; set; }
|
|
/// <summary>运行在该域内的加载器代理</summary>
|
public DriverLoader Loader { get; set; }
|
|
/// <summary>该包内的程序集与公开类型</summary>
|
public DriverAssemblyInfo[] Assemblies { get; set; }
|
}
|
|
/// <summary>
|
/// (类型, 品牌) 查找结果。
|
/// </summary>
|
public sealed class DriverFindResult
|
{
|
public DriverPackage Package { get; set; }
|
public DriverTypeInfo Info { get; set; }
|
}
|
|
/// <summary>
|
/// 驱动管理器:扫描根目录下所有子文件夹,每个子文件夹一个隔离域。
|
///
|
/// 适用于"不同厂商提供的驱动 dll 之间没有任何契约"的场景:
|
/// - 驱动 dll 不需要实现接口、不需要引用宿主程序集;
|
/// - 宿主只通过反射枚举类型/方法、创建实例、调用方法;
|
/// - 每个驱动包自带自己的依赖,互不干扰(可存在同名不同版本);
|
/// - 需要替换驱动文件时先卸载域,文件不会被锁定(影子复制)。
|
/// </summary>
|
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<DriverPackage> _packages = new List<DriverPackage>();
|
|
/// <summary>由 DriverManager 统一登记管理的驱动实例(跨域透明代理)</summary>
|
private readonly Dictionary<string, DriverInstance> _managedDrivers =
|
new Dictionary<string, DriverInstance>(StringComparer.Ordinal);
|
|
public IReadOnlyList<DriverPackage> Packages => _packages;
|
|
/// <summary>当前由 DriverManager 管理的全部驱动实例</summary>
|
public IReadOnlyCollection<DriverInstance> ManagedDrivers => _managedDrivers.Values;
|
|
/// <summary>
|
/// 扫描 rootPath 下的每个子文件夹并创建隔离域加载。
|
/// </summary>
|
public void Scan(string rootPath, Action<string> 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}");
|
}
|
}
|
}
|
}
|
|
/// <summary>
|
/// 列出某个类型可调用的公共方法。
|
/// </summary>
|
public DriverMethodInfo[] GetMethods(DriverPackage package, string typeFullName)
|
{
|
return package.Loader.GetMethods(typeFullName);
|
}
|
|
/// <summary>
|
/// 在所有已加载驱动包中按 (类型, 品牌) 查找驱动。
|
/// 任一字段留空视为通配;同一组合命中多个包时记录警告并返回第一个。
|
/// </summary>
|
public DriverFindResult FindDriver(string driverType, string brand, Action<string> 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;
|
}
|
|
/// <summary>
|
/// 反射调用驱动方法(实际执行发生在该驱动自己的域内)。
|
/// </summary>
|
public InvokeResult Invoke(DriverPackage package, string typeFullName, int methodIndex, string[] args)
|
{
|
return package.Loader.Invoke(typeFullName, methodIndex, args);
|
}
|
|
/// <summary>
|
/// 根据 (类型, 品牌) 找到对应的驱动类型,在其所属驱动包的 AppDomain 内
|
/// 创建一个新实例并以透明代理形式返回,由 DriverManager 统一登记管理。
|
///
|
/// 说明:驱动类型本身不可跨域封送(非 MarshalByRefObject,宿主也不引用厂商程序集),
|
/// 因此无法用泛型 T 返回"原生类型";宿主一律通过 <see cref="DriverInstance"/> 代理操作,
|
/// 每次调用实际执行发生在驱动自己的域内。
|
/// </summary>
|
/// <param name="driverType">注解中的"类型",如 CAN;留空为通配</param>
|
/// <param name="brand">注解中的"品牌",如 BA;留空为通配</param>
|
/// <param name="log">日志回调(可空)</param>
|
/// <returns>驱动实例代理;未找到或创建失败返回 null</returns>
|
public DriverInstance CreateDriverByTypeAndBrand(string driverType, string brand, Action<string> 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;
|
}
|
}
|
|
/// <summary>
|
/// 泛型版本:T 为宿主自定义的"鸭子接口"(方法名、参数类型与驱动方法一一对应)。
|
/// 返回 DispatchProxy 动态代理:接口调用按"方法名 + 参数个数"转发到驱动域内反射执行。
|
///
|
/// 驱动无需实现 T、无需引用宿主程序集——这正是动态代理的意义:
|
/// 不同厂商的驱动可以共享宿主定义的一套接口约定。
|
///
|
/// 约束:
|
/// - T 必须是接口(DispatchProxy 只针对接口生成代理类);
|
/// - 接口方法的参数/返回值必须可跨域封送(基元类型、string、[Serializable] DTO);
|
/// - 每次调用都是一次跨域反射,性能低于直接调用,适合设备控制类低频操作。
|
/// </summary>
|
public T CreateDriverByTypeAndBrand<T>(string driverType, string brand, Action<string> 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<T>(instance);
|
}
|
|
/// <summary>
|
/// 释放一个由 DriverManager 管理的驱动实例(驱动实现 IDisposable 时会调用 Dispose)。
|
/// </summary>
|
public void ReleaseDriver(DriverInstance instance, Action<string> 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}");
|
}
|
}
|
|
/// <summary>
|
/// 卸载某个驱动包前,先通过代理释放其名下所有托管实例(域卸载后代理会失效)。
|
/// </summary>
|
private void ReleaseInstancesOf(DriverPackage package, Action<string> 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));
|
|
/// <summary>
|
/// 卸载所有驱动域(释放程序集、解锁 dll 文件)。
|
/// </summary>
|
public void UnloadAll(Action<string> 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<string> 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;
|
}
|
}
|
|
/// <summary>
|
/// 卸载指定的驱动包(释放该域中的所有程序集)。
|
/// </summary>
|
public void Unload(DriverPackage package, Action<string> 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}");
|
}
|
}
|
|
/// <summary>
|
/// 重载指定的驱动包:卸载旧域后用同一目录重新创建域。
|
/// 依赖影子复制,替换 dll 后无需重启宿主即可生效。
|
/// </summary>
|
public DriverPackage Reload(DriverPackage package, Action<string> log)
|
{
|
var dir = package.Directory;
|
var name = package.Folder;
|
|
Unload(package, log);
|
log?.Invoke($"[重载] {name} ...");
|
|
return LoadPackage(dir, name, log);
|
}
|
|
/// <summary>
|
/// 早期版本会把加载器/DTO 复制到每个驱动目录,此处清理残留,避免旧版本 Contracts 干扰。
|
/// 驱动目录本身不需要、也不应该包含宿主运行时文件。
|
/// </summary>
|
private static void WarnIfStaleRuntimeFiles(string dir, string name, Action<string> 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})");
|
}
|
}
|
}
|
}
|
}
|