Преглед на файлове

feat(operate): 改为拉起独立 control 进程(探活+Process.Start+轮询)

阶段1-Task6:移除进程内 StartRun,新增 ControlProcessLauncher;
App.config 加 controlPort/controlExePath。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
huangjie преди 3 дни
родител
ревизия
9ca63082f1

+ 3 - 0
ivf_tl_operate_2.0/ivf_tl_Operate/App.config

@@ -65,5 +65,8 @@
 		<add key="StopPro" value="15"/>
 		<!--缓存盘符(operate MainWindow.xaml.cs:96 透传 → PathHelper.pan;缺键会致路径异常)  本地层-->
 		<add key="cacheDisk" value="C" />
+		<!-- 双进程:control 本地HTTP端口 + control.exe 路径(相对 operate 输出目录) -->
+		<add key="controlPort" value="38080" />
+		<add key="controlExePath" value="control\ivf_tl_ControlHost.exe" />
 	</appSettings>
 </configuration>

+ 74 - 0
ivf_tl_operate_2.0/ivf_tl_Operate/Helpers/ControlProcessLauncher.cs

@@ -0,0 +1,74 @@
+using System;
+using System.Configuration;
+using System.Diagnostics;
+using System.IO;
+using System.Net.Http;
+using System.Threading;
+
+namespace ivf_tl_Operate.Helpers
+{
+    /// <summary>
+    /// operate 侧:确保 control 独立进程在运行。
+    /// 探 /ping → 不在则 Process.Start 拉起 control.exe(命令行传账号)→ 轮询就绪。
+    /// operate 是管理员、control 也是 requireAdministrator,拉起不再弹 UAC。
+    /// </summary>
+    public static class ControlProcessLauncher
+    {
+        private static readonly HttpClient _http = new HttpClient { Timeout = TimeSpan.FromSeconds(2) };
+
+        public static int Port =>
+            int.TryParse(ConfigurationManager.AppSettings["controlPort"], out var p) ? p : 38080;
+
+        /// <summary>探活:control 的 /ping 是否可达。</summary>
+        public static bool IsControlAlive()
+        {
+            try
+            {
+                var resp = _http.GetAsync($"http://127.0.0.1:{Port}/ping").GetAwaiter().GetResult();
+                return resp.IsSuccessStatusCode;
+            }
+            catch { return false; }
+        }
+
+        /// <summary>
+        /// 确保 control 在跑:已在→直接返回 true;不在→拉起并轮询就绪(最多 waitSeconds)。
+        /// </summary>
+        public static bool EnsureRunning(string account, string password, string cacheDisk,
+            Action<string> log, int waitSeconds = 60)
+        {
+            log = log ?? (_ => { });
+            if (IsControlAlive()) { log("control 已在运行,直接连接"); return true; }
+
+            string exe = ResolveExePath();
+            if (!File.Exists(exe)) { log("找不到 control.exe:" + exe); return false; }
+
+            var psi = new ProcessStartInfo
+            {
+                FileName = exe,
+                Arguments = $"--account={account} --password={password} --cacheDisk={cacheDisk} --port={Port}",
+                UseShellExecute = true,   // requireAdministrator 进程需 ShellExecute 提权启动
+                WindowStyle = ProcessWindowStyle.Hidden
+            };
+            try { Process.Start(psi); }
+            catch (Exception ex) { log("拉起 control 失败:" + ex.Message); return false; }
+            log("已拉起 control.exe,等待就绪…");
+
+            for (int i = 0; i < waitSeconds; i++)
+            {
+                Thread.Sleep(1000);
+                if (IsControlAlive()) { log("control 就绪"); return true; }
+            }
+            log("control 拉起后等待就绪超时");
+            return false;
+        }
+
+        private static string ResolveExePath()
+        {
+            string rel = ConfigurationManager.AppSettings["controlExePath"]
+                         ?? @"control\ivf_tl_ControlHost.exe";
+            return Path.IsPathRooted(rel)
+                ? rel
+                : Path.Combine(AppDomain.CurrentDomain.BaseDirectory, rel);
+        }
+    }
+}

+ 10 - 55
ivf_tl_operate_2.0/ivf_tl_Operate/MainWindow.xaml.cs

@@ -60,74 +60,29 @@ namespace ivf_tl_Operate
             AppData.Instance.MainPageView = mainPageView;
             LoadPage(mainPageView);
 
-            // M1-01 步骤3:operate 登录成功后,在后台线程托管 control 的 StartMain.StartRun()。
-            // StartRun 内部阻塞(AppData.StartAsync().Wait()),必须放后台线程避免卡 UI。
-            // 复刻原 control Window1_Loaded1 的 Task.Run 形态,但去掉 Environment.Exit(0)——
-            // 合并后 control 后台启动失败不应杀掉前台 operate 进程,改为捕获异常 + 记录日志降级。
+            // 双进程改造:control 已剥离为独立进程 ivf_tl_ControlHost.exe。
+            // operate 登录后只负责"确保 control 在跑"(探活→不在则拉起→轮询就绪),
+            // 不再在本进程内跑 StartRun。operate 关闭后 control 继续驱动机器。
             System.Threading.Tasks.Task.Run(() =>
             {
                 try
                 {
-                    // M1-02 步骤2:单登录账号透传。
-                    // 合并后只保留 operate 的 LoginWindow,control 的 Window1 登录窗已从启动路径剔除。
-                    // 把 operate 登录成功后保存的账号/密码(AppData.Instance.CurrentUserInfo)
-                    // 透传给 control 的 AppData.Login,使 control 后台用同一账号工作,不再弹独立登录窗。
-                    // 复刻原 control Window1_Loaded1(Window1.xaml.cs:51-87)里 StartRun 之前的前置:
-                    //   ① ivf_tl_Control.AppData.Instance.Login(account, password)
-                    //   ② PathHelper.pan / LogService.Pan = cacheDisk(缓存盘,仍从 control 侧 config 读,配置统一属 M5)
-                    // 去掉原 Window1 里的 Environment.Exit(0):合并后 control 登录/启动失败不杀前台进程,仅记日志降级。
                     string account = AppData.Instance.CurrentUserInfo.account;
                     string password = AppData.Instance.CurrentUserInfo.password;
-
+                    string cacheDisk = System.Configuration.ConfigurationManager.AppSettings["cacheDisk"] ?? "C";
                     if (string.IsNullOrEmpty(account) || string.IsNullOrEmpty(password))
                     {
-                        ivf_tl_Services.Log4netHelper.WriteLog("control 后台启动跳过:operate 登录账号/密码为空");
-                        return;
-                    }
-
-                    // ① control 端登录(与 operate 同一账号)。失败仅记日志、不退进程(V-012 真机验证)。
-                    if (!ivf_tl_Control.AppData.Instance.Login(account, password))
-                    {
-                        ivf_tl_Services.Log4netHelper.WriteLog("control 后台登录失败:单登录账号透传未通过 control 端 AppData.Login");
+                        ivf_tl_Services.Log4netHelper.WriteLog("跳过拉起 control:operate 登录账号/密码为空");
                         return;
                     }
-
-                    // ② 缓存盘:沿用 control 原 config 项 cacheDisk(StartRun 内随后会用服务器 tmpDir 覆盖 PathHelper.pan)。
-                    string cacheDisk = System.Configuration.ConfigurationManager.AppSettings["cacheDisk"];
-                    if (!string.IsNullOrEmpty(cacheDisk))
-                    {
-                        ivf_tl_UtilHelper.PathHelper.pan = cacheDisk;
-                        ivf_tl_Control.AppData.Instance.LogService.Pan = cacheDisk;
-                    }
-
-                    var startMain = new ivf_tl_Control.StartMain();   // 已并入的 control 类库
-
-                    // M1-03 步骤3:HAL 单例在 control StartRun 之前初始化一次(设备发现),
-                    // 确保后台采集与前台调试拿到的是同一组句柄(同 COM 口/同相机唯一持有)。
-                    // ⚠ 待验证 V-027:HAL.ScanDevices 设备发现稳定(相机index/COM漂移、CCDSN配对)。
-                    try
-                    {
-                        IvfTl.Hardware.Impl.HardwareAccessLayer.Instance.Log =
-                            msg => ivf_tl_Services.Log4netHelper.WriteLog(msg);
-                        var devices = IvfTl.Hardware.Impl.HardwareAccessLayer.Instance.ScanDevices();
-                        ivf_tl_Services.Log4netHelper.WriteLog($"HAL 设备发现完成:发现 {devices.Count} 个舱");
-                    }
-                    catch (Exception hex)
-                    {
-                        // HAL 发现失败不阻断 control 启动(control 自身仍会枚举),仅记日志降级。
-                        ivf_tl_Services.Log4netHelper.WriteLog("HAL 设备发现异常(降级)", hex);
-                    }
-
-                    string err = startMain.StartRun();                 // 阻塞跑 InitTL→InitHouse→StartAsync
-                    if (!string.IsNullOrEmpty(err))
-                        ivf_tl_Services.Log4netHelper.WriteLog($"control 后台启动失败:{err}");
-                    else
-                        ivf_tl_Services.Log4netHelper.WriteLog("control 后台启动成功");
+                    bool ok = ivf_tl_Operate.Helpers.ControlProcessLauncher.EnsureRunning(
+                        account, password, cacheDisk,
+                        msg => ivf_tl_Services.Log4netHelper.WriteLog(msg));
+                    ivf_tl_Services.Log4netHelper.WriteLog(ok ? "control 进程就绪" : "control 进程未就绪(降级:operate 仍可用)");
                 }
                 catch (Exception ex)
                 {
-                    // 不退进程:仅记录日志,前台 operate 继续可用(自愈策略属后续)。
-                    ivf_tl_Services.Log4netHelper.WriteLog("control 后台启动异常", ex);
+                    ivf_tl_Services.Log4netHelper.WriteLog("确保 control 运行异常", ex);
                 }
             });
         }