diff --git a/Ink Canvas.sln b/Ink Canvas.sln index 0bf57c17..7907e2c2 100644 --- a/Ink Canvas.sln +++ b/Ink Canvas.sln @@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InkCanvas.PluginSdk", "InkC EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InkCanvas.Controls", "InkCanvas.Controls\InkCanvas.Controls.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InkCanvasForClass.IACoreHelper", "InkCanvasForClass.IACoreHelper\InkCanvasForClass.IACoreHelper.csproj", "{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -83,6 +85,26 @@ Global {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x64.Build.0 = Release|Any CPU {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x86.ActiveCfg = Release|Any CPU {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x86.Build.0 = Release|Any CPU + {B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Debug|Any CPU.ActiveCfg = Debug|x86 + {B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Debug|Any CPU.Build.0 = Debug|x86 + {B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Debug|ARM.ActiveCfg = Debug|x86 + {B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Debug|ARM.Build.0 = Debug|x86 + {B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Debug|ARM64.ActiveCfg = Debug|x86 + {B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Debug|ARM64.Build.0 = Debug|x86 + {B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Debug|x64.ActiveCfg = Debug|x86 + {B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Debug|x64.Build.0 = Debug|x86 + {B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Debug|x86.ActiveCfg = Debug|x86 + {B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Debug|x86.Build.0 = Debug|x86 + {B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Release|Any CPU.ActiveCfg = Release|x86 + {B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Release|Any CPU.Build.0 = Release|x86 + {B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Release|ARM.ActiveCfg = Release|x86 + {B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Release|ARM.Build.0 = Release|x86 + {B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Release|ARM64.ActiveCfg = Release|x86 + {B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Release|ARM64.Build.0 = Release|x86 + {B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Release|x64.ActiveCfg = Release|x86 + {B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Release|x64.Build.0 = Release|x86 + {B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Release|x86.ActiveCfg = Release|x86 + {B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Ink Canvas/App.xaml.cs b/Ink Canvas/App.xaml.cs index c2d05853..905abd75 100644 --- a/Ink Canvas/App.xaml.cs +++ b/Ink Canvas/App.xaml.cs @@ -1202,6 +1202,17 @@ namespace Ink_Canvas LogHelper.WriteLogToFile($"释放IACore DLL时出错: {ex.Message}", LogHelper.LogType.Error); } + try + { + LogHelper.WriteLogToFile("启动 IACore IPC 辅助进程"); + bool ipcStarted = IpcIACoreClient.Instance.Start(); + LogHelper.WriteLogToFile($"IACore IPC 辅助进程{(ipcStarted ? "启动成功" : "启动失败(将使用本地 IACore 回退)")}"); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"启动 IACore IPC 辅助进程时出错: {ex.Message}", LogHelper.LogType.Error); + } + try { LogHelper.WriteLogToFile("开始注册.icstk文件关联"); @@ -1577,6 +1588,13 @@ namespace Ink_Canvas private void App_Exit(object sender, ExitEventArgs e) { CleanupTerminationMonitoring(); + + try + { + IpcIACoreClient.Instance.Dispose(); + } + catch { } + // 卸载所有插件 try { diff --git a/Ink Canvas/Helpers/InkRecognitionManager.cs b/Ink Canvas/Helpers/InkRecognitionManager.cs index 8c30d975..40b794ab 100644 --- a/Ink Canvas/Helpers/InkRecognitionManager.cs +++ b/Ink Canvas/Helpers/InkRecognitionManager.cs @@ -96,8 +96,10 @@ namespace Ink_Canvas.Helpers return RecognizeShapeWinRtOnDispatcherContext(strokes); } - var legacy = InkRecognizeHelper.RecognizeShapeIACore(strokes); - return Task.FromResult(InkRecognizeHelper.FromIACoreOrEmpty(legacy)); + // IACore 必须走 IPC 辅助进程(x86/.NET 4.7.2)。 + // 在 .NET 6 x64 主进程中本地加载 IAWinFX 会失败,故不再本地回退。 + var ipcResult = IpcIACoreClient.Instance.Recognize(strokes); + return Task.FromResult(ipcResult); } catch (Exception ex) { @@ -214,9 +216,11 @@ namespace Ink_Canvas.Helpers public string GetSystemInfo() { - return _isModernSystemAvailable - ? $"现代化墨迹识别系统 (Windows Runtime API) - 进程架构: {Environment.Is64BitProcess}" - : $"传统墨迹识别系统 (IACore) - 进程架构: {Environment.Is64BitProcess}"; + if (_isModernSystemAvailable) + return $"现代化墨迹识别系统 (Windows Runtime API) - 进程架构: {Environment.Is64BitProcess}"; + if (IpcIACoreClient.Instance.IsAvailable) + return $"传统墨迹识别系统 (IACore via IPC) - 进程架构: {Environment.Is64BitProcess}"; + return $"传统墨迹识别系统 (IACore 本地) - 进程架构: {Environment.Is64BitProcess}"; } public void Dispose() diff --git a/Ink Canvas/Helpers/InkRecognizeHelper.cs b/Ink Canvas/Helpers/InkRecognizeHelper.cs index ed56e693..35d5be84 100644 --- a/Ink Canvas/Helpers/InkRecognizeHelper.cs +++ b/Ink Canvas/Helpers/InkRecognizeHelper.cs @@ -1,79 +1,15 @@ -using System.Linq; using System.Threading.Tasks; -using System.Windows; using System.Windows.Ink; -using System.Windows.Media; namespace Ink_Canvas.Helpers { + /// + /// 墨迹形状/手写识别的对外门面。 + /// IACore 路径通过 IPC 调用 x86 辅助进程;WinRT 路径在主进程内直接调用。 + /// 主进程 (.NET 6 x64) 不再直接引用 IAWinFX 类型。 + /// public class InkRecognizeHelper { - /// IACore / IAWinFX 形状识别(典型用于 32 位进程)。 - public static ShapeRecognizeResult RecognizeShapeIACore(StrokeCollection strokes) - { - if (strokes == null || strokes.Count == 0) - return default; - - var analyzer = new InkAnalyzer(); - analyzer.AddStrokes(strokes); - analyzer.SetStrokesType(strokes, StrokeType.Drawing); - - AnalysisAlternate analysisAlternate = null; - int strokesCount = strokes.Count; - var sfsaf = analyzer.Analyze(); - if (sfsaf.Successful) - { - var alternates = analyzer.GetAlternates(); - if (alternates.Count > 0) - { - while (strokesCount >= 2) - { - var alt0 = alternates[0]; - if (alt0?.AlternateNodes == null || alt0.AlternateNodes.Count == 0) - break; - var drawNode = alt0.AlternateNodes[0] as InkDrawingNode; - if (drawNode == null) - break; - var shapeOk = IsContainShapeType(drawNode.GetShapeName()); - if (alt0.Strokes.Contains(strokes.Last()) && shapeOk) - break; - analyzer.RemoveStroke(strokes[strokes.Count - strokesCount]); - strokesCount--; - sfsaf = analyzer.Analyze(); - if (sfsaf.Successful) - alternates = analyzer.GetAlternates(); - else - break; - if (alternates.Count == 0) - break; - } - if (alternates.Count > 0) - { - var altFinal = alternates[0]; - if (altFinal?.AlternateNodes != null && altFinal.AlternateNodes.Count > 0) - analysisAlternate = altFinal; - } - } - } - - analyzer.Dispose(); - - if (analysisAlternate != null && analysisAlternate.AlternateNodes != null && analysisAlternate.AlternateNodes.Count > 0) - { - var node = analysisAlternate.AlternateNodes[0] as InkDrawingNode; - if (node == null) - return default; - return new ShapeRecognizeResult(node.Centroid, node.HotPoints, analysisAlternate, node); - } - - return default; - } - - /// 兼容旧调用:等价于 - public static ShapeRecognizeResult RecognizeShape(StrokeCollection strokes) => - RecognizeShapeIACore(strokes); - - /// 按设置选择 WinRT()或 IACore;WinRT 请用 public static InkShapeRecognitionResult RecognizeShapeUnified( StrokeCollection strokes, ShapeRecognitionEngineMode mode) @@ -84,11 +20,9 @@ namespace Ink_Canvas.Helpers if (ShapeRecognitionRouter.ResolveUseWinRt(mode)) return InkShapeRecognitionResult.Empty; - var legacy = RecognizeShapeIACore(strokes); - return FromIACoreOrEmpty(legacy); + return IpcIACoreClient.Instance.Recognize(strokes); } - /// 与 CE 反编译版 InkRecognitionManager.RecognizeShapeAsync 对齐的统一入口。 public static Task RecognizeShapeUnifiedAsync( StrokeCollection strokes, ShapeRecognitionEngineMode mode) @@ -109,7 +43,9 @@ namespace Ink_Canvas.Helpers WinRtHandwritingRecognizer.Warmup(); } else - RecognizeShapeIACore(new StrokeCollection()); + { + IpcIACoreClient.Instance.Start(); + } } catch { @@ -117,13 +53,11 @@ namespace Ink_Canvas.Helpers } } - /// WinRT 手写识别(Windows 10+)。 public static Task RecognizeHandwritingUnifiedAsync( StrokeCollection strokes, ShapeRecognitionEngineMode mode) => InkRecognitionManager.Instance.RecognizeHandwritingAsync(strokes, mode); - /// WinRT 下将识别成功的词替换为手写体字形墨迹;是否应用由设置「WinRT 识别转手写体字形」控制。 public static Task CorrectHandwritingStrokesUnifiedAsync( StrokeCollection strokes, ShapeRecognitionEngineMode mode) => @@ -133,7 +67,6 @@ namespace Ink_Canvas.Helpers MainWindow.Settings?.InkToShape?.EnableWinRtHandwritingStrokeBeautify ?? false, MainWindow.Settings?.InkToShape?.HandwritingCorrectionFontFamily); - /// 显式指定是否应用手写体字形替换(忽略开关);字体仍从设置读取。 public static Task CorrectHandwritingStrokesUnifiedAsync( StrokeCollection strokes, ShapeRecognitionEngineMode mode, @@ -144,53 +77,18 @@ namespace Ink_Canvas.Helpers applyHandwritingBeautify, MainWindow.Settings?.InkToShape?.HandwritingCorrectionFontFamily); - internal static InkShapeRecognitionResult FromIACoreOrEmpty(ShapeRecognizeResult legacy) - { - if (legacy?.InkDrawingNode == null) - return InkShapeRecognitionResult.Empty; - - var node = legacy.InkDrawingNode; - var shape = node.GetShape(); - if (shape == null) - return InkShapeRecognitionResult.Empty; - - var hot = ClonePointCollection(node.HotPoints); - return new InkShapeRecognitionResult( - node.GetShapeName(), - legacy.Centroid, - hot, - shape.Width, - shape.Height, - node.Strokes); - } - - private static PointCollection ClonePointCollection(PointCollection src) - { - var dst = new PointCollection(); - if (src == null) return dst; - foreach (System.Windows.Point p in src) - dst.Add(p); - return dst; - } - public static bool IsContainShapeType(string name) { if (string.IsNullOrEmpty(name)) return false; - if (name.Contains("Triangle") || name.Contains("Circle") || - name.Contains("Rectangle") || name.Contains("Diamond") || - name.Contains("Parallelogram") || name.Contains("Square") - || name.Contains("Ellipse")) - { - return true; - } - return false; + return name.Contains("Triangle") || name.Contains("Circle") || + name.Contains("Rectangle") || name.Contains("Diamond") || + name.Contains("Parallelogram") || name.Contains("Square") || + name.Contains("Ellipse"); } } - //Recognizer 的实现 - public enum RecognizeLanguage { SimplifiedChinese = 0x0804, @@ -198,127 +96,17 @@ namespace Ink_Canvas.Helpers English = 0x0809 } - public class ShapeRecognizeResult - { - public ShapeRecognizeResult(Point centroid, PointCollection hotPoints, AnalysisAlternate analysisAlternate, InkDrawingNode node) - { - Centroid = centroid; - HotPoints = hotPoints; - AnalysisAlternate = analysisAlternate; - InkDrawingNode = node; - } - - public AnalysisAlternate AnalysisAlternate { get; } - - public Point Centroid { get; set; } - - public PointCollection HotPoints { get; } - - public InkDrawingNode InkDrawingNode { get; } - } - - /// - /// 图形识别类 - /// - //public class ShapeRecogniser - //{ - // public InkAnalyzer _inkAnalyzer = null; - - // private ShapeRecogniser() - // { - // this._inkAnalyzer = new InkAnalyzer - // { - // AnalysisModes = AnalysisModes.AutomaticReconciliationEnabled - // }; - // } - - // /// - // /// 根据笔迹集合返回图形名称字符串 - // /// - // /// - // /// - // public InkDrawingNode Recognition(StrokeCollection strokeCollection) - // { - // if (strokeCollection == null) - // { - // //MessageBox.Show("dddddd"); - // return null; - // } - - // InkDrawingNode result = null; - // try - // { - // this._inkAnalyzer.AddStrokes(strokeCollection); - // if (this._inkAnalyzer.Analyze().Successful) - // { - // result = _internalAnalyzer(this._inkAnalyzer); - // this._inkAnalyzer.RemoveStrokes(strokeCollection); - // } - // } - // catch (System.Exception ex) - // { - // //result = ex.Message; - // System.Diagnostics.Debug.WriteLine(ex.Message); - // } - - // return result; - // } - - // /// - // /// 实现笔迹的分析,返回图形对应的字符串 - // /// 你在实际的应用中根据返回的字符串来生成对应的Shape - // /// - // /// - // /// - // private InkDrawingNode _internalAnalyzer(InkAnalyzer ink) - // { - // try - // { - // ContextNodeCollection nodecollections = ink.FindNodesOfType(ContextNodeType.InkDrawing); - // foreach (ContextNode node in nodecollections) - // { - // InkDrawingNode drawingNode = node as InkDrawingNode; - // if (drawingNode != null) - // { - // return drawingNode;//.GetShapeName(); - // } - // } - // } - // catch (System.Exception ex) - // { - // System.Diagnostics.Debug.WriteLine(ex.Message); - // } - - // return null; - // } - - - // private static ShapeRecogniser instance = null; - // public static ShapeRecogniser Instance - // { - // get - // { - // return instance == null ? (instance = new ShapeRecogniser()) : instance; - // } - // } - //} - - - //用于自动控制其他形状相对于圆的位置 - public class Circle { - public Circle(Point centroid, double r, Stroke stroke) + public Circle(System.Windows.Point centroid, double r, Stroke stroke) { Centroid = centroid; R = r; Stroke = stroke; } - public Point Centroid { get; set; } - + public System.Windows.Point Centroid { get; set; } public double R { get; set; } - public Stroke Stroke { get; set; } } -} +} \ No newline at end of file diff --git a/Ink Canvas/Helpers/IpcIACoreClient.cs b/Ink Canvas/Helpers/IpcIACoreClient.cs new file mode 100644 index 00000000..9910c6f0 --- /dev/null +++ b/Ink Canvas/Helpers/IpcIACoreClient.cs @@ -0,0 +1,302 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.IO.Pipes; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Ink; +using System.Windows.Media; + +namespace Ink_Canvas.Helpers +{ + /// + /// 通过 Named Pipe IPC 调用 32 位辅助进程 InkCanvasForClass.IACoreHelper.exe 进行 IACore 形状识别。 + /// 单例,线程安全,辅助进程崩溃后自动重启。 + /// + public sealed class IpcIACoreClient : IDisposable + { + // ── 单例 ────────────────────────────────────────────────────────────── + private static IpcIACoreClient _instance; + private static readonly object _instanceLock = new object(); + + public static IpcIACoreClient Instance + { + get + { + if (_instance == null) + lock (_instanceLock) + if (_instance == null) + _instance = new IpcIACoreClient(); + return _instance; + } + } + + // ── 状态 ────────────────────────────────────────────────────────────── + private Process _helperProcess; + private readonly object _pipeLock = new object(); + private bool _disposed; + private bool _available; + + // 辅助进程 EXE 的路径(与主 EXE 同目录) + private static string HelperExePath => + Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "InkCanvasForClass.IACoreHelper.exe"); + + // Named Pipe 名称(含主进程 PID,保证唯一) + private string PipeName => + string.Format("ICC_IACoreHelper_{0}", Process.GetCurrentProcess().Id); + + private IpcIACoreClient() { } + + // ── 公开 API ────────────────────────────────────────────────────────── + + /// 启动辅助进程,返回是否成功。 + public bool Start() + { + if (_disposed) return false; + + // 已运行则无需重启,避免 Warmup + App 两处调用导致重复启动 + if (IsAvailable) return true; + + if (!File.Exists(HelperExePath)) + { + LogHelper.WriteLogToFile($"[IACoreIPC] 辅助进程不存在: {HelperExePath}", LogHelper.LogType.Warning); + _available = false; + return false; + } + + return LaunchHelper(); + } + + /// 是否就绪(辅助进程运行中)。 + public bool IsAvailable => _available && _helperProcess != null && !_helperProcess.HasExited; + + /// + /// 发送笔画到辅助进程进行 IACore 形状识别。 + /// 超时或失败时返回 。 + /// + public InkShapeRecognitionResult Recognize(StrokeCollection strokes) + { + if (strokes == null || strokes.Count == 0) + return InkShapeRecognitionResult.Empty; + + EnsureHelperAlive(); + if (!IsAvailable) + { + LogHelper.WriteLogToFile($"[IACoreIPC] Recognize 跳过:辅助进程不可用(strokes={strokes.Count})", LogHelper.LogType.Warning); + return InkShapeRecognitionResult.Empty; + } + + lock (_pipeLock) + { + try + { + LogHelper.WriteLogToFile($"[IACoreIPC] Recognize 请求:strokes={strokes.Count}"); + var result = SendRecognizeRequest(strokes); + LogHelper.WriteLogToFile( + $"[IACoreIPC] Recognize 响应:IsSuccess={result.IsSuccess}, Shape={result.ShapeName}, StrokesToRemove={result.StrokesToRemove?.Count ?? 0}"); + return result; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"[IACoreIPC] 识别失败: {ex.GetType().Name}: {ex.Message}", LogHelper.LogType.Error); + KillHelper(); + return InkShapeRecognitionResult.Empty; + } + } + } + + // ── 内部实现 ────────────────────────────────────────────────────────── + + private bool LaunchHelper() + { + try + { + KillHelper(); + + var psi = new ProcessStartInfo + { + FileName = HelperExePath, + Arguments = Process.GetCurrentProcess().Id.ToString(), + UseShellExecute = false, + CreateNoWindow = true, + WorkingDirectory = AppDomain.CurrentDomain.BaseDirectory + }; + _helperProcess = Process.Start(psi); + if (_helperProcess == null) + { + _available = false; + return false; + } + _helperProcess.EnableRaisingEvents = true; + _helperProcess.Exited += OnHelperExited; + + // 等待 Pipe 就绪(最多 3 s) + bool pipeReady = WaitForPipe(3000); + _available = pipeReady; + + LogHelper.WriteLogToFile($"[IACoreIPC] 辅助进程启动{(pipeReady ? "成功" : "失败(Pipe 未就绪)")},PID={_helperProcess?.Id}"); + return pipeReady; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"[IACoreIPC] 启动辅助进程失败: {ex.Message}", LogHelper.LogType.Error); + _available = false; + return false; + } + } + + private bool WaitForPipe(int timeoutMs) + { + int elapsed = 0; + while (elapsed < timeoutMs) + { + if (_helperProcess == null || _helperProcess.HasExited) + return false; + + try + { + using (var probe = new NamedPipeClientStream(".", PipeName, PipeDirection.InOut)) + { + probe.Connect(200); + // 连接成功后立即断开,下次 Recognize 会重新连接 + return true; + } + } + catch + { + Thread.Sleep(100); + elapsed += 300; + } + } + return false; + } + + private InkShapeRecognitionResult SendRecognizeRequest(StrokeCollection strokes) + { + using (var client = new NamedPipeClientStream(".", PipeName, PipeDirection.InOut)) + { + client.Connect(IpcTimeoutMs); + + using (var writer = new BinaryWriter(client, System.Text.Encoding.UTF8, leaveOpen: true)) + using (var reader = new BinaryReader(client, System.Text.Encoding.UTF8, leaveOpen: true)) + { + // 序列化请求 + writer.Write(CmdRecognize); + writer.Write(strokes.Count); + foreach (var stroke in strokes) + { + var pts = stroke.StylusPoints; + writer.Write(pts.Count); + foreach (var pt in pts) + { + writer.Write((float)pt.X); + writer.Write((float)pt.Y); + writer.Write(pt.PressureFactor); + } + } + writer.Flush(); + + // 读取响应 + bool success = reader.ReadBoolean(); + string shape = reader.ReadString(); + float cx = reader.ReadSingle(); + float cy = reader.ReadSingle(); + float width = reader.ReadSingle(); + float height = reader.ReadSingle(); + + int hotLen = reader.ReadInt32(); + var hotPoints = new PointCollection(); + for (int i = 0; i < hotLen; i++) + hotPoints.Add(new Point(reader.ReadSingle(), reader.ReadSingle())); + + int idxLen = reader.ReadInt32(); + var indices = new int[idxLen]; + for (int i = 0; i < idxLen; i++) + indices[i] = reader.ReadInt32(); + + if (!success || string.IsNullOrEmpty(shape)) + return InkShapeRecognitionResult.Empty; + + // 根据下标还原参与识别的笔画子集 + var recognized = new StrokeCollection(); + foreach (int idx in indices) + if (idx >= 0 && idx < strokes.Count) + recognized.Add(strokes[idx]); + + return new InkShapeRecognitionResult( + shape, + new Point(cx, cy), + hotPoints, + width, + height, + recognized); + } + } + } + + private void EnsureHelperAlive() + { + if (!IsAvailable) + { + LogHelper.WriteLogToFile("[IACoreIPC] 辅助进程不可用,尝试重启...", LogHelper.LogType.Warning); + LaunchHelper(); + } + } + + private void OnHelperExited(object sender, EventArgs e) + { + _available = false; + LogHelper.WriteLogToFile("[IACoreIPC] 辅助进程意外退出", LogHelper.LogType.Warning); + } + + private void KillHelper() + { + if (_helperProcess == null) return; + try + { + // 先解除事件订阅,避免主动 Kill 时触发 OnHelperExited 误报 + try { _helperProcess.Exited -= OnHelperExited; } catch { } + + if (!_helperProcess.HasExited) + { + // 发送优雅关闭命令 + try + { + using (var client = new NamedPipeClientStream(".", PipeName, PipeDirection.InOut)) + { + client.Connect(500); + using (var w = new BinaryWriter(client)) + w.Write(CmdShutdown); + } + } + catch { /* 忽略,下面直接 Kill */ } + + if (!_helperProcess.WaitForExit(800)) + _helperProcess.Kill(); + } + } + catch { } + finally + { + _helperProcess?.Dispose(); + _helperProcess = null; + _available = false; + } + } + + public void Dispose() + { + if (_disposed) return; + _disposed = true; + KillHelper(); + } + + // ── 协议常量(与 IACoreHelper/IpcProtocol.cs 保持一致) ───────────── + private const int IpcTimeoutMs = 5000; + private const byte CmdRecognize = 0x01; + private const byte CmdShutdown = 0xFF; + } +} \ No newline at end of file diff --git a/Ink Canvas/InkCanvasForClass.csproj b/Ink Canvas/InkCanvasForClass.csproj index 6dc1f47a..2721fa3e 100644 --- a/Ink Canvas/InkCanvasForClass.csproj +++ b/Ink Canvas/InkCanvasForClass.csproj @@ -101,20 +101,6 @@ x64 false - - - .\IACore.dll - False - - - .\IALoader.dll - False - - - .\IAWinFX.dll - False - - @@ -153,6 +139,10 @@ + + false + false + @@ -668,4 +658,11 @@ + + + + + + + diff --git a/InkCanvasForClass.IACoreHelper/App.config b/InkCanvasForClass.IACoreHelper/App.config new file mode 100644 index 00000000..1dc0cb08 --- /dev/null +++ b/InkCanvasForClass.IACoreHelper/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/InkCanvasForClass.IACoreHelper/InkCanvasForClass.IACoreHelper.csproj b/InkCanvasForClass.IACoreHelper/InkCanvasForClass.IACoreHelper.csproj new file mode 100644 index 00000000..96bb9909 --- /dev/null +++ b/InkCanvasForClass.IACoreHelper/InkCanvasForClass.IACoreHelper.csproj @@ -0,0 +1,56 @@ + + + + + Debug + x86 + {B1A2C3D4-E5F6-7890-ABCD-EF1234567891} + Exe + InkCanvasForClass.IACoreHelper + InkCanvasForClass.IACoreHelper + v4.7.2 + 512 + true + true + + + x86 + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + x86 + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + ..\Ink Canvas\Resources\IACore\IAWinFX.dll + false + + + + + + + + + + + + \ No newline at end of file diff --git a/InkCanvasForClass.IACoreHelper/IpcProtocol.cs b/InkCanvasForClass.IACoreHelper/IpcProtocol.cs new file mode 100644 index 00000000..ed93f411 --- /dev/null +++ b/InkCanvasForClass.IACoreHelper/IpcProtocol.cs @@ -0,0 +1,133 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace InkCanvasForClass.IACoreHelper +{ + // Named Pipe 名称,主进程和辅助进程共用 + internal static class IpcConstants + { + public const string PipeName = "ICC_IACoreHelper_{0}"; // {0} = 主进程 PID + public const int RequestTimeout = 5000; // ms + public const byte CmdRecognize = 0x01; + public const byte CmdShutdown = 0xFF; + } + + // 单个 StylusPoint 的轻量传输结构 + internal struct StylusPointDto + { + public float X; + public float Y; + public float Pressure; + } + + // 单条笔画 + internal class StrokeDto + { + public StylusPointDto[] Points; + } + + // 识别请求(主进程 → 辅助进程) + internal class RecognizeRequest + { + public StrokeDto[] Strokes; + + public void WriteTo(BinaryWriter w) + { + w.Write(IpcConstants.CmdRecognize); + w.Write(Strokes.Length); + foreach (var stroke in Strokes) + { + w.Write(stroke.Points.Length); + foreach (var pt in stroke.Points) + { + w.Write(pt.X); + w.Write(pt.Y); + w.Write(pt.Pressure); + } + } + } + + public static RecognizeRequest ReadFrom(BinaryReader r) + { + int strokeCount = r.ReadInt32(); + var strokes = new StrokeDto[strokeCount]; + for (int i = 0; i < strokeCount; i++) + { + int ptCount = r.ReadInt32(); + var pts = new StylusPointDto[ptCount]; + for (int j = 0; j < ptCount; j++) + pts[j] = new StylusPointDto { X = r.ReadSingle(), Y = r.ReadSingle(), Pressure = r.ReadSingle() }; + strokes[i] = new StrokeDto { Points = pts }; + } + return new RecognizeRequest { Strokes = strokes }; + } + } + + // 识别响应(辅助进程 → 主进程) + internal class RecognizeResponse + { + public bool Success; + public string ShapeName; // e.g. "Circle", "Rectangle", "Triangle" ... + public float CentroidX; + public float CentroidY; + public float ShapeWidth; + public float ShapeHeight; + public float[] HotPointsX; + public float[] HotPointsY; + public int[] StrokeIndices; // 参与识别的笔画在原始数组中的下标 + + public void WriteTo(BinaryWriter w) + { + w.Write(Success); + w.Write(ShapeName ?? string.Empty); + w.Write(CentroidX); + w.Write(CentroidY); + w.Write(ShapeWidth); + w.Write(ShapeHeight); + + int hotLen = HotPointsX != null ? HotPointsX.Length : 0; + w.Write(hotLen); + for (int i = 0; i < hotLen; i++) + { + w.Write(HotPointsX[i]); + w.Write(HotPointsY[i]); + } + + int idxLen = StrokeIndices != null ? StrokeIndices.Length : 0; + w.Write(idxLen); + for (int i = 0; i < idxLen; i++) + w.Write(StrokeIndices[i]); + } + + public static RecognizeResponse ReadFrom(BinaryReader r) + { + var resp = new RecognizeResponse + { + Success = r.ReadBoolean(), + ShapeName = r.ReadString(), + CentroidX = r.ReadSingle(), + CentroidY = r.ReadSingle(), + ShapeWidth = r.ReadSingle(), + ShapeHeight = r.ReadSingle() + }; + + int hotLen = r.ReadInt32(); + resp.HotPointsX = new float[hotLen]; + resp.HotPointsY = new float[hotLen]; + for (int i = 0; i < hotLen; i++) + { + resp.HotPointsX[i] = r.ReadSingle(); + resp.HotPointsY[i] = r.ReadSingle(); + } + + int idxLen = r.ReadInt32(); + resp.StrokeIndices = new int[idxLen]; + for (int i = 0; i < idxLen; i++) + resp.StrokeIndices[i] = r.ReadInt32(); + + return resp; + } + } +} \ No newline at end of file diff --git a/InkCanvasForClass.IACoreHelper/Program.cs b/InkCanvasForClass.IACoreHelper/Program.cs new file mode 100644 index 00000000..5b701235 --- /dev/null +++ b/InkCanvasForClass.IACoreHelper/Program.cs @@ -0,0 +1,222 @@ +using System; +using System.IO; +using System.IO.Pipes; +using System.Linq; +using System.Windows; +using System.Windows.Ink; +using System.Windows.Input; + +namespace InkCanvasForClass.IACoreHelper +{ + internal static class Program + { + private static readonly string LogPath = Path.Combine( + AppDomain.CurrentDomain.BaseDirectory, "IACoreHelper.log"); + + private static void Log(string msg) + { + try { File.AppendAllText(LogPath, $"{DateTime.Now:HH:mm:ss.fff} {msg}{Environment.NewLine}"); } + catch { } + } + + [STAThread] + static void Main(string[] args) + { + Log($"=== Helper started, args=[{string.Join(",", args)}], cwd={Environment.CurrentDirectory}, baseDir={AppDomain.CurrentDomain.BaseDirectory} ==="); + + if (args.Length < 1 || !int.TryParse(args[0], out int parentPid)) + { + Console.Error.WriteLine("Usage: IACoreHelper.exe "); + Log("Missing parentPid argument, exiting"); + return; + } + + string pipeName = string.Format(IpcConstants.PipeName, parentPid); + Log($"Pipe name: {pipeName}"); + + try + { + RunPipeServer(pipeName); + } + catch (Exception ex) + { + Log($"FATAL: {ex}"); + Console.Error.WriteLine("IACoreHelper fatal: " + ex.Message); + } + } + + private static void RunPipeServer(string pipeName) + { + while (true) + { + using (var server = new NamedPipeServerStream( + pipeName, + PipeDirection.InOut, + 1, + PipeTransmissionMode.Byte, + PipeOptions.WriteThrough)) + { + server.WaitForConnection(); + + try + { + using (var reader = new BinaryReader(server, System.Text.Encoding.UTF8, leaveOpen: true)) + using (var writer = new BinaryWriter(server, System.Text.Encoding.UTF8, leaveOpen: true)) + { + byte cmd = reader.ReadByte(); + + if (cmd == IpcConstants.CmdShutdown) + return; + + if (cmd == IpcConstants.CmdRecognize) + { + var request = RecognizeRequest.ReadFrom(reader); + var response = HandleRecognize(request); + response.WriteTo(writer); + writer.Flush(); + } + } + } + catch (Exception ex) + { + Console.Error.WriteLine("IACoreHelper pipe error: " + ex.Message); + } + } + // 每次连接后重新监听,支持多次调用 + } + } + + private static RecognizeResponse HandleRecognize(RecognizeRequest request) + { + try + { + Log($"HandleRecognize: strokes={request.Strokes?.Length ?? 0}"); + var strokes = BuildStrokeCollection(request); + Log($"Built StrokeCollection: count={strokes.Count}"); + if (strokes.Count == 0) + return new RecognizeResponse { Success = false, ShapeName = string.Empty }; + + var result = RecognizeCore(strokes); + Log($"RecognizeCore result: success={result.Success}, shape={result.ShapeName}"); + return result; + } + catch (Exception ex) + { + Log($"HandleRecognize EXCEPTION: {ex}"); + Console.Error.WriteLine("IACoreHelper recognize error: " + ex.Message); + return new RecognizeResponse { Success = false, ShapeName = string.Empty }; + } + } + + private static StrokeCollection BuildStrokeCollection(RecognizeRequest request) + { + var sc = new StrokeCollection(); + foreach (var strokeDto in request.Strokes) + { + if (strokeDto.Points == null || strokeDto.Points.Length == 0) + continue; + + var stylusPoints = new StylusPointCollection(); + foreach (var pt in strokeDto.Points) + stylusPoints.Add(new StylusPoint(pt.X, pt.Y, pt.Pressure)); + + sc.Add(new Stroke(stylusPoints)); + } + return sc; + } + + private static RecognizeResponse RecognizeCore(StrokeCollection strokes) + { + var analyzer = new InkAnalyzer(); + analyzer.AddStrokes(strokes); + analyzer.SetStrokesType(strokes, StrokeType.Drawing); + + AnalysisAlternate analysisAlternate = null; + int strokesCount = strokes.Count; + var analysisStatus = analyzer.Analyze(); + + if (analysisStatus.Successful) + { + var alternates = analyzer.GetAlternates(); + if (alternates.Count > 0) + { + while (strokesCount >= 2) + { + var alt0 = alternates[0]; + if (alt0?.AlternateNodes == null || alt0.AlternateNodes.Count == 0) + break; + var drawNode = alt0.AlternateNodes[0] as InkDrawingNode; + if (drawNode == null) + break; + bool shapeOk = IsContainShapeType(drawNode.GetShapeName()); + if (alt0.Strokes.Contains(strokes.Last()) && shapeOk) + break; + analyzer.RemoveStroke(strokes[strokes.Count - strokesCount]); + strokesCount--; + analysisStatus = analyzer.Analyze(); + if (analysisStatus.Successful) + alternates = analyzer.GetAlternates(); + else + break; + if (alternates.Count == 0) + break; + } + + if (alternates.Count > 0) + { + var altFinal = alternates[0]; + if (altFinal?.AlternateNodes != null && altFinal.AlternateNodes.Count > 0) + analysisAlternate = altFinal; + } + } + } + + analyzer.Dispose(); + + if (analysisAlternate?.AlternateNodes == null || analysisAlternate.AlternateNodes.Count == 0) + return new RecognizeResponse { Success = false, ShapeName = string.Empty }; + + var node = analysisAlternate.AlternateNodes[0] as InkDrawingNode; + if (node == null) + return new RecognizeResponse { Success = false, ShapeName = string.Empty }; + + var shape = node.GetShape(); + var center = node.Centroid; + var hot = node.HotPoints; + + float[] hotX = new float[hot?.Count ?? 0]; + float[] hotY = new float[hot?.Count ?? 0]; + if (hot != null) + for (int i = 0; i < hot.Count; i++) { hotX[i] = (float)hot[i].X; hotY[i] = (float)hot[i].Y; } + + // 计算参与识别的笔画在原始集合中的下标 + var participatingStrokes = analysisAlternate.Strokes; + int[] strokeIndices = new int[participatingStrokes?.Count ?? 0]; + if (participatingStrokes != null) + for (int i = 0; i < participatingStrokes.Count; i++) + strokeIndices[i] = strokes.IndexOf(participatingStrokes[i]); + + return new RecognizeResponse + { + Success = true, + ShapeName = node.GetShapeName() ?? string.Empty, + CentroidX = (float)center.X, + CentroidY = (float)center.Y, + ShapeWidth = shape != null ? (float)shape.Width : 0f, + ShapeHeight = shape != null ? (float)shape.Height : 0f, + HotPointsX = hotX, + HotPointsY = hotY, + StrokeIndices = strokeIndices + }; + } + + private static bool IsContainShapeType(string name) + { + if (string.IsNullOrEmpty(name)) return false; + return name.Contains("Triangle") || name.Contains("Circle") || + name.Contains("Rectangle") || name.Contains("Diamond") || + name.Contains("Parallelogram") || name.Contains("Square") || + name.Contains("Ellipse"); + } + } +} \ No newline at end of file diff --git a/InkCanvasForClass.IACoreHelper/Properties/AssemblyInfo.cs b/InkCanvasForClass.IACoreHelper/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..51bf9aa3 --- /dev/null +++ b/InkCanvasForClass.IACoreHelper/Properties/AssemblyInfo.cs @@ -0,0 +1,11 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("InkCanvasForClass.IACoreHelper")] +[assembly: AssemblyDescription("IACore 32-bit ink shape recognition helper process")] +[assembly: AssemblyCompany("ICC CE")] +[assembly: AssemblyProduct("InkCanvasForClass.IACoreHelper")] +[assembly: AssemblyCopyright("Copyright © ICC CE")] +[assembly: ComVisible(false)] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file