07 · 宿主与进程 —— HostBridge / Mcp.Host / RuntimeCore.Host / Supervisor / Shell
前六章的能力都在库里。这一章讲怎么把它们跑起来、开出去:给前端(WebView2)、给外部 AI(MCP)、拆成受管子进程。这些都是顶层出口,自己不产生新能力,只转发下面的 Automation / Backend / ScriptRuntime。
Hmi.HostBridge —— 无头消息路由(前端的后端)
职责:一条 postMessage 进、一条 PostWebMessageAsJson 出的无头、可测路由核心。依赖 Automation(+ 运行态时的 Core/Backend/ScriptRuntime)。契约见 docs/frontend-contract 02。
路由核心 WebMessageRouter
src/Hmi.HostBridge/WebMessageRouter.cs(public sealed class)。吃一条入站信封 JSON,按 kind 分派:
rpc→AutomationRpcDispatcher(组态/感知)、回rpc-result;sub/unsub→TagSubscriptionBridge(运行态点值订阅);tag-update事件由订阅桥经IWebMessageSink主动推。
所有异常在此收敛成 rpc-result(ok:false),绝不让宿主因一条坏消息崩溃。 两种装配:①仅编辑器——只传 dispatcher,tags 桥为空,sub/unsub 回"运行态未就绪";②运行态——同时传订阅桥。
宿主侧管道(不是 Automation 能力,前端在 WebView2 里拿不到本机路径/进程,须由宿主接原生对话框):PickProjectDir、PickFile、PickSaveFile、LaunchRuntime、OpenProjectFolder、RunScript、PreviewStart / PreviewStop —— 都是可选注入的委托,未注入则对应 RPC 显式回"不就绪"。
组态/感知路由 AutomationRpcDispatcher
src/Hmi.HostBridge/AutomationRpcDispatcher.cs(public sealed class)。AutomationResult Dispatch(string method, JsonElement? payload) 把 method(= 契约方法名,如 get_project / list_screens / add_tag …)映射到 IProjectAutomation 的方法,从 JSON payload 取参。与 MCP 工具集是同一批操作的两个出口。未知 method 返回 Fail(不抛)。
runtime_write_tag不在这里 —— 运行态 PLC 写值留阶段六,由路由器另行处理并强制确认。这条边界和第 06 章一脉相承。
点值订阅桥 TagSubscriptionBridge
src/Hmi.HostBridge/TagSubscriptionBridge.cs(public sealed class : ITagSubscriber, IDisposable)。web 发 sub/unsub 订阅一批点名,本桥订到 RTDB,把点变按批转成 tag-update 推给网页。
OnTagChanged在 RTDB 分发线程只把批次拷走入内部队列就返回,真正的投影 + JSON 序列化 + Post 在本桥自己的工作线程上做(把序列化移出分发线程,否则一路 JSON 会成吞吐墙)。内部队列有界 8192 +DropOldest—— 实时显示只需最新帧,与整条显示车道语义一致。
其它分派器(同形,各管一域)
AuthRpcDispatcher(账号/权限,接 Hmi.Security)、HistoryQueryBridge(query_history / aggregate_history,不进 MCP)、AiRpcDispatcher(内置 AI 对话)、McpControlRpcDispatcher(MCP 服务器启停/配置)、UiOverrideBridge(脚本改界面 → 推 ui-prop-update)。出站统一经 IWebMessageSink。
Hmi.Mcp.Host —— 把能力开给外部 AI
职责:一个 exe,把 ProjectAutomation 的组态/感知方法包成 MCP 工具,交给外部 AI 客户端。依赖 Automation(+ ScriptRuntime 做脚本校验)。官方 MCP SDK。
入口 Program.cs —— 两种传输
Hmi.Mcp.Host <工程目录> → stdio(Claude Desktop / Cursor 各自拉起)
Hmi.Mcp.Host <工程目录> --http <host> <port> [--token t]
[--read-only] [--tools a,b,c] → http 常驻(本软件「MCP 服务器」面板启动)
工程目录也可经环境变量 HMI_PROJECT_DIR 指定。
⚠️ stdio 模式:stdout 是 JSON-RPC 通道,任何日志/诊断必须走 stderr,否则污染协议流。
配套类型
McpProjectHost.Open(projectDir)—— 打开工程 + 标记来源 + 接脚本编译校验器,一处收口(测试也走它,避免两个出口形状不一致)。ProjectMcpTools—— 工具清单(逐个方法对应第 06 章的组态/感知能力)。McpToolFilter——--read-only(只留感知)/--tools a,b,c(白名单)在这里落地。McpArgs.Parse(args)—— 命令行解析。
边界不变:只组态 + 感知,没有 PLC 写。
--read-only再收一层,只剩只读。
Hmi.RuntimeCore.Host —— 运行内核子进程
职责:一个 exe,装配工程 + 起采集 + 挂历史/报警/脚本,作监督者的受管子进程。依赖 Backend、ScriptRuntime、Scripting、Project。
Hmi.RuntimeCore.Host <工程目录> [--crash-after-ready <ms>]
stdout 行协议(供 Hmi.Supervisor 判活):
- 装配 + 起采集成功 → 打印恰好一行
RUNTIMECORE READY; - 之后每秒一行
HEARTBEAT <n>; - Ctrl+C / 父进程 Kill / stdin EOF → 干净停机(
RuntimeHost.StopAsync); --crash-after-ready <ms>:就绪后故意异常退出,仅供监督者"崩溃隔离/重启"测试。
内部就是把前几章接起来:ProjectLoader.Load → new RuntimeHost(project) → RuntimeDataServices.Compose(报警 + 历史订到 RTDB,历史库落工程 history/)→ host.Start() → 起脚本宿主(写值闸门:内部变量放行、通道点挡下;Comm.* 接 RuntimeHost 查在线/重连;Fs.* 围栏在工程 data/;Log.* → stderr)。热路径(RTDB + 采集)全在本进程内,不跨进程。
Hmi.Supervisor —— 进程管理器(监督者)
职责:仿力控——把实时库/日志等后台拆成独立进程集中托管,统一起停、无孤儿、崩溃重启。自成一体,无 Hmi.* 依赖。
入口 ProcessSupervisor : IDisposable
src/Hmi.Supervisor/ProcessSupervisor.cs:
| 成员 | 作用 |
|---|---|
StartSessionAsync(specs, ct) |
按顺序拉起每个服务并各等就绪;任一就绪失败则回滚(停已起的)整体抛 |
StopSessionAsync(...) |
一键全清 |
Dispose() |
杀掉所有子进程(OS 保证回收,无孤儿) |
bool IsRunning(name) / RunningServices |
判活 / 列举(诊断) |
Action<ServiceSpec,int>? ServiceAbandoned |
某服务耗尽重启次数仍崩、被放弃时触发 |
崩溃时按 ServiceSpec.AutoRestart 重启或上报,故障不跨进程扩散。
服务描述 ServiceSpec
Name、ExePath、Arguments、WorkingDirectory、ReadyMarker(如 "RUNTIMECORE READY",子进程 stdout 打印含此串的一行即视就绪)、ReadyTimeout(默认 10s)、AutoRestart、MaxRestarts(默认 3)。单个子进程由 ManagedProcess 托管。
本层只做控制面(启停/就绪/心跳,走子进程 stdout 行协议);数据面(前端订点/写值)走前端 WebView2 那条路。
Hmi.Shell —— WPF 薄壳(编辑器/运行程序的窗)
职责:唯一的 net8.0-windows(WPF)程序集。装 WebView2、把网页和 WebMessageRouter 接起来。壳很薄,能力都在下面的库里。
App.xaml.cs—— 进程入口;--runtime <工程目录>走运行态(带登录门禁),否则编辑器。MainWindow.xaml.cs—— 建 WebView2、装虚拟主机加载前端、postMessage → Router.HandleInbound、把WebView2Sink接到路由器 sink;宿主侧管道(选目录/选文件/拉运行态/预览内核)在此实现并注入路由器。WebView2Sink : IWebMessageSink—— 出站:路由器要发的 JSON 经CoreWebView2.PostWebMessageAsJson发出;订阅推送在 RTDB 分发线程回调,这里 marshal 回 UI 线程再调 WebView2(它只能在建它的线程上用)。UiZoomStore、ShellLog。
编辑器进程以
configMode = true起AuthService(权限恒放行,见第 05 章);运行程序--runtime传false,登录/权限/等级约束全生效。
小结 —— 三条出口
- 给前端:
WebMessageRouter+AutomationRpcDispatcher(+ 运行态TagSubscriptionBridge),Hmi.Shell装 WebView2 把它接活。 - 给外部 AI:
Hmi.Mcp.Hostexe,stdio 或 http,McpToolFilter收权限。 - 拆进程托管:
Hmi.RuntimeCore.Host作子进程 +Hmi.Supervisor监督,行协议判活、崩溃重启、无孤儿。
三条出口共用同一条安全边界:组态 + 感知放行,运行态 PLC 写值不从这些出口走。