add:基于IPC的IACore在net6的实现

This commit is contained in:
2026-05-01 23:54:24 +08:00
parent e801394dbe
commit 2a17ea1bd1
11 changed files with 806 additions and 247 deletions
+22
View File
@@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InkCanvas.PluginSdk", "InkC
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InkCanvas.Controls", "InkCanvas.Controls\InkCanvas.Controls.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InkCanvas.Controls", "InkCanvas.Controls\InkCanvas.Controls.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InkCanvasForClass.IACoreHelper", "InkCanvasForClass.IACoreHelper\InkCanvasForClass.IACoreHelper.csproj", "{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU 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|x64.Build.0 = Release|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x86.ActiveCfg = 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 {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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
+18
View File
@@ -1202,6 +1202,17 @@ namespace Ink_Canvas
LogHelper.WriteLogToFile($"释放IACore DLL时出错: {ex.Message}", LogHelper.LogType.Error); 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 try
{ {
LogHelper.WriteLogToFile("开始注册.icstk文件关联"); LogHelper.WriteLogToFile("开始注册.icstk文件关联");
@@ -1577,6 +1588,13 @@ namespace Ink_Canvas
private void App_Exit(object sender, ExitEventArgs e) private void App_Exit(object sender, ExitEventArgs e)
{ {
CleanupTerminationMonitoring(); CleanupTerminationMonitoring();
try
{
IpcIACoreClient.Instance.Dispose();
}
catch { }
// 卸载所有插件 // 卸载所有插件
try try
{ {
+9 -5
View File
@@ -96,8 +96,10 @@ namespace Ink_Canvas.Helpers
return RecognizeShapeWinRtOnDispatcherContext(strokes); return RecognizeShapeWinRtOnDispatcherContext(strokes);
} }
var legacy = InkRecognizeHelper.RecognizeShapeIACore(strokes); // IACore 必须走 IPC 辅助进程(x86/.NET 4.7.2)。
return Task.FromResult(InkRecognizeHelper.FromIACoreOrEmpty(legacy)); // 在 .NET 6 x64 主进程中本地加载 IAWinFX 会失败,故不再本地回退。
var ipcResult = IpcIACoreClient.Instance.Recognize(strokes);
return Task.FromResult(ipcResult);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -214,9 +216,11 @@ namespace Ink_Canvas.Helpers
public string GetSystemInfo() public string GetSystemInfo()
{ {
return _isModernSystemAvailable if (_isModernSystemAvailable)
? $"现代化墨迹识别系统 (Windows Runtime API) - 进程架构: {Environment.Is64BitProcess}" return $"现代化墨迹识别系统 (Windows Runtime API) - 进程架构: {Environment.Is64BitProcess}";
: $"传统墨迹识别系统 (IACore) - 进程架构: {Environment.Is64BitProcess}"; if (IpcIACoreClient.Instance.IsAvailable)
return $"传统墨迹识别系统 (IACore via IPC) - 进程架构: {Environment.Is64BitProcess}";
return $"传统墨迹识别系统 (IACore 本地) - 进程架构: {Environment.Is64BitProcess}";
} }
public void Dispose() public void Dispose()
+16 -228
View File
@@ -1,79 +1,15 @@
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows;
using System.Windows.Ink; using System.Windows.Ink;
using System.Windows.Media;
namespace Ink_Canvas.Helpers namespace Ink_Canvas.Helpers
{ {
/// <summary>
/// 墨迹形状/手写识别的对外门面。
/// IACore 路径通过 IPC 调用 x86 辅助进程;WinRT 路径在主进程内直接调用。
/// 主进程 (.NET 6 x64) 不再直接引用 IAWinFX 类型。
/// </summary>
public class InkRecognizeHelper public class InkRecognizeHelper
{ {
/// <summary>IACore / IAWinFX 形状识别(典型用于 32 位进程)。</summary>
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;
}
/// <summary>兼容旧调用:等价于 <see cref="RecognizeShapeIACore"/>。</summary>
public static ShapeRecognizeResult RecognizeShape(StrokeCollection strokes) =>
RecognizeShapeIACore(strokes);
/// <summary>按设置选择 WinRT<see cref="InkRecognitionManager"/>)或 IACoreWinRT 请用 <see cref="RecognizeShapeUnifiedAsync"/>。</summary>
public static InkShapeRecognitionResult RecognizeShapeUnified( public static InkShapeRecognitionResult RecognizeShapeUnified(
StrokeCollection strokes, StrokeCollection strokes,
ShapeRecognitionEngineMode mode) ShapeRecognitionEngineMode mode)
@@ -84,11 +20,9 @@ namespace Ink_Canvas.Helpers
if (ShapeRecognitionRouter.ResolveUseWinRt(mode)) if (ShapeRecognitionRouter.ResolveUseWinRt(mode))
return InkShapeRecognitionResult.Empty; return InkShapeRecognitionResult.Empty;
var legacy = RecognizeShapeIACore(strokes); return IpcIACoreClient.Instance.Recognize(strokes);
return FromIACoreOrEmpty(legacy);
} }
/// <summary>与 CE 反编译版 <c>InkRecognitionManager.RecognizeShapeAsync</c> 对齐的统一入口。</summary>
public static Task<InkShapeRecognitionResult> RecognizeShapeUnifiedAsync( public static Task<InkShapeRecognitionResult> RecognizeShapeUnifiedAsync(
StrokeCollection strokes, StrokeCollection strokes,
ShapeRecognitionEngineMode mode) ShapeRecognitionEngineMode mode)
@@ -109,7 +43,9 @@ namespace Ink_Canvas.Helpers
WinRtHandwritingRecognizer.Warmup(); WinRtHandwritingRecognizer.Warmup();
} }
else else
RecognizeShapeIACore(new StrokeCollection()); {
IpcIACoreClient.Instance.Start();
}
} }
catch catch
{ {
@@ -117,13 +53,11 @@ namespace Ink_Canvas.Helpers
} }
} }
/// <summary>WinRT 手写识别(Windows 10+)。</summary>
public static Task<HandwritingRecognitionResult> RecognizeHandwritingUnifiedAsync( public static Task<HandwritingRecognitionResult> RecognizeHandwritingUnifiedAsync(
StrokeCollection strokes, StrokeCollection strokes,
ShapeRecognitionEngineMode mode) => ShapeRecognitionEngineMode mode) =>
InkRecognitionManager.Instance.RecognizeHandwritingAsync(strokes, mode); InkRecognitionManager.Instance.RecognizeHandwritingAsync(strokes, mode);
/// <summary>WinRT 下将识别成功的词替换为手写体字形墨迹;是否应用由设置「WinRT 识别转手写体字形」控制。</summary>
public static Task<StrokeCollection> CorrectHandwritingStrokesUnifiedAsync( public static Task<StrokeCollection> CorrectHandwritingStrokesUnifiedAsync(
StrokeCollection strokes, StrokeCollection strokes,
ShapeRecognitionEngineMode mode) => ShapeRecognitionEngineMode mode) =>
@@ -133,7 +67,6 @@ namespace Ink_Canvas.Helpers
MainWindow.Settings?.InkToShape?.EnableWinRtHandwritingStrokeBeautify ?? false, MainWindow.Settings?.InkToShape?.EnableWinRtHandwritingStrokeBeautify ?? false,
MainWindow.Settings?.InkToShape?.HandwritingCorrectionFontFamily); MainWindow.Settings?.InkToShape?.HandwritingCorrectionFontFamily);
/// <summary>显式指定是否应用手写体字形替换(忽略开关);字体仍从设置读取。</summary>
public static Task<StrokeCollection> CorrectHandwritingStrokesUnifiedAsync( public static Task<StrokeCollection> CorrectHandwritingStrokesUnifiedAsync(
StrokeCollection strokes, StrokeCollection strokes,
ShapeRecognitionEngineMode mode, ShapeRecognitionEngineMode mode,
@@ -144,53 +77,18 @@ namespace Ink_Canvas.Helpers
applyHandwritingBeautify, applyHandwritingBeautify,
MainWindow.Settings?.InkToShape?.HandwritingCorrectionFontFamily); 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) public static bool IsContainShapeType(string name)
{ {
if (string.IsNullOrEmpty(name)) if (string.IsNullOrEmpty(name))
return false; return false;
if (name.Contains("Triangle") || name.Contains("Circle") || return name.Contains("Triangle") || name.Contains("Circle") ||
name.Contains("Rectangle") || name.Contains("Diamond") || name.Contains("Rectangle") || name.Contains("Diamond") ||
name.Contains("Parallelogram") || name.Contains("Square") name.Contains("Parallelogram") || name.Contains("Square") ||
|| name.Contains("Ellipse")) name.Contains("Ellipse");
{
return true;
}
return false;
} }
} }
//Recognizer 的实现
public enum RecognizeLanguage public enum RecognizeLanguage
{ {
SimplifiedChinese = 0x0804, SimplifiedChinese = 0x0804,
@@ -198,127 +96,17 @@ namespace Ink_Canvas.Helpers
English = 0x0809 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; }
}
/// <summary>
/// 图形识别类
/// </summary>
//public class ShapeRecogniser
//{
// public InkAnalyzer _inkAnalyzer = null;
// private ShapeRecogniser()
// {
// this._inkAnalyzer = new InkAnalyzer
// {
// AnalysisModes = AnalysisModes.AutomaticReconciliationEnabled
// };
// }
// /// <summary>
// /// 根据笔迹集合返回图形名称字符串
// /// </summary>
// /// <param name="strokeCollection"></param>
// /// <returns></returns>
// 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;
// }
// /// <summary>
// /// 实现笔迹的分析,返回图形对应的字符串
// /// 你在实际的应用中根据返回的字符串来生成对应的Shape
// /// </summary>
// /// <param name="ink"></param>
// /// <returns></returns>
// 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 class Circle
{ {
public Circle(Point centroid, double r, Stroke stroke) public Circle(System.Windows.Point centroid, double r, Stroke stroke)
{ {
Centroid = centroid; Centroid = centroid;
R = r; R = r;
Stroke = stroke; Stroke = stroke;
} }
public Point Centroid { get; set; } public System.Windows.Point Centroid { get; set; }
public double R { get; set; } public double R { get; set; }
public Stroke Stroke { get; set; } public Stroke Stroke { get; set; }
} }
} }
+302
View File
@@ -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
{
/// <summary>
/// 通过 Named Pipe IPC 调用 32 位辅助进程 InkCanvasForClass.IACoreHelper.exe 进行 IACore 形状识别。
/// 单例,线程安全,辅助进程崩溃后自动重启。
/// </summary>
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 ──────────────────────────────────────────────────────────
/// <summary>启动辅助进程,返回是否成功。</summary>
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();
}
/// <summary>是否就绪(辅助进程运行中)。</summary>
public bool IsAvailable => _available && _helperProcess != null && !_helperProcess.HasExited;
/// <summary>
/// 发送笔画到辅助进程进行 IACore 形状识别。
/// 超时或失败时返回 <see cref="InkShapeRecognitionResult.Empty"/>。
/// </summary>
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;
}
}
+11 -14
View File
@@ -101,20 +101,6 @@
<PlatformTarget>x64</PlatformTarget> <PlatformTarget>x64</PlatformTarget>
<Prefer32Bit>false</Prefer32Bit> <Prefer32Bit>false</Prefer32Bit>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<Reference Include="IACore">
<HintPath>.\IACore.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="IALoader">
<HintPath>.\IALoader.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="IAWinFX">
<HintPath>.\IAWinFX.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="app.manifest" /> <None Include="app.manifest" />
</ItemGroup> </ItemGroup>
@@ -153,6 +139,10 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\InkCanvas.PluginSdk\InkCanvas.PluginSdk.csproj" /> <ProjectReference Include="..\InkCanvas.PluginSdk\InkCanvas.PluginSdk.csproj" />
<ProjectReference Include="..\InkCanvas.Controls\InkCanvas.Controls.csproj" /> <ProjectReference Include="..\InkCanvas.Controls\InkCanvas.Controls.csproj" />
<ProjectReference Include="..\InkCanvasForClass.IACoreHelper\InkCanvasForClass.IACoreHelper.csproj">
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
<Private>false</Private>
</ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(MSBuildRuntimeType)' == 'Full'"> <ItemGroup Condition="'$(MSBuildRuntimeType)' == 'Full'">
<COMReference Include="IWshRuntimeLibrary"> <COMReference Include="IWshRuntimeLibrary">
@@ -668,4 +658,11 @@
<Target Name="CleanTelemetryDsn" AfterTargets="Build;Clean" Condition="Exists('$(MSBuildProjectDirectory)\telemetry_dsn.generated.txt')"> <Target Name="CleanTelemetryDsn" AfterTargets="Build;Clean" Condition="Exists('$(MSBuildProjectDirectory)\telemetry_dsn.generated.txt')">
<Delete Files="$(MSBuildProjectDirectory)\telemetry_dsn.generated.txt" /> <Delete Files="$(MSBuildProjectDirectory)\telemetry_dsn.generated.txt" />
</Target> </Target>
<Target Name="CopyIACoreHelper" AfterTargets="Build">
<ItemGroup>
<IACoreHelperFiles Include="$(MSBuildProjectDirectory)\..\InkCanvasForClass.IACoreHelper\bin\$(Configuration)\*.*" />
</ItemGroup>
<Copy SourceFiles="@(IACoreHelperFiles)" DestinationFolder="$(OutDir)" SkipUnchangedFiles="true" Condition="'@(IACoreHelperFiles)' != ''" />
</Target>
</Project> </Project>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup useLegacyV2RuntimeActivationPolicy="true">
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
</configuration>
@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
<ProjectGuid>{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>InkCanvasForClass.IACoreHelper</RootNamespace>
<AssemblyName>InkCanvasForClass.IACoreHelper</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<PlatformTarget>x86</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<PlatformTarget>x86</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<Reference Include="WindowsBase" />
<Reference Include="System.Xml" />
<Reference Include="IAWinFX">
<HintPath>..\Ink Canvas\Resources\IACore\IAWinFX.dll</HintPath>
<Private>false</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="IpcProtocol.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
@@ -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;
}
}
}
+222
View File
@@ -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 <parentPid>");
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");
}
}
}
@@ -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")]