PluginService.cs 完整解析 — 插件发现与注册引擎 核心内容三个全局字典—PluginDic_Module视觉工具/PluginDic_Camera相机/PluginDic_Motion运动控制按[Category]特性自动分类InitPluginAsync() 逐行拆解— 7 个步骤过滤Plugin.*.dll→AppDomain.CurrentDomain.Load反射加载 → 遍历类型找ModuleBase子类 → 读取三个特性 → 按分类注册 → 更新 SplashScreen → 30ms 休眠给 UI 喘息GetPluginInfo()— 读取[Category][DisplayName][ModuleImageName]三个自定义特性 同 Assembly 中查找ModuleViewBase子类调用者全景— 4 个消费方App.xaml.cs启动扫描、ToolView构建13分类工具箱树、ProcessViewActivator.CreateInstance创建模块实例、HardwareConfig/CameraSet硬件型号下拉框插件注册标准— 列出开发者必须满足的 4 个条件文件名Plugin.*.dll 三个特性 继承基类 View 类数据流全景— 从启动扫描 → 构建工具箱 → 拖拽创建实例 → 硬件配置下拉的完整链路5 个隐患—break一个 DLL 只能一个插件、弹窗不跳过继续、无运行时热加载、依赖特性完整性、遍历所有 DLL 的类型性能浪费PluginService.cs 完整解析 — 插件发现与注册引擎文件:Services\PluginService.cs(132行)角色: 启动时扫描Plugins/目录, 反射加载所有Plugin.*.dll, 按类别注册到三个字典调用者:App.xaml.cs(启动入口) /ProcessView(创建模块实例) /ToolView(工具箱) /HardwareConfig(硬件配置)1. 它解决了什么问题JGTechVision 有 50 个插件, 用户拖拽流程时需要对所有插件做分类展示和动态实例化。PluginService就是插件黄页——启动时把所有 DLL 扫描一遍, 登记谁是谁、叫什么、图标是啥、属于哪个分类, 然后存到三个字典里供全局使用。2. 三个全局字典// 视觉工具插件 (模板匹配/测量圆/Blob/逻辑工具...)publicstaticDictionarystring,PluginsInfoPluginDic_Module// KeyDisplayName, 如模板匹配// 相机插件 (Halcon采集/海康威视...)publicstaticDictionarystring,PluginsInfoPluginDic_Camera// KeyDisplayName, 如Halcon采集// 运动控制插件 (正运动ZMotion...)publicstaticDictionarystring,PluginsInfoPluginDic_Motion// KeyDisplayName, 如ZMotion分类依据: 不是靠文件夹路径, 而是靠[Category(相机)]/[Category(轴卡)]特性:if(info.Category相机)PluginDic_Camera[info.ModuleName]info;elseif(info.Category轴卡)PluginDic_Motion[info.ModuleName]info;elsePluginDic_Module[info.ModuleName]info;// 所有其他分类都进这里3. PluginsInfo — 插件元数据publicclassPluginsInfo{publicTypeModuleType;// 插件ViewModel类型 (如 MatchingViewModel)publicTypeModuleViewType;// 插件View类型 (如 MatchingView)publicstringModuleName;// [DisplayName] 显示名 (如模板匹配)publicstringImageName;// [ModuleImageName] 图标名 (如Matching)publicstringCategory;// [Category] 分类 (如检测识别)}4. InitPluginAsync() — 核心扫描逻辑 (37→92行)publicstaticvoidInitPluginAsync(SplashScreenWindowsplashScreen){stringPlugInsDirPath.Combine(Environment.CurrentDirectory,Plugins\\);if(!Directory.Exists(PlugInsDir))return;foreach(vardllFileinDirectory.GetFiles(PlugInsDir)){try{FileInfofinewFileInfo(dllFile);// ① ★ 过滤器: 只处理 Plugin.xxx.dllif(!fi.Name.StartsWith(Plugin.)||!fi.Name.EndsWith(.dll))continue;// Plugin.DataIn.dll ✓ halcondotnet.dll ✗ System.Data.dll ✗// ② ★ 反射加载AssemblyassemPlugInAppDomain.CurrentDomain.Load(Assembly.LoadFile(fi.FullName).GetName());// AppDomain.CurrentDomain.Load 避免锁定dll文件, 支持热更新调试// ③ 遍历类型, 查找 ModuleBase/CameraBase/MotionBase 子类foreach(TypetypeinassemPlugIn.GetTypes()){if(typeof(ModuleBase).IsAssignableFrom(type)||typeof(CameraBase).IsAssignableFrom(type)||typeof(MotionBase).IsAssignableFrom(type)){PluginsInfoinfonewPluginsInfo();// ④ ★ 读取特性元数据if(GetPluginInfo(assemPlugIn,type,refinfo)){// ⑤ 按分类注册到对应字典if(info.Category相机)PluginDic_Camera[info.ModuleName]info;elseif(info.Category轴卡)PluginDic_Motion[info.ModuleName]info;elsePluginDic_Module[info.ModuleName]info;// ⑥ 更新启动画面splashScreen.viewModel.Value加载:info.ModuleName...;Application.Current.Dispatcher.Invoke(DispatcherPriority.Background,newAction(delegate{}));Thread.Sleep(30);// 让启动画面有时间刷新}break;// ★ 每个DLL只取第一个匹配的类型}}}catch(Exceptionex){MessageBox.Show(ex.ToString());// 加载失败的DLL弹窗提示}}}关键设计细节:过滤器Plugin.*.dll: 避免扫描 halcondotnet.dll / System.Data.dll 等第三方 DLL, 大幅减少反射遍历量break只取第一个: 一个 DLL 里可能有多个继承 ModuleBase 的类, 但只注册第一个 — 意味着一个 DLL 一个插件Thread.Sleep(30): 给 UI 线程喘息的机会刷新 SplashScreen, 否则加载过快看不到进度AppDomain.CurrentDomain.Load: 比Assembly.LoadFrom好, 不会锁定 DLL 文件, 方便调试时覆盖5. GetPluginInfo() — 读取特性元数据 (94→132行)publicstaticboolGetPluginInfo(AssemblyassemPlugIn,Typetype,refPluginsInfoinfo){// ① 读取类上的三个自定义特性System.Attribute[]attributeObjsSystem.Attribute.GetCustomAttributes(type);foreach(variteminattributeObjs){if(itemisModuleImageNameAttribute)// [ModuleImageName(Matching)]info.ImageName((ModuleImageNameAttribute)item).ImageName;elseif(itemisCategoryAttribute)// [Category(检测识别)]info.Category((CategoryAttribute)item).Category;elseif(itemisDisplayNameAttribute)// [DisplayName(模板匹配)]info.ModuleName((DisplayNameAttribute)item).DisplayName;}info.ModuleTypetype;// ② 在同一个 Assembly 中查找 ModuleViewBase 子类(View)foreach(TypetempTypeinassemPlugIn.GetTypes()){if(typeof(ModuleViewBase).IsAssignableFrom(tempType)){info.ModuleViewTypetempType;returntrue;// 找到了View → 注册成功}}returntrue;// 没找到View也返回true (有些插件可能不需要View)}一个插件 DLL 中的两个关键类型:Plugin.Matching.dll ├── MatchingViewModel : ModuleBase ← [DisplayName(模板匹配)] [Category(检测识别)] └── MatchingView : ModuleViewBase ← WPF窗口, ModuleBase 通过 ModuleViewBase.ModuleBase 关联6. 调用者全景6.1 启动入口 — App.xaml.cs// App.xaml.cs line 66PluginService.InitPluginAsync(splashScreen);在 SplashScreen 显示期间执行扫描, 扫描完成后进入主界面。6.2 工具箱 — ToolView// ToolView.xaml.cs — 构建左侧工具箱树string[]pluginsNamePluginService.PluginDic_Module.Keys.ToArray();foreach(variteminpluginsName){varinfoPluginService.PluginDic_Module[item];// 根据 info.Category 分配到 13 个分类节点下if(info.Category常用工具){Nameinfo.ModuleName;Iconinfo.ImageName;}if(info.Category图像处理){...}// ... 13 个分类}6.3 拖拽创建模块 — ProcessView// ProcessView.xaml.cs — 从工具箱拖入流程时ModuleBasemodule(ModuleBase)Activator.CreateInstance(PluginService.PluginDic_Module[pluginName].ModuleType);// → new MatchingViewModel()6.4 硬件配置 — CameraSetViewModel / HardwareConfigViewModel// CameraSetViewModel — 相机型号下拉框ListstringCameraTypesPluginService.PluginDic_Camera.Keys.ToList();// → [Halcon采集, 海康威视]// HardwareConfigViewModel — 运动控制品牌下拉框ListstringMotionBrandsPluginService.PluginDic_Motion.Keys.ToList();// → [ZMotion]7. 插件开发者的注册标准要让一个 DLL 被 PluginService 识别, 必须同时满足:1. 文件名: Plugin.Xxx.dll ← 过滤器要求 2. 类上标记三个特性: [Category(检测识别)] ← 决定分类 [DisplayName(模板匹配)] ← 决定Key 显示名 [ModuleImageName(Matching)] ← 决定图标 3. 继承 ModuleBase/CameraBase/MotionBase ← 类型过滤器 4. 同Assembly中有 ModuleViewBase 子类 ← View注册完整示例(来自 Plugin.Matching):[Category(检测识别)][DisplayName(模板匹配)][ModuleImageName(Matching)][Serializable]publicclassMatchingViewModel:ModuleBase{...}publicclassMatchingView:ModuleViewBase{...}8. 数据流全景启动 │ ▼ App.xaml.cs → PluginService.InitPluginAsync(splashScreen) │ ├→ 遍历 Plugins\*.dll │ ├── Plugin.Matching.dll → MatchingViewModel[检测识别] → PluginDic_Module │ ├── Plugin.Blob.dll → BlobViewModel[图像处理] → PluginDic_Module │ ├── Plugin.CSharpScript.dll → CSharpScriptViewModel[逻辑工具] → PluginDic_Module │ ├── (相机DLL) → CameraHalcon[相机] → PluginDic_Camera │ └── (运动DLL) → ZMotionControl[轴卡] → PluginDic_Motion │ └→ SplashScreen 逐条显示 加载:模板匹配... 加载:Blob分析... ════════ 运行中 ════════ ToolView → PluginDic_Module.Keys → 构建工具箱树 用户拖入模板匹配 → ProcessView → Activator.CreateInstance(PluginDic_Module[模板匹配].ModuleType) → new MatchingViewModel() → 添加到 ModuleList 用户打开硬件配置 → CameraSetViewModel → PluginDic_Camera.Keys → 相机型号下拉 HardwareConfigViewModel → PluginDic_Motion.Keys → 运动控制品牌下拉9. 设计分析优点零配置发现: 放入 Plugins 目录即可自动注册, 不需要任何配置文件分类自动: 通过[Category]特性, 13 个分类自动映射无 DLL 锁定:AppDomain.CurrentDomain.Load避免文件锁, 支持调试热替换启动画面进度:Thread.Sleep(30) SplashScreen 更新提供加载反馈隐患问题说明只取第一个类break意味着一个 DLL 只能有一个插件, 不能打包多个异常处理粗糙catch (Exception ex) { MessageBox.Show(ex.ToString()); }— 加载失败直接弹窗, 不跳过继续无热加载只在启动时扫描一次, 运行时新增 DLL 不会被发现依赖特性完整性缺少任何一个特性都会导致ModuleName或Category为空遍历所有 DLL 的类型Directory.GetFiles 双重foreach— 如果 Plugins 目录有大量非插件 DLL, 性能浪费文档说明: 基于 PluginService.cs (132行) PluginsInfo 模型 ModuleImageNameAttribute 特性 全部调用点静态分析生成。插件系统是 JGTechVision 可扩展性的核心基础设施。当前版本 2026-06-10。