03 · 脚本引擎与运行时脚本宿主 —— Hmi.Scripting / Hmi.ScriptRuntime
Hmi.Scripting 是纯粹的编译/执行引擎和函数库表面;Hmi.ScriptRuntime 把它接到真 RTDB / 历史 / 报警上,驱动"启动/退出/条件/周期/画面/元素事件"各类脚本。
Hmi.Scripting —— Roslyn 引擎 + 函数库
职责:C# 脚本引擎(Roslyn)+ 暴露给用户脚本的函数库表面(ScriptGlobals)+ 安全分析器。依赖 Hmi.Core;Microsoft.CodeAnalysis.CSharp.Scripting 4.9.2。
引擎 ScriptEngine
src/Hmi.Scripting/Engine/ScriptEngine.cs(public sealed class):
Compile(name, code)—— 先安全分析 → Roslyn 编译 → 缓存。Task<ScriptRunResult> RunAsync(name, code, ScriptGlobals globals, timeout, ct)—— 带协作式超时执行。ClearCache()。
用 CSharpScript,以 ScriptGlobals 作为脚本的全局类型(脚本里直接写 Tag.Read(...) 就是调它)。
函数库表面 ScriptGlobals
src/Hmi.Scripting/ScriptGlobals.cs(public sealed class)—— 暴露给用户脚本的所有函数库:
Tag Log Sys Timer Math2 Str Bit Fs
Alarm History Db Ui Comm Report Recipe
构造时注入 ITagGateway + 可选宿主服务;没接上的服务给占位实现(调用返回"不可用"而不是崩)。
宿主能力接口 + 安全闸
Hmi.Scripting.Abstractions(HostServices.cs):IScriptLogSink、IAlarmService、IHistoryService、IDbService、IUiService、ICommService、IReportService、IRecipeService。文件访问在 Hmi.Scripting.Api.FileApi 里做围栏;ScriptSafetyAnalyzer 在编译前拦危险代码。
Hmi.ScriptRuntime —— 把脚本接到真实运行
职责:运行时脚本宿主 + 组合运行数据服务。依赖 Core、Scripting、Historian、Alarms、Project、Automation。
宿主 ScriptRuntimeHost : IDisposable
src/Hmi.ScriptRuntime/ScriptRuntimeHost.cs。构造 (ScriptDispatcher dispatcher, LoadedProject project):
StartAsync—— 跑启动脚本 + 起全局条件/周期定时器。OpenScreenAsync—— 旧画面退出脚本 → 新画面进入脚本 → 切换画面周期定时器。RaiseElementEventAsync/RunNamedScriptAsync/StopAsync。
单个
SemaphoreSlim串行化所有脚本执行;每脚本TickGate防堆积(周期/条件上一拍没跑完就合并丢弃,生命周期事件永远等待闸门)。AlarmNotifier发软件启动/停止邮件。
组装器 ScriptRuntimeBuilder
构造 (RtdbCore rtdb, IReadOnlyList<TagDef> tags, IActionDispatcher writer);链式 WithHistorian / WithAlarms / WithLog / WithCurrentUser / WithDatabase / WithUi / WithReport / WithRecipe / WithComm / WithFileRoot;ScriptGlobals Build() 把 RtdbTagGateway + HistorianHistoryService + AlarmEngineService 接好。这就是脚本宿主对接 RTDB/历史/报警的地方。
一键组合 RuntimeDataServices.Compose
RuntimeDataServices : IDisposable。静态 Compose(RtdbCore rtdb, LoadedProject project, string historianDbPath, IHistorian? historian, ...) —— 映射报警/历史配置,建 AlarmEngine + AlarmEvaluationTicker + SqliteHistorian + HistorianRecorder + RetentionSweeper + LinkedVariableMirror,并把它们经 Lossless 车道订阅到 RTDB。暴露 Alarms / Recorder / Historian / Retention / AlarmTicker / …。
编译校验实现 RoslynScriptChecker : IScriptCompiler
接口 IScriptCompiler 声明在 Hmi.Automation(依赖倒置),实现在这里。构造 (LoadedProject project, ScriptEngine? engine = null);CheckFragment / CheckProject,把 ScriptDiagnostic 映射成 ScriptIssue。编辑器和 MCP 两处都注入它做 F7 编译校验。
还有:
ScriptDispatcher(触发路由)、ForwardingLogSink、RuntimeWriteGate(写值闸:内部变量放行、通道/PLC 点挡住脚本侧写入) —— 安全边界在脚本这一层的落点。
小结
- 只想编译校验一段脚本 →
ScriptEngine或RoslynScriptChecker。 - 想让脚本在运行时真的读点、记历史、触发报警 →
ScriptRuntimeBuilder/RuntimeDataServices.Compose组好再交给ScriptRuntimeHost。