alielie
7 天以前 e57938fbda9bff702004038f76d009422968bff5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
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})");
                }
            }
        }
    }
}