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