From b8262901a0869f8531db739241ac0e81721f89c4 Mon Sep 17 00:00:00 2001 From: StarInk Date: Sun, 9 Feb 2025 08:23:14 +0200 Subject: [PATCH 01/17] =?UTF-8?q?=F0=9F=92=BE=20Feat:=20Workflow=20UI=20Ba?= =?UTF-8?q?se?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Workflow/WorkflowInfo.cs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 KitX Shared/KitX.Shared.CSharp/Workflow/WorkflowInfo.cs diff --git a/KitX Shared/KitX.Shared.CSharp/Workflow/WorkflowInfo.cs b/KitX Shared/KitX.Shared.CSharp/Workflow/WorkflowInfo.cs new file mode 100644 index 0000000..ac3ee1e --- /dev/null +++ b/KitX Shared/KitX.Shared.CSharp/Workflow/WorkflowInfo.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; + +namespace KitX.Shared.CSharp.Workflow; + +public class WorkflowInfo +{ + public string Name { get; set; } = string.Empty; + + public string Version { get; set; } = string.Empty; + + public Dictionary DisplayName { get; set; } = []; + + public string AuthorName { get; set; } = string.Empty; + + public string AuthorLink { get; set; } = string.Empty; + + public string PublisherName { get; set; } = string.Empty; + + public string PublisherLink { get; set; } = string.Empty; + + public Dictionary SimpleDescription { get; set; } = []; + + public Dictionary ComplexDescription { get; set; } = []; + + public Dictionary TotalDescriptionInMarkdown { get; set; } = []; + + public string IconInBase64 { get; set; } = string.Empty; + + public DateTime PublishDate { get; set; } + + public DateTime LastUpdateDate { get; set; } + + public bool IsMarketVersion { get; set; } + + public string OriginalFilePath { get; set; } = string.Empty; +} From 81b32a336db67616877370e56f1c5b166d8fb39d Mon Sep 17 00:00:00 2001 From: StarInk Date: Sun, 9 Feb 2025 23:51:43 +0200 Subject: [PATCH 02/17] =?UTF-8?q?=F0=9F=92=BE=20Feat(Kscript.CSharp):=20?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=8E=A5=E5=8F=A3=E5=92=8C=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E7=B1=BB=E4=BB=A5=E6=94=AF=E6=8C=81=E8=AE=BE=E5=A4=87=E5=92=8C?= =?UTF-8?q?=E6=8F=92=E4=BB=B6=E5=8A=9F=E8=83=BD-=E5=88=9D=E5=A7=8B?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Kscript.CSharp/Interfaces/IComposer.cs | 15 +++++ .../Kscript.CSharp/Interfaces/IDevice.cs | 15 +++++ .../Kscript.CSharp/Interfaces/IFunction.cs | 13 ++++ .../Kscript.CSharp/Interfaces/IPlugin.cs | 16 +++++ .../Kscript.CSharp/Kscript.CSharp.csproj | 24 +++++++ .../Kscript.CSharp/Services/Composer.cs | 67 +++++++++++++++++++ KitX Script/Kscript.CSharp/Services/Device.cs | 56 ++++++++++++++++ .../Kscript.CSharp/Services/Function.cs | 60 +++++++++++++++++ .../Services/NetworkConnector.cs | 67 +++++++++++++++++++ KitX Script/Kscript.CSharp/Services/Plugin.cs | 62 +++++++++++++++++ 10 files changed, 395 insertions(+) create mode 100644 KitX Script/Kscript.CSharp/Interfaces/IComposer.cs create mode 100644 KitX Script/Kscript.CSharp/Interfaces/IDevice.cs create mode 100644 KitX Script/Kscript.CSharp/Interfaces/IFunction.cs create mode 100644 KitX Script/Kscript.CSharp/Interfaces/IPlugin.cs create mode 100644 KitX Script/Kscript.CSharp/Kscript.CSharp.csproj create mode 100644 KitX Script/Kscript.CSharp/Services/Composer.cs create mode 100644 KitX Script/Kscript.CSharp/Services/Device.cs create mode 100644 KitX Script/Kscript.CSharp/Services/Function.cs create mode 100644 KitX Script/Kscript.CSharp/Services/NetworkConnector.cs create mode 100644 KitX Script/Kscript.CSharp/Services/Plugin.cs diff --git a/KitX Script/Kscript.CSharp/Interfaces/IComposer.cs b/KitX Script/Kscript.CSharp/Interfaces/IComposer.cs new file mode 100644 index 0000000..0216730 --- /dev/null +++ b/KitX Script/Kscript.CSharp/Interfaces/IComposer.cs @@ -0,0 +1,15 @@ +using KitX.Shared.CSharp.Device; + +namespace Kscript.CSharp.Interfaces +{ + public interface IComposer + { + Task RequestLocalDevice(); + Task RequestMainController(); + Task RequestRandomDesktopDevice(); + Task RequestRandomMobileDevice(); + Task RequestDeviceByFilter(Func filter); + Task RequestUserSelectedDevice(IEnumerable candidates); + Task> RequestDeviceList(); + } +} diff --git a/KitX Script/Kscript.CSharp/Interfaces/IDevice.cs b/KitX Script/Kscript.CSharp/Interfaces/IDevice.cs new file mode 100644 index 0000000..a430d6c --- /dev/null +++ b/KitX Script/Kscript.CSharp/Interfaces/IDevice.cs @@ -0,0 +1,15 @@ +using KitX.Shared.CSharp.Device; +using KitX.Shared.CSharp.Plugin; + +namespace Kscript.CSharp.Interfaces +{ + public interface IDevice + { + DeviceInfo Info { get; } + Task RequestPlugin(string idOrName); + Task> RequestPluginsByType(string type); + Task> GetPluginList(); + bool HasPlugin(string idOrName); + Task CreatePluginInstance(PluginInfo info); + } +} diff --git a/KitX Script/Kscript.CSharp/Interfaces/IFunction.cs b/KitX Script/Kscript.CSharp/Interfaces/IFunction.cs new file mode 100644 index 0000000..48026af --- /dev/null +++ b/KitX Script/Kscript.CSharp/Interfaces/IFunction.cs @@ -0,0 +1,13 @@ +using KitX.Shared.CSharp.Plugin; + +namespace Kscript.CSharp.Interfaces +{ + public interface IFunction + { + Function Info { get; } + PluginInfo AssociatedPlugin { get; } + Task Invoke(params object[] parameters); + bool ValidateParameters(object[] parameters); + Type GetReturnType(); + } +} diff --git a/KitX Script/Kscript.CSharp/Interfaces/IPlugin.cs b/KitX Script/Kscript.CSharp/Interfaces/IPlugin.cs new file mode 100644 index 0000000..d66fd94 --- /dev/null +++ b/KitX Script/Kscript.CSharp/Interfaces/IPlugin.cs @@ -0,0 +1,16 @@ +using KitX.Shared.CSharp.Device; +using KitX.Shared.CSharp.Plugin; + +namespace Kscript.CSharp.Interfaces +{ + public interface IPlugin + { + PluginInfo Info { get; } + DeviceInfo AssociatedDevice { get; } + Task RequestFunction(string idOrName); + Task> GetFunctionList(); + Task GetFunctionByType(string type); + Task ExecuteFunction(string functionName, params object[] parameters); + bool HasFunction(string idOrName); + } +} diff --git a/KitX Script/Kscript.CSharp/Kscript.CSharp.csproj b/KitX Script/Kscript.CSharp/Kscript.CSharp.csproj new file mode 100644 index 0000000..a2d8d7c --- /dev/null +++ b/KitX Script/Kscript.CSharp/Kscript.CSharp.csproj @@ -0,0 +1,24 @@ + + + + net8.0 + enable + enable + True + + + + $(Version) + $(Version) + 24.10.$([System.DateTime]::UtcNow.Date.Subtract($([System.DateTime]::Parse("2024-02-07"))).TotalDays).$([System.Math]::Floor($([System.DateTime]::UtcNow.TimeOfDay.TotalMinutes))) + + + + Kscript.CSharp + StarDustSeemsInk,Dynesshely + Crequency + KitX Script CSharp + AGPL-3.0-only + True + + diff --git a/KitX Script/Kscript.CSharp/Services/Composer.cs b/KitX Script/Kscript.CSharp/Services/Composer.cs new file mode 100644 index 0000000..d37475d --- /dev/null +++ b/KitX Script/Kscript.CSharp/Services/Composer.cs @@ -0,0 +1,67 @@ +using KitX.Shared.CSharp.Device; +using Kscript.CSharp.Interfaces; + +namespace Kscript.CSharp.Services +{ + public class Composer : IComposer + { + private readonly NetworkConnector _connector = NetworkConnector.Instance; + private IEnumerable _cachedDeviceList; + + public async Task RequestLocalDevice() + { + var deviceInfo = await _connector.GetLocalDeviceInfo(); + return new Device(deviceInfo); + } + + public async Task RequestMainController() + { + var devices = await RequestDeviceList(); + var mainController = devices.FirstOrDefault(x => x.IsMainController); + return mainController != null ? new Device(mainController) : null; + } + + public async Task RequestRandomDesktopDevice() + { + var devices = await RequestDeviceList(); + var desktopDevices = devices.Where(x => x.IsDesktopDevice).ToList(); + if (!desktopDevices.Any()) return null; + + var random = new Random(); + var randomDevice = desktopDevices[random.Next(desktopDevices.Count)]; + return new Device(randomDevice); + } + + public async Task RequestRandomMobileDevice() + { + var devices = await RequestDeviceList(); + var mobileDevices = devices.Where(x => x.IsMobileDevice).ToList(); + if (!mobileDevices.Any()) return null; + + var random = new Random(); + var randomDevice = mobileDevices[random.Next(mobileDevices.Count)]; + return new Device(randomDevice); + } + + public async Task RequestDeviceByFilter(Func filter) + { + var devices = await RequestDeviceList(); + var matchedDevice = devices.FirstOrDefault(filter); + return matchedDevice != null ? new Device(matchedDevice) : null; + } + + public async Task RequestUserSelectedDevice(IEnumerable candidates) + { + // 这里需要调用本地UI让用户选择设备 + // 简化实现,返回第一个设备 + var device = candidates.FirstOrDefault(); + return device != null ? new Device(device) : null; + } + + public async Task> RequestDeviceList() + { + _cachedDeviceList = await _connector.GetDeviceList(); + return _cachedDeviceList; + } + } +} diff --git a/KitX Script/Kscript.CSharp/Services/Device.cs b/KitX Script/Kscript.CSharp/Services/Device.cs new file mode 100644 index 0000000..1a7f7d6 --- /dev/null +++ b/KitX Script/Kscript.CSharp/Services/Device.cs @@ -0,0 +1,56 @@ +using KitX.Shared.CSharp.Device; +using KitX.Shared.CSharp.Plugin; +using Kscript.CSharp.Interfaces; + +namespace Kscript.CSharp.Services +{ + public class Device : IDevice + { + private readonly NetworkConnector _connector = NetworkConnector.Instance; + private Dictionary _pluginCache = new(); + + public DeviceInfo Info { get; } + + public Device(DeviceInfo info) + { + Info = info; + } + + public async Task RequestPlugin(string idOrName) + { + var plugins = await GetPluginList(); + var pluginInfo = plugins.FirstOrDefault(p => + p.Id.Equals(idOrName, StringComparison.OrdinalIgnoreCase) || + p.Name.Equals(idOrName, StringComparison.OrdinalIgnoreCase)); + + return pluginInfo != null ? await CreatePluginInstance(pluginInfo) : null; + } + + public async Task> RequestPluginsByType(string type) + { + var plugins = await GetPluginList(); + var matchedPlugins = plugins.Where(p => p.Type.Equals(type, StringComparison.OrdinalIgnoreCase)); + var tasks = matchedPlugins.Select(CreatePluginInstance); + return await Task.WhenAll(tasks); + } + + public async Task> GetPluginList() + { + var plugins = await _connector.GetPluginList(Info.Id); + _pluginCache = plugins.ToDictionary(p => p.Id); + return plugins; + } + + public bool HasPlugin(string idOrName) + { + return _pluginCache.Values.Any(p => + p.Id.Equals(idOrName, StringComparison.OrdinalIgnoreCase) || + p.Name.Equals(idOrName, StringComparison.OrdinalIgnoreCase)); + } + + public async Task CreatePluginInstance(PluginInfo info) + { + return new Plugin(info, Info, _connector); + } + } +} diff --git a/KitX Script/Kscript.CSharp/Services/Function.cs b/KitX Script/Kscript.CSharp/Services/Function.cs new file mode 100644 index 0000000..315f59c --- /dev/null +++ b/KitX Script/Kscript.CSharp/Services/Function.cs @@ -0,0 +1,60 @@ +using KitX.Shared.CSharp.Device; +using KitX.Shared.CSharp.Plugin; +using Kscript.CSharp.Interfaces; + +namespace Kscript.CSharp.Services +{ + public class Function : IFunction + { + private readonly NetworkConnector _connector; + private readonly DeviceInfo _deviceInfo; + + public Function Info { get; } + public PluginInfo AssociatedPlugin { get; } + + public Function(KitX.Shared.CSharp.Plugin.Function info, PluginInfo pluginInfo, + DeviceInfo deviceInfo, NetworkConnector connector) + { + Info = info; + AssociatedPlugin = pluginInfo; + _deviceInfo = deviceInfo; + _connector = connector; + } + + public async Task Invoke(params object[] parameters) + { + if (!ValidateParameters(parameters)) + { + throw new ArgumentException("Invalid parameters"); + } + + return await _connector.ExecuteFunction( + _deviceInfo.Id, + AssociatedPlugin.Id, + Info.Name, + parameters + ); + } + + public bool ValidateParameters(object[] parameters) + { + if (parameters.Length != Info.Parameters.Length) + return false; + + for (int i = 0; i < parameters.Length; i++) + { + if (!Info.Parameters[i].ParameterType.IsInstanceOfType(parameters[i])) + { + return false; + } + } + + return true; + } + + public Type GetReturnType() + { + return Type.GetType(Info.ReturnType) ?? typeof(object); + } + } +} diff --git a/KitX Script/Kscript.CSharp/Services/NetworkConnector.cs b/KitX Script/Kscript.CSharp/Services/NetworkConnector.cs new file mode 100644 index 0000000..2c43911 --- /dev/null +++ b/KitX Script/Kscript.CSharp/Services/NetworkConnector.cs @@ -0,0 +1,67 @@ +using KitX.Shared.CSharp.WebCommand; +using KitX.Shared.CSharp.Device; +using KitX.Shared.CSharp.Plugin; + +namespace Kscript.CSharp.Services +{ + internal class NetworkConnector + { + private static readonly NetworkConnector _instance = new(); + private readonly Dictionary _deviceCache = new(); + private readonly Connector _connector = new(); + + public static NetworkConnector Instance => _instance; + + private NetworkConnector() + { + // 初始化连接器 + _connector.Connect(); + } + + public async Task GetLocalDeviceInfo() + { + var request = new RequestBuilder() + .SetCommandType(CommandType.GetLocalDeviceInfo) + .Build(); + var response = await _connector.SendRequest(request); + return response.As(); + } + + public async Task> GetDeviceList() + { + var request = new RequestBuilder() + .SetCommandType(CommandType.GetDeviceList) + .Build(); + var response = await _connector.SendRequest(request); + var devices = response.As>(); + foreach (var device in devices) + { + _deviceCache[device.Id] = device; + } + return devices; + } + + public async Task> GetPluginList(string deviceId) + { + var request = new RequestBuilder() + .SetCommandType(CommandType.GetPluginList) + .SetTarget(deviceId) + .Build(); + var response = await _connector.SendRequest(request); + return response.As>(); + } + + public async Task ExecuteFunction(string deviceId, string pluginId, string functionName, object[] parameters) + { + var request = new RequestBuilder() + .SetCommandType(CommandType.ExecuteFunction) + .SetTarget(deviceId) + .AddParameter("pluginId", pluginId) + .AddParameter("functionName", functionName) + .AddParameter("parameters", parameters) + .Build(); + var response = await _connector.SendRequest(request); + return response.Result; + } + } +} diff --git a/KitX Script/Kscript.CSharp/Services/Plugin.cs b/KitX Script/Kscript.CSharp/Services/Plugin.cs new file mode 100644 index 0000000..6ca7896 --- /dev/null +++ b/KitX Script/Kscript.CSharp/Services/Plugin.cs @@ -0,0 +1,62 @@ +using KitX.Shared.CSharp.Device; +using KitX.Shared.CSharp.Plugin; +using Kscript.CSharp.Interfaces; + +namespace Kscript.CSharp.Services +{ + public class Plugin : IPlugin + { + private readonly NetworkConnector _connector; + private readonly Dictionary _functionCache = new(); + + public PluginInfo Info { get; } + public DeviceInfo AssociatedDevice { get; } + + public Plugin(PluginInfo info, DeviceInfo deviceInfo, NetworkConnector connector) + { + Info = info; + AssociatedDevice = deviceInfo; + _connector = connector; + } + + public async Task RequestFunction(string idOrName) + { + var functions = await GetFunctionList(); + var function = functions.FirstOrDefault(f => + f.Info.Id.Equals(idOrName, StringComparison.OrdinalIgnoreCase) || + f.Info.Name.Equals(idOrName, StringComparison.OrdinalIgnoreCase)); + return function; + } + + public async Task> GetFunctionList() + { + var functions = Info.Functions.Select(f => + new Function(f, Info, AssociatedDevice, _connector) as IFunction); + return await Task.FromResult(functions); + } + + public async Task GetFunctionByType(string type) + { + var functions = await GetFunctionList(); + return functions.FirstOrDefault(f => + f.Info.ReturnType.Equals(type, StringComparison.OrdinalIgnoreCase)); + } + + public async Task ExecuteFunction(string functionName, params object[] parameters) + { + return await _connector.ExecuteFunction( + AssociatedDevice.Id, + Info.Id, + functionName, + parameters + ); + } + + public bool HasFunction(string idOrName) + { + return Info.Functions.Any(f => + f.Id.Equals(idOrName, StringComparison.OrdinalIgnoreCase) || + f.Name.Equals(idOrName, StringComparison.OrdinalIgnoreCase)); + } + } +} From c5e413e8829dcac9421b51fce4c2792584613cd5 Mon Sep 17 00:00:00 2001 From: StarInk Date: Mon, 10 Feb 2025 01:41:14 +0200 Subject: [PATCH 03/17] =?UTF-8?q?=F0=9F=92=BE=20=F0=9F=90=9BFeat,=20Bug(Ks?= =?UTF-8?q?cript.CSharp):=20=E6=9B=B4=E6=96=B0=E6=8E=A5=E5=8F=A3=E4=BB=A5?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E6=96=B0=E5=8A=9F=E8=83=BD=EF=BC=8C=E7=A7=BB?= =?UTF-8?q?=E9=99=A4=E4=B8=8D=E5=BF=85=E8=A6=81=E7=9A=84=E7=BD=91=E7=BB=9C?= =?UTF-8?q?=E8=BF=9E=E6=8E=A5=E5=99=A8=EF=BC=8C=E6=9C=AA=E6=89=BE=E5=88=B0?= =?UTF-8?q?=E5=90=88=E9=80=82=E7=9A=84=E8=8E=B7=E5=8F=96=E7=BD=91=E7=BB=9C?= =?UTF-8?q?=E5=B9=BF=E6=92=AD=E4=BF=A1=E6=81=AF=E7=9A=84=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Kscript.CSharp/Interfaces/IComposer.cs | 12 ++-- .../Kscript.CSharp/Interfaces/IDevice.cs | 3 +- .../Kscript.CSharp/Interfaces/IFunction.cs | 5 +- .../Kscript.CSharp/Interfaces/IPlugin.cs | 4 +- .../Kscript.CSharp/Kscript.CSharp.csproj | 4 ++ .../Kscript.CSharp/Services/Composer.cs | 52 +++++++------- KitX Script/Kscript.CSharp/Services/Device.cs | 24 ++----- .../Kscript.CSharp/Services/Function.cs | 54 ++++++--------- .../Services/NetworkConnector.cs | 67 ------------------- KitX Script/Kscript.CSharp/Services/Plugin.cs | 37 ++++++---- 10 files changed, 92 insertions(+), 170 deletions(-) delete mode 100644 KitX Script/Kscript.CSharp/Services/NetworkConnector.cs diff --git a/KitX Script/Kscript.CSharp/Interfaces/IComposer.cs b/KitX Script/Kscript.CSharp/Interfaces/IComposer.cs index 0216730..c7c11ef 100644 --- a/KitX Script/Kscript.CSharp/Interfaces/IComposer.cs +++ b/KitX Script/Kscript.CSharp/Interfaces/IComposer.cs @@ -1,15 +1,15 @@ -using KitX.Shared.CSharp.Device; +using KitX.Shared.CSharp.Device; namespace Kscript.CSharp.Interfaces { public interface IComposer { Task RequestLocalDevice(); - Task RequestMainController(); - Task RequestRandomDesktopDevice(); - Task RequestRandomMobileDevice(); - Task RequestDeviceByFilter(Func filter); - Task RequestUserSelectedDevice(IEnumerable candidates); + Task RequestMainController(); + Task RequestRandomDesktopDevice(); + Task RequestRandomMobileDevice(); + Task RequestDeviceByFilter(Func filter); + Task RequestUserSelectedDevice(IEnumerable candidates); Task> RequestDeviceList(); } } diff --git a/KitX Script/Kscript.CSharp/Interfaces/IDevice.cs b/KitX Script/Kscript.CSharp/Interfaces/IDevice.cs index a430d6c..029b635 100644 --- a/KitX Script/Kscript.CSharp/Interfaces/IDevice.cs +++ b/KitX Script/Kscript.CSharp/Interfaces/IDevice.cs @@ -1,4 +1,4 @@ -using KitX.Shared.CSharp.Device; +using KitX.Shared.CSharp.Device; using KitX.Shared.CSharp.Plugin; namespace Kscript.CSharp.Interfaces @@ -7,7 +7,6 @@ public interface IDevice { DeviceInfo Info { get; } Task RequestPlugin(string idOrName); - Task> RequestPluginsByType(string type); Task> GetPluginList(); bool HasPlugin(string idOrName); Task CreatePluginInstance(PluginInfo info); diff --git a/KitX Script/Kscript.CSharp/Interfaces/IFunction.cs b/KitX Script/Kscript.CSharp/Interfaces/IFunction.cs index 48026af..3f71963 100644 --- a/KitX Script/Kscript.CSharp/Interfaces/IFunction.cs +++ b/KitX Script/Kscript.CSharp/Interfaces/IFunction.cs @@ -1,4 +1,4 @@ -using KitX.Shared.CSharp.Plugin; +using KitX.Shared.CSharp.Plugin; namespace Kscript.CSharp.Interfaces { @@ -6,8 +6,7 @@ public interface IFunction { Function Info { get; } PluginInfo AssociatedPlugin { get; } - Task Invoke(params object[] parameters); - bool ValidateParameters(object[] parameters); + Task Invoke(params string[] parameters); Type GetReturnType(); } } diff --git a/KitX Script/Kscript.CSharp/Interfaces/IPlugin.cs b/KitX Script/Kscript.CSharp/Interfaces/IPlugin.cs index d66fd94..d6d7b37 100644 --- a/KitX Script/Kscript.CSharp/Interfaces/IPlugin.cs +++ b/KitX Script/Kscript.CSharp/Interfaces/IPlugin.cs @@ -1,4 +1,4 @@ -using KitX.Shared.CSharp.Device; +using KitX.Shared.CSharp.Device; using KitX.Shared.CSharp.Plugin; namespace Kscript.CSharp.Interfaces @@ -9,7 +9,7 @@ public interface IPlugin DeviceInfo AssociatedDevice { get; } Task RequestFunction(string idOrName); Task> GetFunctionList(); - Task GetFunctionByType(string type); + Task GetFunctionByType(string type); Task ExecuteFunction(string functionName, params object[] parameters); bool HasFunction(string idOrName); } diff --git a/KitX Script/Kscript.CSharp/Kscript.CSharp.csproj b/KitX Script/Kscript.CSharp/Kscript.CSharp.csproj index a2d8d7c..c2dce81 100644 --- a/KitX Script/Kscript.CSharp/Kscript.CSharp.csproj +++ b/KitX Script/Kscript.CSharp/Kscript.CSharp.csproj @@ -21,4 +21,8 @@ AGPL-3.0-only True + + + + diff --git a/KitX Script/Kscript.CSharp/Services/Composer.cs b/KitX Script/Kscript.CSharp/Services/Composer.cs index d37475d..77f59bb 100644 --- a/KitX Script/Kscript.CSharp/Services/Composer.cs +++ b/KitX Script/Kscript.CSharp/Services/Composer.cs @@ -1,41 +1,41 @@ -using KitX.Shared.CSharp.Device; +using KitX.Shared.CSharp.Device; +using KitX.Shared.CSharp.WebCommand; using Kscript.CSharp.Interfaces; namespace Kscript.CSharp.Services { public class Composer : IComposer { - private readonly NetworkConnector _connector = NetworkConnector.Instance; - private IEnumerable _cachedDeviceList; + private readonly Connector _connector = Connector.Instance; + private IEnumerable? _cachedDeviceList; - public async Task RequestLocalDevice() - { - var deviceInfo = await _connector.GetLocalDeviceInfo(); - return new Device(deviceInfo); - } + public async Task RequestLocalDevice() => throw new NotImplementedException(); - public async Task RequestMainController() + public async Task RequestMainController() { var devices = await RequestDeviceList(); - var mainController = devices.FirstOrDefault(x => x.IsMainController); + var mainController = devices.FirstOrDefault(x => x.IsMainDevice); return mainController != null ? new Device(mainController) : null; } - public async Task RequestRandomDesktopDevice() + public async Task RequestRandomDesktopDevice() { var devices = await RequestDeviceList(); - var desktopDevices = devices.Where(x => x.IsDesktopDevice).ToList(); + var desktopDevices = devices.Where(x => x.DeviceOSType == OperatingSystems.Windows + || x.DeviceOSType == OperatingSystems.MacOS + || x.DeviceOSType == OperatingSystems.Linux).ToList(); if (!desktopDevices.Any()) return null; - + var random = new Random(); var randomDevice = desktopDevices[random.Next(desktopDevices.Count)]; return new Device(randomDevice); } - public async Task RequestRandomMobileDevice() + public async Task RequestRandomMobileDevice() { var devices = await RequestDeviceList(); - var mobileDevices = devices.Where(x => x.IsMobileDevice).ToList(); + var mobileDevices = devices.Where(x => x.DeviceOSType == OperatingSystems.Android + || x.DeviceOSType == OperatingSystems.IOS).ToList(); if (!mobileDevices.Any()) return null; var random = new Random(); @@ -43,25 +43,29 @@ public async Task RequestRandomMobileDevice() return new Device(randomDevice); } - public async Task RequestDeviceByFilter(Func filter) + public async Task RequestDeviceByFilter(Func filter) { var devices = await RequestDeviceList(); var matchedDevice = devices.FirstOrDefault(filter); return matchedDevice != null ? new Device(matchedDevice) : null; } - public async Task RequestUserSelectedDevice(IEnumerable candidates) + // todo: implement this method + public Task RequestUserSelectedDevice(IEnumerable candidates) => throw new NotImplementedException(); + + public async Task> RequestDeviceList() => throw new NotImplementedException(); + + private async Task HandleResponse(Request response) { - // 这里需要调用本地UI让用户选择设备 - // 简化实现,返回第一个设备 - var device = candidates.FirstOrDefault(); - return device != null ? new Device(device) : null; + response.Match( + response.GetContent(content => content), // 这里需要提供实际的解密函数 + matchCommand: ProcessCommandResponse + ); } - public async Task> RequestDeviceList() + private void ProcessCommandResponse(string content) { - _cachedDeviceList = await _connector.GetDeviceList(); - return _cachedDeviceList; + // 处理命令响应 } } } diff --git a/KitX Script/Kscript.CSharp/Services/Device.cs b/KitX Script/Kscript.CSharp/Services/Device.cs index 1a7f7d6..d4e41f3 100644 --- a/KitX Script/Kscript.CSharp/Services/Device.cs +++ b/KitX Script/Kscript.CSharp/Services/Device.cs @@ -1,12 +1,13 @@ -using KitX.Shared.CSharp.Device; +using KitX.Shared.CSharp.Device; using KitX.Shared.CSharp.Plugin; +using KitX.Shared.CSharp.WebCommand; using Kscript.CSharp.Interfaces; namespace Kscript.CSharp.Services { public class Device : IDevice { - private readonly NetworkConnector _connector = NetworkConnector.Instance; + private readonly Connector _connector = Connector.Instance; private Dictionary _pluginCache = new(); public DeviceInfo Info { get; } @@ -20,37 +21,22 @@ public async Task RequestPlugin(string idOrName) { var plugins = await GetPluginList(); var pluginInfo = plugins.FirstOrDefault(p => - p.Id.Equals(idOrName, StringComparison.OrdinalIgnoreCase) || p.Name.Equals(idOrName, StringComparison.OrdinalIgnoreCase)); return pluginInfo != null ? await CreatePluginInstance(pluginInfo) : null; } - public async Task> RequestPluginsByType(string type) - { - var plugins = await GetPluginList(); - var matchedPlugins = plugins.Where(p => p.Type.Equals(type, StringComparison.OrdinalIgnoreCase)); - var tasks = matchedPlugins.Select(CreatePluginInstance); - return await Task.WhenAll(tasks); - } - - public async Task> GetPluginList() - { - var plugins = await _connector.GetPluginList(Info.Id); - _pluginCache = plugins.ToDictionary(p => p.Id); - return plugins; - } + public async Task> GetPluginList() => throw new NotImplementedException(); public bool HasPlugin(string idOrName) { return _pluginCache.Values.Any(p => - p.Id.Equals(idOrName, StringComparison.OrdinalIgnoreCase) || p.Name.Equals(idOrName, StringComparison.OrdinalIgnoreCase)); } public async Task CreatePluginInstance(PluginInfo info) { - return new Plugin(info, Info, _connector); + return new Plugin(info, Info); } } } diff --git a/KitX Script/Kscript.CSharp/Services/Function.cs b/KitX Script/Kscript.CSharp/Services/Function.cs index 315f59c..db23572 100644 --- a/KitX Script/Kscript.CSharp/Services/Function.cs +++ b/KitX Script/Kscript.CSharp/Services/Function.cs @@ -1,60 +1,48 @@ -using KitX.Shared.CSharp.Device; +using KitX.Shared.CSharp.Device; using KitX.Shared.CSharp.Plugin; +using KitX.Shared.CSharp.WebCommand; using Kscript.CSharp.Interfaces; namespace Kscript.CSharp.Services { public class Function : IFunction { - private readonly NetworkConnector _connector; + private readonly Connector _connector = Connector.Instance; private readonly DeviceInfo _deviceInfo; - public Function Info { get; } + public KitX.Shared.CSharp.Plugin.Function Info { get; } public PluginInfo AssociatedPlugin { get; } - public Function(KitX.Shared.CSharp.Plugin.Function info, PluginInfo pluginInfo, - DeviceInfo deviceInfo, NetworkConnector connector) + public Function(KitX.Shared.CSharp.Plugin.Function info, PluginInfo pluginInfo, DeviceInfo deviceInfo) { Info = info; AssociatedPlugin = pluginInfo; _deviceInfo = deviceInfo; - _connector = connector; } - public async Task Invoke(params object[] parameters) + public async Task Invoke(params string[] parameters) { - if (!ValidateParameters(parameters)) - { - throw new ArgumentException("Invalid parameters"); - } - - return await _connector.ExecuteFunction( - _deviceInfo.Id, - AssociatedPlugin.Id, - Info.Name, - parameters - ); - } - - public bool ValidateParameters(object[] parameters) - { - if (parameters.Length != Info.Parameters.Length) - return false; - - for (int i = 0; i < parameters.Length; i++) - { - if (!Info.Parameters[i].ParameterType.IsInstanceOfType(parameters[i])) + _connector.Request() + .UpdateCommand(cmd => + { + cmd.FunctionName = Info.Name; + cmd.FunctionArgs = parameters.Select(p => new Parameter { Value = p }).ToList(); + return cmd; + }) + .UpdateRequest(req => { - return false; - } - } + req.Target = _deviceInfo.Device; + return req; + }) + .Send(); - return true; + // 等待并处理响应 + return null; // 需要处理实际响应 } public Type GetReturnType() { - return Type.GetType(Info.ReturnType) ?? typeof(object); + return Type.GetType(Info.ReturnValueType) ?? typeof(object); } } } diff --git a/KitX Script/Kscript.CSharp/Services/NetworkConnector.cs b/KitX Script/Kscript.CSharp/Services/NetworkConnector.cs deleted file mode 100644 index 2c43911..0000000 --- a/KitX Script/Kscript.CSharp/Services/NetworkConnector.cs +++ /dev/null @@ -1,67 +0,0 @@ -using KitX.Shared.CSharp.WebCommand; -using KitX.Shared.CSharp.Device; -using KitX.Shared.CSharp.Plugin; - -namespace Kscript.CSharp.Services -{ - internal class NetworkConnector - { - private static readonly NetworkConnector _instance = new(); - private readonly Dictionary _deviceCache = new(); - private readonly Connector _connector = new(); - - public static NetworkConnector Instance => _instance; - - private NetworkConnector() - { - // 初始化连接器 - _connector.Connect(); - } - - public async Task GetLocalDeviceInfo() - { - var request = new RequestBuilder() - .SetCommandType(CommandType.GetLocalDeviceInfo) - .Build(); - var response = await _connector.SendRequest(request); - return response.As(); - } - - public async Task> GetDeviceList() - { - var request = new RequestBuilder() - .SetCommandType(CommandType.GetDeviceList) - .Build(); - var response = await _connector.SendRequest(request); - var devices = response.As>(); - foreach (var device in devices) - { - _deviceCache[device.Id] = device; - } - return devices; - } - - public async Task> GetPluginList(string deviceId) - { - var request = new RequestBuilder() - .SetCommandType(CommandType.GetPluginList) - .SetTarget(deviceId) - .Build(); - var response = await _connector.SendRequest(request); - return response.As>(); - } - - public async Task ExecuteFunction(string deviceId, string pluginId, string functionName, object[] parameters) - { - var request = new RequestBuilder() - .SetCommandType(CommandType.ExecuteFunction) - .SetTarget(deviceId) - .AddParameter("pluginId", pluginId) - .AddParameter("functionName", functionName) - .AddParameter("parameters", parameters) - .Build(); - var response = await _connector.SendRequest(request); - return response.Result; - } - } -} diff --git a/KitX Script/Kscript.CSharp/Services/Plugin.cs b/KitX Script/Kscript.CSharp/Services/Plugin.cs index 6ca7896..d105fed 100644 --- a/KitX Script/Kscript.CSharp/Services/Plugin.cs +++ b/KitX Script/Kscript.CSharp/Services/Plugin.cs @@ -1,29 +1,28 @@ -using KitX.Shared.CSharp.Device; +using KitX.Shared.CSharp.Device; using KitX.Shared.CSharp.Plugin; +using KitX.Shared.CSharp.WebCommand; using Kscript.CSharp.Interfaces; namespace Kscript.CSharp.Services { public class Plugin : IPlugin { - private readonly NetworkConnector _connector; + private readonly Connector _connector = Connector.Instance; private readonly Dictionary _functionCache = new(); public PluginInfo Info { get; } public DeviceInfo AssociatedDevice { get; } - public Plugin(PluginInfo info, DeviceInfo deviceInfo, NetworkConnector connector) + public Plugin(PluginInfo info, DeviceInfo deviceInfo) { Info = info; AssociatedDevice = deviceInfo; - _connector = connector; } public async Task RequestFunction(string idOrName) { var functions = await GetFunctionList(); var function = functions.FirstOrDefault(f => - f.Info.Id.Equals(idOrName, StringComparison.OrdinalIgnoreCase) || f.Info.Name.Equals(idOrName, StringComparison.OrdinalIgnoreCase)); return function; } @@ -35,27 +34,37 @@ public async Task> GetFunctionList() return await Task.FromResult(functions); } - public async Task GetFunctionByType(string type) + public async Task GetFunctionByType(string type) { var functions = await GetFunctionList(); return functions.FirstOrDefault(f => - f.Info.ReturnType.Equals(type, StringComparison.OrdinalIgnoreCase)); + f.Info.ReturnValueType.Equals(type, StringComparison.OrdinalIgnoreCase)); } public async Task ExecuteFunction(string functionName, params object[] parameters) { - return await _connector.ExecuteFunction( - AssociatedDevice.Id, - Info.Id, - functionName, - parameters - ); + _connector.Request() + .UpdateCommand(cmd => + { + cmd.PluginConnectionId = Info.Id; + cmd.FunctionName = functionName; + cmd.FunctionArgs = parameters.Select(p => new Parameter { Value = p }).ToList(); + return cmd; + }) + .UpdateRequest(req => + { + req.Target = new DeviceLocator { Id = AssociatedDevice.Id }; + return req; + }) + .Send(); + + // 处理响应 + return null; // 需要处理实际响应 } public bool HasFunction(string idOrName) { return Info.Functions.Any(f => - f.Id.Equals(idOrName, StringComparison.OrdinalIgnoreCase) || f.Name.Equals(idOrName, StringComparison.OrdinalIgnoreCase)); } } From 9ba9d8ee722f5be52f4ef63db6e5112946582a55 Mon Sep 17 00:00:00 2001 From: StarInk Date: Mon, 10 Feb 2025 02:21:48 +0200 Subject: [PATCH 04/17] =?UTF-8?q?=F0=9F=94=A7=20Fix(Kscript.CSharp):=20?= =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=8E=A5=E5=8F=A3=E4=BB=A5=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E5=AD=97=E7=AC=A6=E4=B8=B2=E6=95=B0=E7=BB=84=E4=BD=9C=E4=B8=BA?= =?UTF-8?q?=E5=8F=82=E6=95=B0=EF=BC=8C=E9=87=8D=E5=91=BD=E5=90=8D=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E5=8F=82=E6=95=B0=E4=BB=A5=E6=8F=90=E9=AB=98=E5=8F=AF?= =?UTF-8?q?=E8=AF=BB=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- KitX Script/Kscript.CSharp/Interfaces/IPlugin.cs | 2 +- KitX Script/Kscript.CSharp/Services/Device.cs | 8 ++++---- KitX Script/Kscript.CSharp/Services/Plugin.cs | 16 +++++++--------- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/KitX Script/Kscript.CSharp/Interfaces/IPlugin.cs b/KitX Script/Kscript.CSharp/Interfaces/IPlugin.cs index d6d7b37..4ab8b6e 100644 --- a/KitX Script/Kscript.CSharp/Interfaces/IPlugin.cs +++ b/KitX Script/Kscript.CSharp/Interfaces/IPlugin.cs @@ -10,7 +10,7 @@ public interface IPlugin Task RequestFunction(string idOrName); Task> GetFunctionList(); Task GetFunctionByType(string type); - Task ExecuteFunction(string functionName, params object[] parameters); + Task ExecuteFunction(string functionName, params string[] parameters); bool HasFunction(string idOrName); } } diff --git a/KitX Script/Kscript.CSharp/Services/Device.cs b/KitX Script/Kscript.CSharp/Services/Device.cs index d4e41f3..888ece1 100644 --- a/KitX Script/Kscript.CSharp/Services/Device.cs +++ b/KitX Script/Kscript.CSharp/Services/Device.cs @@ -17,21 +17,21 @@ public Device(DeviceInfo info) Info = info; } - public async Task RequestPlugin(string idOrName) + public async Task RequestPlugin(string Name) { var plugins = await GetPluginList(); var pluginInfo = plugins.FirstOrDefault(p => - p.Name.Equals(idOrName, StringComparison.OrdinalIgnoreCase)); + p.Name.Equals(Name, StringComparison.OrdinalIgnoreCase)); return pluginInfo != null ? await CreatePluginInstance(pluginInfo) : null; } public async Task> GetPluginList() => throw new NotImplementedException(); - public bool HasPlugin(string idOrName) + public bool HasPlugin(string Name) { return _pluginCache.Values.Any(p => - p.Name.Equals(idOrName, StringComparison.OrdinalIgnoreCase)); + p.Name.Equals(Name, StringComparison.OrdinalIgnoreCase)); } public async Task CreatePluginInstance(PluginInfo info) diff --git a/KitX Script/Kscript.CSharp/Services/Plugin.cs b/KitX Script/Kscript.CSharp/Services/Plugin.cs index d105fed..2e3ae92 100644 --- a/KitX Script/Kscript.CSharp/Services/Plugin.cs +++ b/KitX Script/Kscript.CSharp/Services/Plugin.cs @@ -8,7 +8,6 @@ namespace Kscript.CSharp.Services public class Plugin : IPlugin { private readonly Connector _connector = Connector.Instance; - private readonly Dictionary _functionCache = new(); public PluginInfo Info { get; } public DeviceInfo AssociatedDevice { get; } @@ -19,18 +18,18 @@ public Plugin(PluginInfo info, DeviceInfo deviceInfo) AssociatedDevice = deviceInfo; } - public async Task RequestFunction(string idOrName) + public async Task RequestFunction(string Name) { var functions = await GetFunctionList(); var function = functions.FirstOrDefault(f => - f.Info.Name.Equals(idOrName, StringComparison.OrdinalIgnoreCase)); + f.Info.Name.Equals(Name, StringComparison.OrdinalIgnoreCase)); return function; } public async Task> GetFunctionList() { var functions = Info.Functions.Select(f => - new Function(f, Info, AssociatedDevice, _connector) as IFunction); + new Function(f, Info, AssociatedDevice) as IFunction); return await Task.FromResult(functions); } @@ -41,19 +40,18 @@ public async Task> GetFunctionList() f.Info.ReturnValueType.Equals(type, StringComparison.OrdinalIgnoreCase)); } - public async Task ExecuteFunction(string functionName, params object[] parameters) + public async Task ExecuteFunction(string functionName, params string[] parameters) { _connector.Request() .UpdateCommand(cmd => { - cmd.PluginConnectionId = Info.Id; cmd.FunctionName = functionName; cmd.FunctionArgs = parameters.Select(p => new Parameter { Value = p }).ToList(); return cmd; }) .UpdateRequest(req => { - req.Target = new DeviceLocator { Id = AssociatedDevice.Id }; + req.Target = AssociatedDevice.Device; return req; }) .Send(); @@ -62,10 +60,10 @@ public async Task ExecuteFunction(string functionName, params object[] p return null; // 需要处理实际响应 } - public bool HasFunction(string idOrName) + public bool HasFunction(string Name) { return Info.Functions.Any(f => - f.Name.Equals(idOrName, StringComparison.OrdinalIgnoreCase)); + f.Name.Equals(Name, StringComparison.OrdinalIgnoreCase)); } } } From 93b834ddb0b37bdbb5bd5b456ab6eca3e5ac47ff Mon Sep 17 00:00:00 2001 From: StarInk Date: Wed, 12 Feb 2025 01:29:03 +0200 Subject: [PATCH 05/17] =?UTF-8?q?=F0=9F=92=BE=20Feat(Kscript.CSharp):=20?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=AF=B9=20System.Text.Json=20=E7=9A=84?= =?UTF-8?q?=E6=94=AF=E6=8C=81=EF=BC=8C=E6=9B=B4=E6=96=B0=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E4=BB=A5=E8=BF=94=E5=9B=9E=E5=8F=AF=E7=A9=BA=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=EF=BC=8C=E4=BC=98=E5=8C=96=E6=8F=92=E4=BB=B6=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E9=80=BB=E8=BE=91-=E4=B8=8D=E5=8F=AF?= =?UTF-8?q?=E7=94=A8=EF=BC=8C=E5=BE=85=E8=AE=A8=E8=AE=BA-01?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Kscript.CSharp/Interfaces/IComposer.cs | 2 +- .../Kscript.CSharp/Interfaces/IPlugin.cs | 2 +- .../Kscript.CSharp/Services/Composer.cs | 153 ++++++++++++++++-- KitX Script/Kscript.CSharp/Services/Device.cs | 89 +++++++++- .../Kscript.CSharp/Services/Function.cs | 113 ++++++++++++- KitX Script/Kscript.CSharp/Services/Plugin.cs | 114 ++++++++++++- .../KitX.Shared.CSharp.csproj | 4 + 7 files changed, 457 insertions(+), 20 deletions(-) diff --git a/KitX Script/Kscript.CSharp/Interfaces/IComposer.cs b/KitX Script/Kscript.CSharp/Interfaces/IComposer.cs index c7c11ef..d051b2e 100644 --- a/KitX Script/Kscript.CSharp/Interfaces/IComposer.cs +++ b/KitX Script/Kscript.CSharp/Interfaces/IComposer.cs @@ -4,7 +4,7 @@ namespace Kscript.CSharp.Interfaces { public interface IComposer { - Task RequestLocalDevice(); + Task RequestLocalDevice(); Task RequestMainController(); Task RequestRandomDesktopDevice(); Task RequestRandomMobileDevice(); diff --git a/KitX Script/Kscript.CSharp/Interfaces/IPlugin.cs b/KitX Script/Kscript.CSharp/Interfaces/IPlugin.cs index 4ab8b6e..2e290d0 100644 --- a/KitX Script/Kscript.CSharp/Interfaces/IPlugin.cs +++ b/KitX Script/Kscript.CSharp/Interfaces/IPlugin.cs @@ -7,7 +7,7 @@ public interface IPlugin { PluginInfo Info { get; } DeviceInfo AssociatedDevice { get; } - Task RequestFunction(string idOrName); + Task RequestFunction(string idOrName); Task> GetFunctionList(); Task GetFunctionByType(string type); Task ExecuteFunction(string functionName, params string[] parameters); diff --git a/KitX Script/Kscript.CSharp/Services/Composer.cs b/KitX Script/Kscript.CSharp/Services/Composer.cs index 77f59bb..b911ac8 100644 --- a/KitX Script/Kscript.CSharp/Services/Composer.cs +++ b/KitX Script/Kscript.CSharp/Services/Composer.cs @@ -1,6 +1,8 @@ using KitX.Shared.CSharp.Device; +using KitX.Shared.CSharp.Plugin; using KitX.Shared.CSharp.WebCommand; using Kscript.CSharp.Interfaces; +using System.Text.Json; namespace Kscript.CSharp.Services { @@ -9,7 +11,16 @@ public class Composer : IComposer private readonly Connector _connector = Connector.Instance; private IEnumerable? _cachedDeviceList; - public async Task RequestLocalDevice() => throw new NotImplementedException(); + public async Task RequestLocalDevice() + { + var devices = await RequestDeviceList(); + var localDevice = devices.FirstOrDefault(x => + (x.Device.IPv4?.Equals("127.0.0.1") ?? false) || // Check localhost + (x.Device.IPv4?.Equals("::1") ?? false)); // Check IPv6 localhost + // todo: fixme: 这个检查方案会有问题。改进建议:检查IP地址是否与本地设备的IP地址有交集。因为广播出来的地址可能是本机地址的外网地址 + + return localDevice != null ? new Device(localDevice) : null; + } public async Task RequestMainController() { @@ -24,7 +35,7 @@ public class Composer : IComposer var desktopDevices = devices.Where(x => x.DeviceOSType == OperatingSystems.Windows || x.DeviceOSType == OperatingSystems.MacOS || x.DeviceOSType == OperatingSystems.Linux).ToList(); - if (!desktopDevices.Any()) return null; + if (desktopDevices.Count == 0) return null; var random = new Random(); var randomDevice = desktopDevices[random.Next(desktopDevices.Count)]; @@ -36,7 +47,7 @@ public class Composer : IComposer var devices = await RequestDeviceList(); var mobileDevices = devices.Where(x => x.DeviceOSType == OperatingSystems.Android || x.DeviceOSType == OperatingSystems.IOS).ToList(); - if (!mobileDevices.Any()) return null; + if (mobileDevices.Count == 0) return null; var random = new Random(); var randomDevice = mobileDevices[random.Next(mobileDevices.Count)]; @@ -50,22 +61,144 @@ public class Composer : IComposer return matchedDevice != null ? new Device(matchedDevice) : null; } - // todo: implement this method - public Task RequestUserSelectedDevice(IEnumerable candidates) => throw new NotImplementedException(); + public async Task RequestUserSelectedDevice(IEnumerable candidates) + { + var tcs = new TaskCompletionSource(); + + void OnResponse(Request response) + { + try + { + response.Match( + response.GetContent(content => // 这里返回的是用户选择的设备的 MAC 地址 + { + // Parse selected device from response + if (string.IsNullOrEmpty(content)) + { + tcs.SetResult(null); + return content; + } + + var selectedDevice = candidates.FirstOrDefault(d => + d.Device.MacAddress.ToString().Equals(content, StringComparison.OrdinalIgnoreCase)); + tcs.SetResult(selectedDevice); + return content; + }), + matchCommand: ProcessCommandResponse + ); + } + catch (Exception ex) + { + tcs.SetException(ex); + } + } - public async Task> RequestDeviceList() => throw new NotImplementedException(); + // Send candidates to local device for selection + _connector.Request() + .UpdateCommand(cmd => + { + cmd.FunctionName = "SelectDevice"; + cmd.FunctionArgs = candidates.Select(d => new Parameter { Value = d.ToString() }).ToList(); + return cmd; + }) + .UpdateRequest(req => + { + req.Type = RequestTypes.Command; + req.Version = RequestVersions.V1; + req.Target = null; // Send to local device + return req; + }) + .SetSender(OnResponse) + .Send(); - private async Task HandleResponse(Request response) + var selectedDevice = await tcs.Task; + return selectedDevice != null ? new Device(selectedDevice) : null; + } + + public async Task> RequestDeviceList() { + if (_cachedDeviceList != null) + return _cachedDeviceList; + + var tcs = new TaskCompletionSource>(); + + void OnResponse(Request response) + { + try + { + var devices = HandleDeviceListResponse(response); + _cachedDeviceList = devices; + tcs.SetResult(devices); + } + catch (Exception ex) + { + tcs.SetException(ex); + } + } + + // Request device list from network + _connector.Request() + .UpdateCommand(cmd => + { + cmd.FunctionName = "GetDeviceList"; + return cmd; + }) + .UpdateRequest(req => + { + req.Type = RequestTypes.Command; + req.Version = RequestVersions.V1; + return req; + }) + .SetSender(OnResponse) + .Send(); + + return await tcs.Task; + } + + private IEnumerable HandleDeviceListResponse(Request response) + { + var devices = new List(); + response.Match( - response.GetContent(content => content), // 这里需要提供实际的解密函数 - matchCommand: ProcessCommandResponse + response.GetContent(content => + { + // Parse device list from content + // This should be replaced with proper deserialization based on response format + // For now returning empty list as placeholder + return content; + }), + matchCommand: content => + { + ProcessCommandResponse(content); + } ); + + return devices; } private void ProcessCommandResponse(string content) { - // 处理命令响应 + if (string.IsNullOrEmpty(content)) + return; + + try + { + // Handle different command responses based on content + if (content.StartsWith("DeviceList:")) + { + var deviceListJson = content["DeviceList:".Length..]; + _cachedDeviceList = JsonSerializer.Deserialize>(deviceListJson); + } + else if (content.StartsWith("Error:")) + { + var error = content["Error:".Length..]; + throw new InvalidOperationException($"Command error: {error}"); + } + } + catch (Exception ex) + { + throw new InvalidOperationException($"Failed to process command response: {ex.Message}", ex); + } } } } diff --git a/KitX Script/Kscript.CSharp/Services/Device.cs b/KitX Script/Kscript.CSharp/Services/Device.cs index 888ece1..b8ed769 100644 --- a/KitX Script/Kscript.CSharp/Services/Device.cs +++ b/KitX Script/Kscript.CSharp/Services/Device.cs @@ -1,6 +1,7 @@ -using KitX.Shared.CSharp.Device; +using KitX.Shared.CSharp.Device; using KitX.Shared.CSharp.Plugin; using KitX.Shared.CSharp.WebCommand; +using System.Text.Json; using Kscript.CSharp.Interfaces; namespace Kscript.CSharp.Services @@ -26,12 +27,92 @@ public async Task RequestPlugin(string Name) return pluginInfo != null ? await CreatePluginInstance(pluginInfo) : null; } - public async Task> GetPluginList() => throw new NotImplementedException(); + public async Task> GetPluginList() + { + if (_pluginCache.Any()) + return _pluginCache.Values; + + var tcs = new TaskCompletionSource>(); + + void OnResponse(Request response) + { + try + { + response.Match( + response.GetContent(content => + { + if (string.IsNullOrEmpty(content)) + { + tcs.SetResult(Array.Empty()); + return content; + } + + // With the following code + var plugins = JsonSerializer.Deserialize>(content); + + // Update cache + _pluginCache = plugins.ToDictionary( + p => p.Name, + p => p, + StringComparer.OrdinalIgnoreCase + ); + + tcs.SetResult(plugins); + return content; + }), + matchCommand: content => + { + if (content.StartsWith("Error:")) + { + var error = content.Substring("Error:".Length); + tcs.SetException(new InvalidOperationException($"Failed to get plugin list: {error}")); + } + else if (content.StartsWith("PluginList:")) + { + var pluginListJson = content.Substring("PluginList:".Length); + var plugins = JsonSerializer.Deserialize>(pluginListJson); + + // Update cache + _pluginCache = plugins.ToDictionary( + p => p.Name, + p => p, + StringComparer.OrdinalIgnoreCase + ); + + tcs.SetResult(plugins); + } + } + ); + } + catch (Exception ex) + { + tcs.SetException(ex); + } + } + + // Request plugin list from device + _connector.Request() + .UpdateCommand(cmd => + { + cmd.FunctionName = "GetPluginList"; + return cmd; + }) + .UpdateRequest(req => + { + req.Type = RequestTypes.Command; + req.Version = RequestVersions.V1; + req.Target = Info.Device; + return req; + }) + .SetSender(OnResponse) + .Send(); + + return await tcs.Task; + } public bool HasPlugin(string Name) { - return _pluginCache.Values.Any(p => - p.Name.Equals(Name, StringComparison.OrdinalIgnoreCase)); + return _pluginCache.ContainsKey(Name); } public async Task CreatePluginInstance(PluginInfo info) diff --git a/KitX Script/Kscript.CSharp/Services/Function.cs b/KitX Script/Kscript.CSharp/Services/Function.cs index db23572..0ea316f 100644 --- a/KitX Script/Kscript.CSharp/Services/Function.cs +++ b/KitX Script/Kscript.CSharp/Services/Function.cs @@ -36,13 +36,120 @@ public async Task Invoke(params string[] parameters) }) .Send(); - // 等待并处理响应 - return null; // 需要处理实际响应 + var tcs = new TaskCompletionSource(); + + void OnResponse(Request response) + { + try + { + response.Match( + response.GetContent(content => + { + if (string.IsNullOrEmpty(content)) + { + tcs.SetResult(null); + return content; + } + + // Parse response based on return type + var returnType = GetReturnType(); + var result = ParseResponse(content, returnType); + tcs.SetResult(result); + return content; + }), + matchCommand: content => + { + if (content.StartsWith("Error:")) + { + var error = content.Substring("Error:".Length); + tcs.SetException(new InvalidOperationException($"Function execution failed: {error}")); + } + else if (content.StartsWith("Result:")) + { + var resultJson = content.Substring("Result:".Length); + var returnType = GetReturnType(); + var result = ParseResponse(resultJson, returnType); + tcs.SetResult(result); + } + } + ); + } + catch (Exception ex) + { + tcs.SetException(ex); + } + } + + _connector.Request() + .SetSender(OnResponse); + + return await tcs.Task; } public Type GetReturnType() { - return Type.GetType(Info.ReturnValueType) ?? typeof(object); + // Try to get the type from the assembly first + var type = Type.GetType(Info.ReturnValueType); + if (type != null) + return type; + + // Handle common type names that might not be fully qualified + switch (Info.ReturnValueType.ToLower()) + { + case "string": + return typeof(string); + case "int": + case "int32": + return typeof(int); + case "long": + case "int64": + return typeof(long); + case "float": + case "single": + return typeof(float); + case "double": + return typeof(double); + case "bool": + case "boolean": + return typeof(bool); + case "void": + return typeof(void); + default: + return typeof(object); + } + } + + private object ParseResponse(string content, Type returnType) + { + if (string.IsNullOrEmpty(content)) + return null; + + try + { + if (returnType == typeof(void)) + return null; + + // Handle primitive types + if (returnType == typeof(string)) + return content; + if (returnType == typeof(int)) + return int.Parse(content); + if (returnType == typeof(long)) + return long.Parse(content); + if (returnType == typeof(float)) + return float.Parse(content); + if (returnType == typeof(double)) + return double.Parse(content); + if (returnType == typeof(bool)) + return bool.Parse(content); + + // For complex types, deserialize from JSON + return System.Text.Json.JsonSerializer.Deserialize(content, returnType); + } + catch (Exception ex) + { + throw new InvalidOperationException($"Failed to parse response as {returnType.Name}: {ex.Message}", ex); + } } } } diff --git a/KitX Script/Kscript.CSharp/Services/Plugin.cs b/KitX Script/Kscript.CSharp/Services/Plugin.cs index 2e3ae92..8038730 100644 --- a/KitX Script/Kscript.CSharp/Services/Plugin.cs +++ b/KitX Script/Kscript.CSharp/Services/Plugin.cs @@ -57,7 +57,119 @@ public async Task ExecuteFunction(string functionName, params string[] p .Send(); // 处理响应 - return null; // 需要处理实际响应 + var tcs = new TaskCompletionSource(); + + void OnResponse(Request response) + { + try + { + response.Match( + response.GetContent(content => + { + var returnType = GetFunctionReturnType(functionName); + var result = ParseFunctionResponse(content, returnType); + tcs.SetResult(result); + return content; + }), + matchCommand: content => + { + if (content.StartsWith("Error:")) + { + var error = content.Substring("Error:".Length); + tcs.SetException(new InvalidOperationException($"Function execution failed: {error}")); + } + else if (content.StartsWith("Result:")) + { + var resultJson = content.Substring("Result:".Length); + var returnType = GetFunctionReturnType(functionName); + var result = ParseFunctionResponse(resultJson, returnType); + tcs.SetResult(result); + } + } + ); + } + catch (Exception ex) + { + tcs.SetException(ex); + } + } + + _connector.Request() + .SetSender(OnResponse); + + return await tcs.Task; + } + + private Type GetFunctionReturnType(string functionName) + { + var function = Info.Functions.FirstOrDefault(f => + f.Name.Equals(functionName, StringComparison.OrdinalIgnoreCase)); + + if (function == null) + throw new InvalidOperationException($"Function '{functionName}' not found in plugin '{Info.Name}'"); + + // Try to get the type from the assembly first + var type = Type.GetType(function.ReturnValueType); + if (type != null) + return type; + + // Handle common type names that might not be fully qualified + switch (function.ReturnValueType.ToLower()) + { + case "string": + return typeof(string); + case "int": + case "int32": + return typeof(int); + case "long": + case "int64": + return typeof(long); + case "float": + case "single": + return typeof(float); + case "double": + return typeof(double); + case "bool": + case "boolean": + return typeof(bool); + case "void": + return typeof(void); + default: + return typeof(object); + } + } + + private object ParseFunctionResponse(string content, Type returnType) + { + if (string.IsNullOrEmpty(content)) + return null; + + try + { + if (returnType == typeof(void)) + return null; + + // Handle primitive types + if (returnType == typeof(string)) + return content; + if (returnType == typeof(int)) + return int.Parse(content); + if (returnType == typeof(long)) + return long.Parse(content); + if (returnType == typeof(float)) + return float.Parse(content); + if (returnType == typeof(double)) + return double.Parse(content); + if (returnType == typeof(bool)) + return bool.Parse(content); + + // For complex types, deserialize from JSON + return System.Text.Json.JsonSerializer.Deserialize(content, returnType); + } + catch (Exception ex) + { + throw new InvalidOperationException($"Failed to parse response as {returnType.Name}: {ex.Message}", ex); + } } public bool HasFunction(string Name) diff --git a/KitX Shared/KitX.Shared.CSharp/KitX.Shared.CSharp.csproj b/KitX Shared/KitX.Shared.CSharp/KitX.Shared.CSharp.csproj index 03d1926..6246e52 100644 --- a/KitX Shared/KitX.Shared.CSharp/KitX.Shared.CSharp.csproj +++ b/KitX Shared/KitX.Shared.CSharp/KitX.Shared.CSharp.csproj @@ -37,4 +37,8 @@ + + + + From c759c6ac16ee6faf6b7c5c661bf0795849d67281 Mon Sep 17 00:00:00 2001 From: StarInk Date: Sat, 15 Feb 2025 10:25:41 +0200 Subject: [PATCH 06/17] =?UTF-8?q?=F0=9F=92=BE=20Feat(Kscript.CSharp):=20?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=9C=8D=E5=8A=A1=E7=B1=BB=E4=BB=A5=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=8F=AF=E9=80=89=E7=9A=84=20Connector=20=E5=8F=82?= =?UTF-8?q?=E6=95=B0=EF=BC=8C=E4=BC=98=E5=8C=96=E6=8F=92=E4=BB=B6=E5=92=8C?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E5=AE=9E=E4=BE=8B=E5=8C=96=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- KitX Script/Kscript.CSharp/Services/Composer.cs | 9 +++++++-- KitX Script/Kscript.CSharp/Services/Device.cs | 7 ++++--- KitX Script/Kscript.CSharp/Services/Function.cs | 5 +++-- KitX Script/Kscript.CSharp/Services/Plugin.cs | 11 ++++++----- 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/KitX Script/Kscript.CSharp/Services/Composer.cs b/KitX Script/Kscript.CSharp/Services/Composer.cs index b911ac8..a707c2f 100644 --- a/KitX Script/Kscript.CSharp/Services/Composer.cs +++ b/KitX Script/Kscript.CSharp/Services/Composer.cs @@ -8,7 +8,12 @@ namespace Kscript.CSharp.Services { public class Composer : IComposer { - private readonly Connector _connector = Connector.Instance; + private readonly Connector _connector; + + public Composer(Connector? connector = null) + { + _connector = connector ?? Connector.Instance; + } private IEnumerable? _cachedDeviceList; public async Task RequestLocalDevice() @@ -98,7 +103,7 @@ void OnResponse(Request response) .UpdateCommand(cmd => { cmd.FunctionName = "SelectDevice"; - cmd.FunctionArgs = candidates.Select(d => new Parameter { Value = d.ToString() }).ToList(); + cmd.FunctionArgs = candidates.Select(d => new Parameter { Value = d.ToString() }).ToList(); // todo: 改为传入一个函数用于筛选设备 return cmd; }) .UpdateRequest(req => diff --git a/KitX Script/Kscript.CSharp/Services/Device.cs b/KitX Script/Kscript.CSharp/Services/Device.cs index b8ed769..52d222d 100644 --- a/KitX Script/Kscript.CSharp/Services/Device.cs +++ b/KitX Script/Kscript.CSharp/Services/Device.cs @@ -8,14 +8,15 @@ namespace Kscript.CSharp.Services { public class Device : IDevice { - private readonly Connector _connector = Connector.Instance; + private readonly Connector _connector; private Dictionary _pluginCache = new(); public DeviceInfo Info { get; } - public Device(DeviceInfo info) + public Device(DeviceInfo info, Connector? connector = null) { Info = info; + _connector = connector ?? Connector.Instance; } public async Task RequestPlugin(string Name) @@ -117,7 +118,7 @@ public bool HasPlugin(string Name) public async Task CreatePluginInstance(PluginInfo info) { - return new Plugin(info, Info); + return new Plugin(info, Info, _connector); } } } diff --git a/KitX Script/Kscript.CSharp/Services/Function.cs b/KitX Script/Kscript.CSharp/Services/Function.cs index 0ea316f..f9aefba 100644 --- a/KitX Script/Kscript.CSharp/Services/Function.cs +++ b/KitX Script/Kscript.CSharp/Services/Function.cs @@ -7,17 +7,18 @@ namespace Kscript.CSharp.Services { public class Function : IFunction { - private readonly Connector _connector = Connector.Instance; + private readonly Connector _connector; private readonly DeviceInfo _deviceInfo; public KitX.Shared.CSharp.Plugin.Function Info { get; } public PluginInfo AssociatedPlugin { get; } - public Function(KitX.Shared.CSharp.Plugin.Function info, PluginInfo pluginInfo, DeviceInfo deviceInfo) + public Function(KitX.Shared.CSharp.Plugin.Function info, PluginInfo pluginInfo, DeviceInfo deviceInfo, Connector? connector = null) { Info = info; AssociatedPlugin = pluginInfo; _deviceInfo = deviceInfo; + _connector = connector ?? Connector.Instance; } public async Task Invoke(params string[] parameters) diff --git a/KitX Script/Kscript.CSharp/Services/Plugin.cs b/KitX Script/Kscript.CSharp/Services/Plugin.cs index 8038730..b3f9a45 100644 --- a/KitX Script/Kscript.CSharp/Services/Plugin.cs +++ b/KitX Script/Kscript.CSharp/Services/Plugin.cs @@ -1,4 +1,4 @@ -using KitX.Shared.CSharp.Device; +using KitX.Shared.CSharp.Device; using KitX.Shared.CSharp.Plugin; using KitX.Shared.CSharp.WebCommand; using Kscript.CSharp.Interfaces; @@ -7,15 +7,16 @@ namespace Kscript.CSharp.Services { public class Plugin : IPlugin { - private readonly Connector _connector = Connector.Instance; + private readonly Connector _connector; public PluginInfo Info { get; } public DeviceInfo AssociatedDevice { get; } - public Plugin(PluginInfo info, DeviceInfo deviceInfo) + public Plugin(PluginInfo info, DeviceInfo deviceInfo, Connector? connector = null) { Info = info; AssociatedDevice = deviceInfo; + _connector = connector ?? Connector.Instance; } public async Task RequestFunction(string Name) @@ -29,7 +30,7 @@ public async Task RequestFunction(string Name) public async Task> GetFunctionList() { var functions = Info.Functions.Select(f => - new Function(f, Info, AssociatedDevice) as IFunction); + new Function(f, Info, AssociatedDevice, _connector) as IFunction); return await Task.FromResult(functions); } @@ -105,7 +106,7 @@ private Type GetFunctionReturnType(string functionName) var function = Info.Functions.FirstOrDefault(f => f.Name.Equals(functionName, StringComparison.OrdinalIgnoreCase)); - if (function == null) + if (function.Equals(default(Function))) throw new InvalidOperationException($"Function '{functionName}' not found in plugin '{Info.Name}'"); // Try to get the type from the assembly first From d72cb557019b56461dc31e9e492cfb9a47840383 Mon Sep 17 00:00:00 2001 From: StarInk Date: Sat, 15 Feb 2025 21:53:18 +0200 Subject: [PATCH 07/17] =?UTF-8?q?=F0=9F=92=BE=20Feat(Kscript.CSharp):=20?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=AE=BE=E5=A4=87=E4=BA=8B=E4=BB=B6=E7=9B=91?= =?UTF-8?q?=E5=90=AC=E5=99=A8=E6=8E=A5=E5=8F=A3=E5=92=8C=E7=BD=91=E7=BB=9C?= =?UTF-8?q?=E5=B7=A5=E5=85=B7=E7=B1=BB=EF=BC=8C=E6=8F=90=E4=BE=9B=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E7=8A=B6=E6=80=81=E6=9B=B4=E6=96=B0=E5=92=8C=E7=BD=91?= =?UTF-8?q?=E7=BB=9C=E5=9C=B0=E5=9D=80=E6=A3=80=E6=9F=A5=E5=8A=9F=E8=83=BD?= =?UTF-8?q?-=E6=83=B3=E9=87=8D=E6=9E=84=E5=AE=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Interfaces/IDeviceEventListener.cs | 34 ++ .../Kscript.CSharp/Services/Composer.cs | 315 ++++++++++-------- .../Kscript.CSharp/Utils/DeviceCache.cs | 193 +++++++++++ .../Utils/DeviceRequestBuilder.cs | 141 ++++++++ .../Kscript.CSharp/Utils/NetworkUtils.cs | 93 ++++++ .../Kscript.CSharp/Utils/ResponseHandler.cs | 124 +++++++ 6 files changed, 770 insertions(+), 130 deletions(-) create mode 100644 KitX Script/Kscript.CSharp/Interfaces/IDeviceEventListener.cs create mode 100644 KitX Script/Kscript.CSharp/Utils/DeviceCache.cs create mode 100644 KitX Script/Kscript.CSharp/Utils/DeviceRequestBuilder.cs create mode 100644 KitX Script/Kscript.CSharp/Utils/NetworkUtils.cs create mode 100644 KitX Script/Kscript.CSharp/Utils/ResponseHandler.cs diff --git a/KitX Script/Kscript.CSharp/Interfaces/IDeviceEventListener.cs b/KitX Script/Kscript.CSharp/Interfaces/IDeviceEventListener.cs new file mode 100644 index 0000000..3071b78 --- /dev/null +++ b/KitX Script/Kscript.CSharp/Interfaces/IDeviceEventListener.cs @@ -0,0 +1,34 @@ +using KitX.Shared.CSharp.Device; + +namespace Kscript.CSharp.Interfaces +{ + /// + /// 设备事件监听器接口 + /// + public interface IDeviceEventListener + { + /// + /// 当设备列表更新时触发 + /// + /// 更新后的设备列表 + void OnDeviceListUpdated(IEnumerable devices); + + /// + /// 当有新设备添加时触发 + /// + /// 新添加的设备 + void OnDeviceAdded(DeviceInfo device); + + /// + /// 当设备被移除时触发 + /// + /// 被移除的设备 + void OnDeviceRemoved(DeviceInfo device); + + /// + /// 当设备状态变化时触发 + /// + /// 状态发生变化的设备 + void OnDeviceStatusChanged(DeviceInfo device); + } +} \ No newline at end of file diff --git a/KitX Script/Kscript.CSharp/Services/Composer.cs b/KitX Script/Kscript.CSharp/Services/Composer.cs index a707c2f..0f1f520 100644 --- a/KitX Script/Kscript.CSharp/Services/Composer.cs +++ b/KitX Script/Kscript.CSharp/Services/Composer.cs @@ -2,207 +2,262 @@ using KitX.Shared.CSharp.Plugin; using KitX.Shared.CSharp.WebCommand; using Kscript.CSharp.Interfaces; +using Kscript.CSharp.Utils; using System.Text.Json; namespace Kscript.CSharp.Services { - public class Composer : IComposer + /// + /// 设备管理器 + /// + public class Composer : IComposer, IDisposable { private readonly Connector _connector; + private readonly DeviceCache _deviceCache; + private readonly DeviceRequestBuilder _requestBuilder; + private readonly HashSet _listeners = new(); + private bool _isDisposed; + + private readonly object _lockObject = new(); + private Dictionary _lastKnownDevices = new(); + + // 设备离线判断阈值(超过这个时间没有收到更新则认为设备离线) + private static readonly TimeSpan DeviceOfflineThreshold = TimeSpan.FromMinutes(2); public Composer(Connector? connector = null) { _connector = connector ?? Connector.Instance; + _deviceCache = new DeviceCache(); + _requestBuilder = new DeviceRequestBuilder(_connector); + } + + /// + /// 添加设备事件监听器 + /// + public void AddListener(IDeviceEventListener listener) + { + if (_isDisposed) + throw new ObjectDisposedException(nameof(Composer)); + + lock (_lockObject) + { + _listeners.Add(listener); + } } - private IEnumerable? _cachedDeviceList; + /// + /// 移除设备事件监听器 + /// + public void RemoveListener(IDeviceEventListener listener) + { + if (_isDisposed) + return; + + lock (_lockObject) + { + _listeners.Remove(listener); + } + } + + /// + /// 获取本地设备 + /// public async Task RequestLocalDevice() { var devices = await RequestDeviceList(); - var localDevice = devices.FirstOrDefault(x => - (x.Device.IPv4?.Equals("127.0.0.1") ?? false) || // Check localhost - (x.Device.IPv4?.Equals("::1") ?? false)); // Check IPv6 localhost - // todo: fixme: 这个检查方案会有问题。改进建议:检查IP地址是否与本地设备的IP地址有交集。因为广播出来的地址可能是本机地址的外网地址 - - return localDevice != null ? new Device(localDevice) : null; + var localDevice = devices.FirstOrDefault(NetworkUtils.IsLocalDevice); + return localDevice != null ? new Device(localDevice, _connector) : null; } + /// + /// 获取主控制器设备 + /// public async Task RequestMainController() { var devices = await RequestDeviceList(); var mainController = devices.FirstOrDefault(x => x.IsMainDevice); - return mainController != null ? new Device(mainController) : null; + return mainController != null ? new Device(mainController, _connector) : null; } + /// + /// 获取随机桌面设备 + /// public async Task RequestRandomDesktopDevice() { var devices = await RequestDeviceList(); var desktopDevices = devices.Where(x => x.DeviceOSType == OperatingSystems.Windows - || x.DeviceOSType == OperatingSystems.MacOS - || x.DeviceOSType == OperatingSystems.Linux).ToList(); - if (desktopDevices.Count == 0) return null; + || x.DeviceOSType == OperatingSystems.MacOS + || x.DeviceOSType == OperatingSystems.Linux) + .ToList(); + + if (!desktopDevices.Any()) + return null; var random = new Random(); var randomDevice = desktopDevices[random.Next(desktopDevices.Count)]; - return new Device(randomDevice); + return new Device(randomDevice, _connector); } + /// + /// 获取随机移动设备 + /// public async Task RequestRandomMobileDevice() { var devices = await RequestDeviceList(); var mobileDevices = devices.Where(x => x.DeviceOSType == OperatingSystems.Android - || x.DeviceOSType == OperatingSystems.IOS).ToList(); - if (mobileDevices.Count == 0) return null; + || x.DeviceOSType == OperatingSystems.IOS) + .ToList(); + if (!mobileDevices.Any()) + return null; + var random = new Random(); var randomDevice = mobileDevices[random.Next(mobileDevices.Count)]; - return new Device(randomDevice); + return new Device(randomDevice, _connector); } + /// + /// 根据过滤器获取设备 + /// public async Task RequestDeviceByFilter(Func filter) { var devices = await RequestDeviceList(); var matchedDevice = devices.FirstOrDefault(filter); - return matchedDevice != null ? new Device(matchedDevice) : null; + return matchedDevice != null ? new Device(matchedDevice, _connector) : null; } + /// + /// 获取用户选择的设备 + /// public async Task RequestUserSelectedDevice(IEnumerable candidates) { - var tcs = new TaskCompletionSource(); - - void OnResponse(Request response) - { - try + var response = await _requestBuilder + .WithFunction("SelectDevice", candidates) + .ExecuteAsync(async response => { - response.Match( - response.GetContent(content => // 这里返回的是用户选择的设备的 MAC 地址 - { - // Parse selected device from response - if (string.IsNullOrEmpty(content)) - { - tcs.SetResult(null); - return content; - } - - var selectedDevice = candidates.FirstOrDefault(d => - d.Device.MacAddress.ToString().Equals(content, StringComparison.OrdinalIgnoreCase)); - tcs.SetResult(selectedDevice); - return content; - }), - matchCommand: ProcessCommandResponse - ); - } - catch (Exception ex) - { - tcs.SetException(ex); - } - } + var result = await ResponseHandler.HandleStringResponse(response); + if (string.IsNullOrEmpty(result)) + return null; - // Send candidates to local device for selection - _connector.Request() - .UpdateCommand(cmd => - { - cmd.FunctionName = "SelectDevice"; - cmd.FunctionArgs = candidates.Select(d => new Parameter { Value = d.ToString() }).ToList(); // todo: 改为传入一个函数用于筛选设备 - return cmd; - }) - .UpdateRequest(req => - { - req.Type = RequestTypes.Command; - req.Version = RequestVersions.V1; - req.Target = null; // Send to local device - return req; - }) - .SetSender(OnResponse) - .Send(); - - var selectedDevice = await tcs.Task; - return selectedDevice != null ? new Device(selectedDevice) : null; + return candidates.FirstOrDefault(d => + d.Device.MacAddress.ToString().Equals(result, StringComparison.OrdinalIgnoreCase)); + }); + + return response != null ? new Device(response, _connector) : null; } + /// + /// 获取设备列表 + /// public async Task> RequestDeviceList() { - if (_cachedDeviceList != null) - return _cachedDeviceList; + return await _deviceCache.GetOrAdd("devices", async () => + { + var response = await _requestBuilder + .WithFunction("GetDeviceList") + .ExecuteAsync(async response => + { + var result = await ResponseHandler.HandlePrefixedResponse>( + response, + "DeviceList:"); + + if (result != null) + UpdateDeviceStates(result); - var tcs = new TaskCompletionSource>(); + return result ?? new List(); + }); - void OnResponse(Request response) + return response; + }); + } + + private void UpdateDeviceStates(IEnumerable newDevices) + { + if (_isDisposed) + return; + + lock (_lockObject) { - try - { - var devices = HandleDeviceListResponse(response); - _cachedDeviceList = devices; - tcs.SetResult(devices); - } - catch (Exception ex) + var newDevicesDict = newDevices.ToDictionary(d => d.Device.MacAddress.ToString()); + + // Find removed devices + var removedDevices = _lastKnownDevices.Keys + .Except(newDevicesDict.Keys) + .Select(key => _lastKnownDevices[key]) + .ToList(); + + // Find added devices + var addedDevices = newDevicesDict.Keys + .Except(_lastKnownDevices.Keys) + .Select(key => newDevicesDict[key]) + .ToList(); + + // Find changed devices + var changedDevices = _lastKnownDevices.Keys + .Intersect(newDevicesDict.Keys) + .Where(key => HasDeviceChanged(_lastKnownDevices[key], newDevicesDict[key])) + .Select(key => newDevicesDict[key]) + .ToList(); + + // Update last known devices + _lastKnownDevices = newDevicesDict; + + // Notify listeners + var listeners = _listeners.ToList(); + foreach (var listener in listeners) { - tcs.SetException(ex); + try + { + foreach (var device in removedDevices) + listener.OnDeviceRemoved(device); + + foreach (var device in addedDevices) + listener.OnDeviceAdded(device); + + foreach (var device in changedDevices) + listener.OnDeviceStatusChanged(device); + + listener.OnDeviceListUpdated(newDevices); + } + catch (Exception ex) + { + // Log error but continue with other listeners + System.Diagnostics.Debug.WriteLine($"Error in device listener: {ex.Message}"); + } } } - - // Request device list from network - _connector.Request() - .UpdateCommand(cmd => - { - cmd.FunctionName = "GetDeviceList"; - return cmd; - }) - .UpdateRequest(req => - { - req.Type = RequestTypes.Command; - req.Version = RequestVersions.V1; - return req; - }) - .SetSender(OnResponse) - .Send(); - - return await tcs.Task; } - private IEnumerable HandleDeviceListResponse(Request response) + private bool HasDeviceChanged(DeviceInfo oldDevice, DeviceInfo newDevice) { - var devices = new List(); - - response.Match( - response.GetContent(content => - { - // Parse device list from content - // This should be replaced with proper deserialization based on response format - // For now returning empty list as placeholder - return content; - }), - matchCommand: content => - { - ProcessCommandResponse(content); - } - ); + // 检查设备在线状态(基于最后一次发送时间) + var oldDeviceOnline = DateTime.UtcNow - oldDevice.SendTime < DeviceOfflineThreshold; + var newDeviceOnline = DateTime.UtcNow - newDevice.SendTime < DeviceOfflineThreshold; - return devices; + if (oldDeviceOnline != newDeviceOnline) + return true; + + // 检查关键属性变化 + return oldDevice.PluginsCount != newDevice.PluginsCount || + oldDevice.Device.IPv4 != newDevice.Device.IPv4 || + oldDevice.Device.IPv6 != newDevice.Device.IPv6 || + oldDevice.PluginsServerPort != newDevice.PluginsServerPort || + oldDevice.DevicesServerPort != newDevice.DevicesServerPort || + oldDevice.IsMainDevice != newDevice.IsMainDevice || + oldDevice.DeviceOSVersion != newDevice.DeviceOSVersion; } - private void ProcessCommandResponse(string content) + public void Dispose() { - if (string.IsNullOrEmpty(content)) + if (_isDisposed) return; - try - { - // Handle different command responses based on content - if (content.StartsWith("DeviceList:")) - { - var deviceListJson = content["DeviceList:".Length..]; - _cachedDeviceList = JsonSerializer.Deserialize>(deviceListJson); - } - else if (content.StartsWith("Error:")) - { - var error = content["Error:".Length..]; - throw new InvalidOperationException($"Command error: {error}"); - } - } - catch (Exception ex) + _isDisposed = true; + _deviceCache.Dispose(); + lock (_lockObject) { - throw new InvalidOperationException($"Failed to process command response: {ex.Message}", ex); + _listeners.Clear(); + _lastKnownDevices.Clear(); } } } diff --git a/KitX Script/Kscript.CSharp/Utils/DeviceCache.cs b/KitX Script/Kscript.CSharp/Utils/DeviceCache.cs new file mode 100644 index 0000000..7598a04 --- /dev/null +++ b/KitX Script/Kscript.CSharp/Utils/DeviceCache.cs @@ -0,0 +1,193 @@ +using System.Collections.Concurrent; +using KitX.Shared.CSharp.Device; + +namespace Kscript.CSharp.Utils +{ + /// + /// 设备信息缓存管理器 + /// + public class DeviceCache : IDisposable + { + private class CacheEntry + { + public T Value { get; set; } + public DateTime Expiration { get; set; } + public DateTime CreatedAt { get; } = DateTime.UtcNow; + private int _accessCount; + + public bool IsExpired => DateTime.UtcNow > Expiration; + + public int GetAccessCount() + { + return Interlocked.CompareExchange(ref _accessCount, 0, 0); + } + + public void IncrementAccess() + { + Interlocked.Increment(ref _accessCount); + } + } + + private readonly ConcurrentDictionary>> _cache = new(); + private readonly TimeSpan _defaultExpiration; + private readonly Timer _cleanupTimer; + private volatile bool _isDisposed; + + /// + /// 初始化设备缓存 + /// + /// 默认的缓存过期时间 + /// 缓存清理的时间间隔 + public DeviceCache( + TimeSpan? defaultExpiration = null, + TimeSpan? cleanupInterval = null) + { + _defaultExpiration = defaultExpiration ?? TimeSpan.FromMinutes(1); + var interval = cleanupInterval ?? TimeSpan.FromMinutes(5); + + _cleanupTimer = new Timer( + CleanupCallback, + null, + interval, + interval + ); + } + + /// + /// 设置缓存项 + /// + public void Set(string key, IEnumerable value, TimeSpan? expiration = null) + { + var entry = new CacheEntry> + { + Value = value, + Expiration = DateTime.UtcNow.Add(expiration ?? _defaultExpiration) + }; + + _cache.AddOrUpdate(key, _ => entry, (_, __) => entry); + } + + /// + /// 尝试获取缓存项 + /// + public IEnumerable? Get(string key) + { + var entry = _cache.GetOrAdd(key, _ => null); + if (entry == null || entry.IsExpired) + { + _cache.TryRemove(key, out _); + return null; + } + + entry.IncrementAccess(); + return entry.Value; + } + + /// + /// 检查缓存项是否存在且有效 + /// + public bool IsValid(string key) + { + var entry = _cache.GetOrAdd(key, _ => null); + return entry != null && !entry.IsExpired; + } + + /// + /// 获取缓存项,如果不存在或已过期则使用提供的函数获取新值 + /// + public async Task> GetOrAdd( + string key, + Func>> valueFactory, + TimeSpan? expiration = null) + { + var existingValue = Get(key); + if (existingValue != null) + return existingValue; + + var newValue = await valueFactory(); + Set(key, newValue, expiration); + return newValue; + } + + /// + /// 移除缓存项 + /// + public bool Remove(string key) + { + return _cache.TryRemove(key, out _); + } + + /// + /// 清除所有缓存 + /// + public void Clear() + { + _cache.Clear(); + } + + /// + /// 获取缓存统计信息 + /// + public CacheStatistics GetStatistics() + { + var now = DateTime.UtcNow; + var entries = _cache.ToArray(); + + return new CacheStatistics + { + TotalItems = entries.Length, + ExpiredItems = entries.Count(x => x.Value.IsExpired), + TotalAccesses = entries.Sum(x => x.Value.GetAccessCount()), + AverageAccessesPerItem = entries.Length > 0 + ? entries.Average(x => x.Value.GetAccessCount()) + : 0, + OldestItemAge = entries.Length > 0 + ? (now - entries.Min(x => x.Value.CreatedAt)) + : TimeSpan.Zero, + AverageItemAge = entries.Length > 0 + ? TimeSpan.FromTicks((long)entries.Average(x => + (now - x.Value.CreatedAt).Ticks)) + : TimeSpan.Zero + }; + } + + private void CleanupCallback(object? state) + { + if (_isDisposed) + return; + + var expiredKeys = _cache + .Where(kvp => kvp.Value.IsExpired) + .Select(kvp => kvp.Key) + .ToList(); + + foreach (var key in expiredKeys) + { + _cache.TryRemove(key, out _); + } + } + + public void Dispose() + { + if (_isDisposed) + return; + + _isDisposed = true; + _cleanupTimer.Dispose(); + Clear(); + } + } + + /// + /// 缓存统计信息 + /// + public class CacheStatistics + { + public int TotalItems { get; set; } + public int ExpiredItems { get; set; } + public int TotalAccesses { get; set; } + public double AverageAccessesPerItem { get; set; } + public TimeSpan OldestItemAge { get; set; } + public TimeSpan AverageItemAge { get; set; } + } +} \ No newline at end of file diff --git a/KitX Script/Kscript.CSharp/Utils/DeviceRequestBuilder.cs b/KitX Script/Kscript.CSharp/Utils/DeviceRequestBuilder.cs new file mode 100644 index 0000000..8ffa998 --- /dev/null +++ b/KitX Script/Kscript.CSharp/Utils/DeviceRequestBuilder.cs @@ -0,0 +1,141 @@ +using System.Text.Json; +using KitX.Shared.CSharp.WebCommand; +using KitX.Shared.CSharp.Device; +using KitX.Shared.CSharp.Plugin; + +namespace Kscript.CSharp.Utils +{ + /// + /// 设备请求构建器 + /// + public class DeviceRequestBuilder + { + private readonly Connector _connector; + private Command _command; + private Request _request; + + public DeviceRequestBuilder(Connector connector) + { + _connector = connector; + _command = new Command(); + _request = new Request + { + Type = RequestTypes.Command, + Version = RequestVersions.V1 + }; + } + + /// + /// 设置请求的函数名和参数 + /// + public DeviceRequestBuilder WithFunction(string name, params object[] args) + { + _command.FunctionName = name; + _command.FunctionArgs = args.Select(a => new Parameter + { + Value = JsonSerializer.Serialize(a), + Type = a.GetType().Name + }).ToList(); + return this; + } + + /// + /// 设置请求的目标设备 + /// + public DeviceRequestBuilder WithTarget(DeviceLocator? target) + { + _request.Target = target; + return this; + } + + /// + /// 设置请求的目标设备信息 + /// + public DeviceRequestBuilder WithTargetDevice(DeviceInfo device) + { + return WithTarget(device.Device); + } + + /// + /// 执行请求并处理响应 + /// + public async Task ExecuteAsync(Func> responseHandler) + { + var tcs = new TaskCompletionSource(); + + void OnResponse(Request response) + { + try + { + var result = responseHandler(response); + result.ContinueWith(t => + { + if (t.IsFaulted) + tcs.SetException(t.Exception ?? new Exception("Unknown error")); + else if (t.IsCanceled) + tcs.SetCanceled(); + else + tcs.SetResult(t.Result); + }); + } + catch (Exception ex) + { + tcs.SetException(ex); + } + } + + _connector.Request() + .UpdateCommand(cmd => + { + cmd.FunctionName = _command.FunctionName; + cmd.FunctionArgs = _command.FunctionArgs; + return cmd; + }) + .UpdateRequest(req => + { + req.Type = _request.Type; + req.Version = _request.Version; + req.Target = _request.Target; + return req; + }) + .SetSender(OnResponse) + .Send(); + + return await tcs.Task; + } + + /// + /// 重置构建器状态 + /// + public DeviceRequestBuilder Reset() + { + _command = new Command(); + _request = new Request + { + Type = RequestTypes.Command, + Version = RequestVersions.V1 + }; + return this; + } + + /// + /// 创建请求克隆 + /// + public DeviceRequestBuilder Clone() + { + var clone = new DeviceRequestBuilder(_connector); + clone._command = new Command + { + FunctionName = _command.FunctionName, + FunctionArgs = _command.FunctionArgs?.ToList() + }; + clone._request = new Request + { + Type = _request.Type, + Version = _request.Version, + Target = _request.Target + }; + return clone; + } + } +} \ No newline at end of file diff --git a/KitX Script/Kscript.CSharp/Utils/NetworkUtils.cs b/KitX Script/Kscript.CSharp/Utils/NetworkUtils.cs new file mode 100644 index 0000000..f5c0eac --- /dev/null +++ b/KitX Script/Kscript.CSharp/Utils/NetworkUtils.cs @@ -0,0 +1,93 @@ +using System.Net.NetworkInformation; +using KitX.Shared.CSharp.Device; + +namespace Kscript.CSharp.Utils +{ + /// + /// 提供网络相关的工具方法 + /// + public static class NetworkUtils + { + /// + /// 检查给定的设备是否是本地设备 + /// + /// 要检查的设备信息 + /// 如果是本地设备返回true,否则返回false + public static bool IsLocalDevice(DeviceInfo device) + { + if (device.Device.IPv4 == null) + return false; + + // 获取本机所有IP地址 + var localIPs = GetLocalIPAddresses(); + + // 检查设备IP是否与本机IP匹配 + return localIPs.Contains(device.Device.IPv4) || + IsLocalNetworkAddress(device.Device.IPv4); + } + + /// + /// 获取本机所有网络接口的IP地址 + /// + private static HashSet GetLocalIPAddresses() + { + return new HashSet( + NetworkInterface.GetAllNetworkInterfaces() + .Where(n => n.OperationalStatus == OperationalStatus.Up) + .SelectMany(n => n.GetIPProperties().UnicastAddresses) + .Select(a => a.Address.ToString()) + ); + } + + /// + /// 检查给定的IP地址是否为本地网络地址 + /// + private static bool IsLocalNetworkAddress(string ip) + { + return ip.StartsWith("127.") || // Loopback + ip.StartsWith("169.254.") || // Link-local + ip.StartsWith("192.168.") || // 私有网络 + ip.StartsWith("10.") || // 私有网络 + ip.StartsWith("172.") || // 私有网络 + ip.Equals("::1"); // IPv6 loopback + } + + /// + /// 检查给定的IP地址是否为私有网络地址 + /// + public static bool IsPrivateNetworkAddress(string ip) + { + return ip.StartsWith("192.168.") || // Class C private network + ip.StartsWith("10.") || // Class A private network + (ip.StartsWith("172.") && // Class B private network + TryGetSecondOctet(ip, out var secondOctet) && + secondOctet >= 16 && secondOctet <= 31); + } + + private static bool TryGetSecondOctet(string ip, out int octet) + { + octet = 0; + var parts = ip.Split('.'); + if (parts.Length < 2) return false; + return int.TryParse(parts[1], out octet); + } + + /// + /// 判断两个IP地址是否在同一子网内 + /// + public static bool IsInSameSubnet(string ip1, string ip2, string subnetMask) + { + var ip1Parts = ip1.Split('.').Select(byte.Parse).ToArray(); + var ip2Parts = ip2.Split('.').Select(byte.Parse).ToArray(); + var maskParts = subnetMask.Split('.').Select(byte.Parse).ToArray(); + + for (int i = 0; i < 4; i++) + { + if ((ip1Parts[i] & maskParts[i]) != (ip2Parts[i] & maskParts[i])) + return false; + } + + return true; + } + } +} \ No newline at end of file diff --git a/KitX Script/Kscript.CSharp/Utils/ResponseHandler.cs b/KitX Script/Kscript.CSharp/Utils/ResponseHandler.cs new file mode 100644 index 0000000..bba69b5 --- /dev/null +++ b/KitX Script/Kscript.CSharp/Utils/ResponseHandler.cs @@ -0,0 +1,124 @@ +using System.Text.Json; +using KitX.Shared.CSharp.WebCommand; + +namespace Kscript.CSharp.Utils +{ + /// + /// 统一处理网络响应的工具类 + /// + public static class ResponseHandler + { + /// + /// 处理网络响应并返回指定类型的结果 + /// + /// 响应结果的类型 + /// 网络响应 + /// 处理响应内容的函数 + /// 处理命令响应的函数 + /// 处理后的结果 + public static async Task HandleResponse( + Request response, + Func contentHandler, + Func commandHandler) + { + return await Task.Run(() => + { + try + { + T? result = default; + response.Match( + response.GetContent(content => + { + result = contentHandler(content); + return content; + }), + matchCommand: command => + { + result = commandHandler(command); + } + ); + return result; + } + catch (Exception ex) + { + throw new InvalidOperationException( + $"Failed to handle response: {ex.Message}", ex); + } + }); + } + + /// + /// 处理JSON响应并反序列化为指定类型 + /// + public static async Task HandleJsonResponse( + Request response, + JsonSerializerOptions? options = null) + { + return await HandleResponse( + response, + content => string.IsNullOrEmpty(content) ? default : + JsonSerializer.Deserialize(content, options), + command => + { + if (command.StartsWith("Error:")) + { + var error = command.Substring("Error:".Length); + throw new InvalidOperationException($"Command error: {error}"); + } + return default; + } + ); + } + + /// + /// 处理带前缀的命令响应 + /// + public static async Task HandlePrefixedResponse( + Request response, + string prefix, + JsonSerializerOptions? options = null) + { + return await HandleResponse( + response, + content => string.IsNullOrEmpty(content) ? default : + JsonSerializer.Deserialize(content, options), + command => + { + if (command.StartsWith("Error:")) + { + var error = command.Substring("Error:".Length); + throw new InvalidOperationException($"Command error: {error}"); + } + else if (command.StartsWith(prefix)) + { + var json = command.Substring(prefix.Length); + return JsonSerializer.Deserialize(json, options); + } + return default; + } + ); + } + + /// + /// 处理简单的字符串响应 + /// + public static async Task HandleStringResponse( + Request response, + bool throwOnError = true) + { + return await HandleResponse( + response, + content => content ?? string.Empty, + command => + { + if (throwOnError && command.StartsWith("Error:")) + { + var error = command.Substring("Error:".Length); + throw new InvalidOperationException($"Command error: {error}"); + } + return command; + } + ); + } + } +} \ No newline at end of file From 090432d8f1b2de1ff1e9bc31a41a826d5b4b8ab9 Mon Sep 17 00:00:00 2001 From: StarInk Date: Tue, 29 Jul 2025 14:38:47 +0800 Subject: [PATCH 08/17] =?UTF-8?q?=F0=9F=92=BE=20Feat(Kscript.CSharp.Parser?= =?UTF-8?q?):=20=E5=88=9B=E5=BB=BA=E5=B9=B6=E5=88=9D=E6=AD=A5=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E4=BB=8E=E6=8F=92=E4=BB=B6=E4=BF=A1=E6=81=AF=E5=88=97?= =?UTF-8?q?=E8=A1=A8Json=E6=96=87=E4=BB=B6=E7=94=9F=E6=88=90=E6=8F=92?= =?UTF-8?q?=E4=BB=B6=E9=9D=99=E6=80=81API=EF=BC=8C=E4=BD=BFC#=E8=84=9A?= =?UTF-8?q?=E6=9C=AC=E8=83=BD=E6=97=A0=E6=84=9F=E8=B0=83=E7=94=A8=E6=8F=92?= =?UTF-8?q?=E4=BB=B6=E5=8F=8A=E5=85=B6=E5=8A=9F=E8=83=BD=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CodeGen/AssemblyCache.cs | 242 +++++++++++++++ .../CodeGen/MethodEmitter.cs | 267 ++++++++++++++++ .../CodeGen/TypeMapper.cs | 212 +++++++++++++ .../Core/IPluginManager.cs | 38 +++ .../Core/MockPluginManager.cs | 140 +++++++++ .../Examples/BasicUsageExample.cs | 235 ++++++++++++++ .../Kscript.CSharp.Parser/Examples/Program.cs | 39 +++ .../Exceptions/ParserException.cs | 43 +++ .../Kscript.CSharp.Parser/GlobalUsings.cs | 6 + .../Kscript.CSharp.Parser.csproj | 21 ++ .../Kscript.CSharp.Parser.sln | 24 ++ .../Kscript.CSharp.Parser/LLMDocs/InitPlan.md | 76 +++++ .../Models/PluginCallInfo.cs | 44 +++ KitX Script/Kscript.CSharp.Parser/Parser.cs | 271 ++++++++++++++++ KitX Script/Kscript.CSharp.Parser/README.md | 202 ++++++++++++ KitX Script/Kscript.CSharp.Parser/USAGE.md | 292 ++++++++++++++++++ .../Kscript.CSharp.Parser/example.json | 154 +++++++++ KitX Script/Kscript.CSharp/Kscript.CSharp.sln | 24 ++ 18 files changed, 2330 insertions(+) create mode 100644 KitX Script/Kscript.CSharp.Parser/CodeGen/AssemblyCache.cs create mode 100644 KitX Script/Kscript.CSharp.Parser/CodeGen/MethodEmitter.cs create mode 100644 KitX Script/Kscript.CSharp.Parser/CodeGen/TypeMapper.cs create mode 100644 KitX Script/Kscript.CSharp.Parser/Core/IPluginManager.cs create mode 100644 KitX Script/Kscript.CSharp.Parser/Core/MockPluginManager.cs create mode 100644 KitX Script/Kscript.CSharp.Parser/Examples/BasicUsageExample.cs create mode 100644 KitX Script/Kscript.CSharp.Parser/Examples/Program.cs create mode 100644 KitX Script/Kscript.CSharp.Parser/Exceptions/ParserException.cs create mode 100644 KitX Script/Kscript.CSharp.Parser/GlobalUsings.cs create mode 100644 KitX Script/Kscript.CSharp.Parser/Kscript.CSharp.Parser.csproj create mode 100644 KitX Script/Kscript.CSharp.Parser/Kscript.CSharp.Parser.sln create mode 100644 KitX Script/Kscript.CSharp.Parser/LLMDocs/InitPlan.md create mode 100644 KitX Script/Kscript.CSharp.Parser/Models/PluginCallInfo.cs create mode 100644 KitX Script/Kscript.CSharp.Parser/Parser.cs create mode 100644 KitX Script/Kscript.CSharp.Parser/README.md create mode 100644 KitX Script/Kscript.CSharp.Parser/USAGE.md create mode 100644 KitX Script/Kscript.CSharp.Parser/example.json create mode 100644 KitX Script/Kscript.CSharp/Kscript.CSharp.sln diff --git a/KitX Script/Kscript.CSharp.Parser/CodeGen/AssemblyCache.cs b/KitX Script/Kscript.CSharp.Parser/CodeGen/AssemblyCache.cs new file mode 100644 index 0000000..26e4ce0 --- /dev/null +++ b/KitX Script/Kscript.CSharp.Parser/CodeGen/AssemblyCache.cs @@ -0,0 +1,242 @@ +using System.Collections.Concurrent; +using System.Security.Cryptography; +using System.Text; +using System.Text.Json; + +namespace Kscript.CSharp.Parser.CodeGen; + +/// +/// 程序集缓存管理器,避免重复生成相同的程序集 +/// +public static class AssemblyCache +{ + /// + /// 程序集缓存字典,Key 为插件清单的哈希值,Value 为生成的程序集 + /// + private static readonly ConcurrentDictionary _assemblyCache = new(); + + /// + /// 程序集引用计数,用于清理不再使用的程序集 + /// + private static readonly ConcurrentDictionary _referenceCount = new(); + + /// + /// 缓存锁,确保并发安全 + /// + private static readonly object _cacheLock = new(); + + /// + /// 获取或生成程序集 + /// + /// 插件信息列表 + /// 程序集名称 + /// 插件管理器实例 + /// 缓存的或新生成的程序集 + public static Assembly GetOrCreateAssembly(List plugins, string assemblyName = "DynamicPluginAssembly", Core.IPluginManager? pluginManager = null) + { + var cacheKey = GenerateCacheKey(plugins, assemblyName); + + // 尝试从缓存获取 + if (_assemblyCache.TryGetValue(cacheKey, out var cachedAssembly)) + { + IncrementReference(cacheKey); + return cachedAssembly; + } + + lock (_cacheLock) + { + // 双重检查锁定模式 + if (_assemblyCache.TryGetValue(cacheKey, out cachedAssembly)) + { + IncrementReference(cacheKey); + return cachedAssembly; + } + + // 生成新的程序集 + var newAssembly = MethodEmitter.GenerateAssembly(plugins, assemblyName, pluginManager); + + // 添加到缓存 + _assemblyCache[cacheKey] = newAssembly; + _referenceCount[cacheKey] = 1; + + return newAssembly; + } + } + + /// + /// 生成插件清单的缓存键 + /// + /// 插件信息列表 + /// 程序集名称 + /// 缓存键字符串 + private static string GenerateCacheKey(List plugins, string assemblyName) + { + try + { + // 创建用于哈希的数据对象 + var cacheData = new + { + AssemblyName = assemblyName, + Plugins = plugins.Select(p => new + { + p.Name, + p.Version, + Functions = p.Functions.Select(f => new + { + f.Name, + f.ReturnValueType, + Parameters = f.Parameters.Select(param => new + { + param.Name, + param.Type, + param.IsOptional, + param.Value + }).OrderBy(param => param.Name) + }).OrderBy(f => f.Name) + }).OrderBy(p => p.Name) + }; + + // 序列化为JSON + var json = JsonSerializer.Serialize(cacheData, new JsonSerializerOptions + { + WriteIndented = false, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }); + + // 计算SHA256哈希 + using var sha256 = SHA256.Create(); + var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(json)); + return Convert.ToHexString(hashBytes); + } + catch (Exception ex) + { + // 如果哈希计算失败,使用基础信息生成简单键 + var fallbackKey = $"{assemblyName}_{plugins.Count}_{string.Join("_", plugins.Select(p => $"{p.Name}_{p.Functions.Count}"))}_{DateTime.UtcNow.Ticks}"; + Console.WriteLine($"[AssemblyCache] 哈希计算失败,使用回退键: {ex.Message}"); + return fallbackKey; + } + } + + /// + /// 增加引用计数 + /// + /// 缓存键 + private static void IncrementReference(string cacheKey) + { + _referenceCount.AddOrUpdate(cacheKey, 1, (key, count) => count + 1); + } + + /// + /// 减少引用计数 + /// + /// 缓存键 + public static void DecrementReference(string cacheKey) + { + if (_referenceCount.TryGetValue(cacheKey, out var count)) + { + var newCount = count - 1; + if (newCount <= 0) + { + // 引用计数为0,从缓存中移除 + _assemblyCache.TryRemove(cacheKey, out _); + _referenceCount.TryRemove(cacheKey, out _); + } + else + { + _referenceCount[cacheKey] = newCount; + } + } + } + + /// + /// 清除所有缓存 + /// + public static void ClearCache() + { + lock (_cacheLock) + { + _assemblyCache.Clear(); + _referenceCount.Clear(); + } + } + + /// + /// 获取缓存统计信息 + /// + /// 缓存统计信息 + public static CacheStatistics GetStatistics() + { + return new CacheStatistics + { + CachedAssemblyCount = _assemblyCache.Count, + TotalReferences = _referenceCount.Values.Sum(), + CacheKeys = _assemblyCache.Keys.ToList() + }; + } + + /// + /// 检查是否存在缓存 + /// + /// 插件信息列表 + /// 程序集名称 + /// 是否存在缓存 + public static bool HasCache(List plugins, string assemblyName = "DynamicPluginAssembly") + { + var cacheKey = GenerateCacheKey(plugins, assemblyName); + return _assemblyCache.ContainsKey(cacheKey); + } + + /// + /// 强制重新生成程序集(绕过缓存) + /// + /// 插件信息列表 + /// 程序集名称 + /// 插件管理器实例 + /// 新生成的程序集 + public static Assembly ForceRegenerate(List plugins, string assemblyName = "DynamicPluginAssembly", Core.IPluginManager? pluginManager = null) + { + var cacheKey = GenerateCacheKey(plugins, assemblyName); + + lock (_cacheLock) + { + // 移除现有缓存 + _assemblyCache.TryRemove(cacheKey, out _); + _referenceCount.TryRemove(cacheKey, out _); + + // 生成新的程序集 + var newAssembly = MethodEmitter.GenerateAssembly(plugins, assemblyName, pluginManager); + + // 添加到缓存 + _assemblyCache[cacheKey] = newAssembly; + _referenceCount[cacheKey] = 1; + + return newAssembly; + } + } +} + +/// +/// 缓存统计信息 +/// +public class CacheStatistics +{ + /// + /// 缓存的程序集数量 + /// + public int CachedAssemblyCount { get; set; } + + /// + /// 总引用数量 + /// + public int TotalReferences { get; set; } + + /// + /// 缓存键列表 + /// + public List CacheKeys { get; set; } = new(); + + public override string ToString() + { + return $"缓存统计: {CachedAssemblyCount} 个程序集, {TotalReferences} 个引用"; + } +} diff --git a/KitX Script/Kscript.CSharp.Parser/CodeGen/MethodEmitter.cs b/KitX Script/Kscript.CSharp.Parser/CodeGen/MethodEmitter.cs new file mode 100644 index 0000000..f4921fd --- /dev/null +++ b/KitX Script/Kscript.CSharp.Parser/CodeGen/MethodEmitter.cs @@ -0,0 +1,267 @@ +using Kscript.CSharp.Parser.Core; +using Kscript.CSharp.Parser.Exceptions; +using Kscript.CSharp.Parser.Models; + +namespace Kscript.CSharp.Parser.CodeGen; + +/// +/// IL 方法生成器,负责生成插件调用的静态方法 +/// +public static class MethodEmitter +{ + /// + /// 为插件生成动态程序集 + /// + /// 插件信息列表 + /// 程序集名称 + /// 插件管理器实例 + /// 生成的动态程序集 + public static Assembly GenerateAssembly(List plugins, string assemblyName = "DynamicPluginAssembly", IPluginManager? pluginManager = null) + { + try + { + // 创建动态程序集 + var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly( + new AssemblyName(assemblyName), + AssemblyBuilderAccess.Run); + + var moduleBuilder = assemblyBuilder.DefineDynamicModule($"{assemblyName}.dll"); + + // 为每个插件生成静态类 + foreach (var plugin in plugins) + { + GeneratePluginClass(moduleBuilder, plugin, pluginManager); + } + + return assemblyBuilder; + } + catch (Exception ex) + { + throw ParserException.AssemblyGenerationError(assemblyName, ex); + } + } + + /// + /// 为单个插件生成静态类 + /// + private static void GeneratePluginClass(ModuleBuilder moduleBuilder, PluginInfo plugin, IPluginManager? pluginManager) + { + try + { + // 创建静态类 + var typeBuilder = moduleBuilder.DefineType( + plugin.Name, + TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.Abstract | TypeAttributes.Sealed); + + // 添加插件管理器字段 + var pluginManagerField = typeBuilder.DefineField( + "_pluginManager", + typeof(IPluginManager), + FieldAttributes.Private | FieldAttributes.Static); + + // 生成静态构造函数来初始化插件管理器 + GenerateStaticConstructor(typeBuilder, pluginManagerField, pluginManager); + + // 为每个功能生成静态方法 + foreach (var function in plugin.Functions) + { + GeneratePluginMethod(typeBuilder, plugin.Name, function, pluginManagerField); + } + + // 创建类型 + typeBuilder.CreateType(); + } + catch (Exception ex) + { + throw ParserException.ILGenerationError($"{plugin.Name} 类", ex); + } + } + + /// + /// 生成静态构造函数 + /// + private static void GenerateStaticConstructor(TypeBuilder typeBuilder, FieldBuilder pluginManagerField, IPluginManager? pluginManager) + { + var constructorBuilder = typeBuilder.DefineConstructor( + MethodAttributes.Private | MethodAttributes.Static | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, + CallingConventions.Standard, + Type.EmptyTypes); + + var il = constructorBuilder.GetILGenerator(); + + if (pluginManager != null) + { + // 如果提供了插件管理器实例,直接使用 + il.Emit(OpCodes.Ldtoken, pluginManager.GetType()); + il.Emit(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle")!); + il.Emit(OpCodes.Call, typeof(Activator).GetMethod("CreateInstance", new[] { typeof(Type) })!); + il.Emit(OpCodes.Castclass, typeof(IPluginManager)); + } + else + { + // 使用默认的 MockPluginManager + il.Emit(OpCodes.Newobj, typeof(MockPluginManager).GetConstructor(Type.EmptyTypes)!); + } + + il.Emit(OpCodes.Stsfld, pluginManagerField); + il.Emit(OpCodes.Ret); + } + + /// + /// 为插件功能生成静态方法 + /// + private static void GeneratePluginMethod(TypeBuilder typeBuilder, string pluginName, Function function, FieldBuilder pluginManagerField) + { + try + { + // 解析参数类型 + var parameterTypes = function.Parameters + .Select(p => TypeMapper.MapType(p.Type)) + .ToArray(); + + // 解析返回值类型 + var returnType = TypeMapper.MapType(function.ReturnValueType); + + // 定义方法 + var methodBuilder = typeBuilder.DefineMethod( + function.Name, + MethodAttributes.Public | MethodAttributes.Static, + returnType, + parameterTypes); + + // 设置参数名称 + for (int i = 0; i < function.Parameters.Count; i++) + { + var param = function.Parameters[i]; + var paramBuilder = methodBuilder.DefineParameter(i + 1, ParameterAttributes.None, param.Name); + + // 如果参数是可选的,设置默认值 + if (param.IsOptional && !string.IsNullOrEmpty(param.Value)) + { + var defaultValue = ConvertDefaultValue(param.Value, parameterTypes[i]); + if (defaultValue != null) + { + paramBuilder.SetConstant(defaultValue); + } + } + } + + // 生成方法体IL代码 + GenerateMethodBody(methodBuilder, pluginName, function, parameterTypes, returnType, pluginManagerField); + } + catch (Exception ex) + { + throw ParserException.ILGenerationError($"{pluginName}.{function.Name}", ex); + } + } + + /// + /// 生成方法体IL代码 + /// + private static void GenerateMethodBody(MethodBuilder methodBuilder, string pluginName, Function function, + Type[] parameterTypes, Type returnType, FieldBuilder pluginManagerField) + { + var il = methodBuilder.GetILGenerator(); + + // 声明局部变量 + var callInfoLocal = il.DeclareLocal(typeof(PluginCallInfo)); + var parametersArrayLocal = il.DeclareLocal(typeof(object[])); + var parameterTypesArrayLocal = il.DeclareLocal(typeof(Type[])); + LocalBuilder? resultLocal = null; + + if (returnType != typeof(void)) + { + resultLocal = il.DeclareLocal(returnType); + } + + // 创建参数数组 + il.Emit(OpCodes.Ldc_I4, parameterTypes.Length); + il.Emit(OpCodes.Newarr, typeof(object)); + il.Emit(OpCodes.Stloc, parametersArrayLocal); + + // 创建参数类型数组 + il.Emit(OpCodes.Ldc_I4, parameterTypes.Length); + il.Emit(OpCodes.Newarr, typeof(Type)); + il.Emit(OpCodes.Stloc, parameterTypesArrayLocal); + + // 填充参数数组和类型数组 + for (int i = 0; i < parameterTypes.Length; i++) + { + // 填充参数值 + il.Emit(OpCodes.Ldloc, parametersArrayLocal); + il.Emit(OpCodes.Ldc_I4, i); + il.Emit(OpCodes.Ldarg, i); + + // 如果是值类型,需要装箱 + if (parameterTypes[i].IsValueType) + { + il.Emit(OpCodes.Box, parameterTypes[i]); + } + + il.Emit(OpCodes.Stelem_Ref); + + // 填充参数类型 + il.Emit(OpCodes.Ldloc, parameterTypesArrayLocal); + il.Emit(OpCodes.Ldc_I4, i); + il.Emit(OpCodes.Ldtoken, parameterTypes[i]); + il.Emit(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle")!); + il.Emit(OpCodes.Stelem_Ref); + } + + // 创建 PluginCallInfo 实例 + il.Emit(OpCodes.Ldstr, pluginName); + il.Emit(OpCodes.Ldstr, function.Name); + il.Emit(OpCodes.Ldloc, parametersArrayLocal); + il.Emit(OpCodes.Ldloc, parameterTypesArrayLocal); + il.Emit(OpCodes.Newobj, typeof(PluginCallInfo).GetConstructor(new[] + { + typeof(string), typeof(string), typeof(object[]), typeof(Type[]) + })!); + il.Emit(OpCodes.Stloc, callInfoLocal); + + // 调用插件管理器 + il.Emit(OpCodes.Ldsfld, pluginManagerField); + il.Emit(OpCodes.Ldloc, callInfoLocal); + + if (returnType == typeof(void)) + { + // 调用无返回值的方法 + var voidCallMethod = typeof(IPluginManager).GetMethods() + .First(m => m.Name == "Call" && !m.IsGenericMethod && m.GetParameters().Length == 1); + il.Emit(OpCodes.Callvirt, voidCallMethod); + } + else + { + // 调用有返回值的泛型方法 + var genericCallMethod = typeof(IPluginManager).GetMethods() + .First(m => m.Name == "Call" && m.IsGenericMethod && m.GetParameters().Length == 1) + .MakeGenericMethod(returnType); + il.Emit(OpCodes.Callvirt, genericCallMethod); + il.Emit(OpCodes.Stloc, resultLocal!); + il.Emit(OpCodes.Ldloc, resultLocal!); + } + + il.Emit(OpCodes.Ret); + } + + /// + /// 转换默认值 + /// + private static object? ConvertDefaultValue(string value, Type targetType) + { + try + { + if (targetType == typeof(string)) + return value; + + if (string.IsNullOrEmpty(value)) + return null; + + return Convert.ChangeType(value, targetType); + } + catch + { + return null; + } + } +} diff --git a/KitX Script/Kscript.CSharp.Parser/CodeGen/TypeMapper.cs b/KitX Script/Kscript.CSharp.Parser/CodeGen/TypeMapper.cs new file mode 100644 index 0000000..061a31f --- /dev/null +++ b/KitX Script/Kscript.CSharp.Parser/CodeGen/TypeMapper.cs @@ -0,0 +1,212 @@ +using System.Collections.Concurrent; +using System.Text.RegularExpressions; + +namespace Kscript.CSharp.Parser.CodeGen; + +/// +/// 类型映射器,将字符串类型名映射到 System.Type +/// +public static class TypeMapper +{ + /// + /// 基础类型映射表 + /// + private static readonly Dictionary _basicTypeMap = new() + { + { "void", typeof(void) }, + { "bool", typeof(bool) }, + { "byte", typeof(byte) }, + { "sbyte", typeof(sbyte) }, + { "short", typeof(short) }, + { "ushort", typeof(ushort) }, + { "int", typeof(int) }, + { "uint", typeof(uint) }, + { "long", typeof(long) }, + { "ulong", typeof(ulong) }, + { "float", typeof(float) }, + { "double", typeof(double) }, + { "decimal", typeof(decimal) }, + { "char", typeof(char) }, + { "string", typeof(string) }, + { "object", typeof(object) } + }; + + /// + /// 自定义类型映射表 + /// + private static readonly ConcurrentDictionary _customTypeMap = new(); + + /// + /// 类型缓存 + /// + private static readonly ConcurrentDictionary _typeCache = new(); + + /// + /// 泛型类型正则表达式 + /// + private static readonly Regex _genericTypeRegex = new(@"^(\w+)<(.+)>$", RegexOptions.Compiled); + + /// + /// 映射字符串类型名到 System.Type + /// + /// 类型名字符串 + /// 对应的 Type 对象,如果找不到则返回 typeof(object) + public static Type MapType(string typeName) + { + if (string.IsNullOrWhiteSpace(typeName)) + return typeof(object); + + // 先检查缓存 + if (_typeCache.TryGetValue(typeName, out var cachedType)) + return cachedType; + + Type? resolvedType = null; + + try + { + // 1. 检查基础类型 + if (_basicTypeMap.TryGetValue(typeName, out resolvedType)) + { + _typeCache[typeName] = resolvedType; + return resolvedType; + } + + // 2. 检查自定义映射 + if (_customTypeMap.TryGetValue(typeName, out resolvedType)) + { + _typeCache[typeName] = resolvedType; + return resolvedType; + } + + // 3. 处理泛型类型 + var genericMatch = _genericTypeRegex.Match(typeName); + if (genericMatch.Success) + { + resolvedType = ResolveGenericType(genericMatch); + if (resolvedType != null) + { + _typeCache[typeName] = resolvedType; + return resolvedType; + } + } + + // 4. 尝试直接解析类型 + resolvedType = Type.GetType(typeName); + if (resolvedType != null) + { + _typeCache[typeName] = resolvedType; + return resolvedType; + } + + // 5. 回退到 object 类型 + resolvedType = typeof(object); + _typeCache[typeName] = resolvedType; + return resolvedType; + } + catch + { + // 发生异常时回退到 object 类型 + resolvedType = typeof(object); + _typeCache[typeName] = resolvedType; + return resolvedType; + } + } + + /// + /// 解析泛型类型 + /// + private static Type? ResolveGenericType(Match genericMatch) + { + var genericTypeName = genericMatch.Groups[1].Value; + var genericArgs = genericMatch.Groups[2].Value; + + // 解析泛型参数 + var argTypes = ParseGenericArguments(genericArgs); + if (argTypes.Length == 0) + return null; + + // 处理常见的泛型类型 + return genericTypeName switch + { + "List" => typeof(List<>).MakeGenericType(argTypes), + "Dictionary" when argTypes.Length == 2 => typeof(Dictionary<,>).MakeGenericType(argTypes), + "Array" => argTypes[0].MakeArrayType(), + "Nullable" => typeof(Nullable<>).MakeGenericType(argTypes), + _ => null + }; + } + + /// + /// 解析泛型参数 + /// + private static Type[] ParseGenericArguments(string argsString) + { + var args = SplitGenericArguments(argsString); + return args.Select(MapType).ToArray(); + } + + /// + /// 分割泛型参数字符串,处理嵌套泛型 + /// + private static string[] SplitGenericArguments(string argsString) + { + var args = new List(); + var current = ""; + var depth = 0; + + foreach (char c in argsString) + { + if (c == '<') + { + depth++; + current += c; + } + else if (c == '>') + { + depth--; + current += c; + } + else if (c == ',' && depth == 0) + { + args.Add(current.Trim()); + current = ""; + } + else + { + current += c; + } + } + + if (!string.IsNullOrEmpty(current)) + args.Add(current.Trim()); + + return args.ToArray(); + } + + /// + /// 注册自定义类型映射 + /// + /// 类型名字符串 + /// 对应的 Type 对象 + public static void RegisterCustomType(string typeName, Type type) + { + _customTypeMap[typeName] = type; + _typeCache.TryRemove(typeName, out _); // 清除缓存 + } + + /// + /// 清除类型缓存 + /// + public static void ClearCache() + { + _typeCache.Clear(); + } + + /// + /// 获取所有已注册的自定义类型 + /// + public static IReadOnlyDictionary GetCustomTypes() + { + return _customTypeMap.AsReadOnly(); + } +} diff --git a/KitX Script/Kscript.CSharp.Parser/Core/IPluginManager.cs b/KitX Script/Kscript.CSharp.Parser/Core/IPluginManager.cs new file mode 100644 index 0000000..e66e9ea --- /dev/null +++ b/KitX Script/Kscript.CSharp.Parser/Core/IPluginManager.cs @@ -0,0 +1,38 @@ +using Kscript.CSharp.Parser.Models; + +namespace Kscript.CSharp.Parser.Core; + +/// +/// 插件管理器接口 - 模拟接口,实际实现由外部项目提供 +/// +public interface IPluginManager +{ + /// + /// 调用插件方法 + /// + /// 返回值类型 + /// 调用信息 + /// 插件方法的返回值 + T Call(PluginCallInfo callInfo); + + /// + /// 调用插件方法(无返回值) + /// + /// 调用信息 + void Call(PluginCallInfo callInfo); + + /// + /// 检查插件是否存在 + /// + /// 插件名称 + /// 插件是否存在 + bool IsPluginExists(string pluginName); + + /// + /// 检查插件方法是否存在 + /// + /// 插件名称 + /// 方法名称 + /// 方法是否存在 + bool IsMethodExists(string pluginName, string methodName); +} diff --git a/KitX Script/Kscript.CSharp.Parser/Core/MockPluginManager.cs b/KitX Script/Kscript.CSharp.Parser/Core/MockPluginManager.cs new file mode 100644 index 0000000..083c602 --- /dev/null +++ b/KitX Script/Kscript.CSharp.Parser/Core/MockPluginManager.cs @@ -0,0 +1,140 @@ +using Kscript.CSharp.Parser.Models; + +namespace Kscript.CSharp.Parser.Core; + +/// +/// 模拟插件管理器 - 用于调试和测试 +/// +public class MockPluginManager : IPluginManager +{ + /// + /// 模拟的插件数据,用于验证调用 + /// + private readonly Dictionary> _mockData = new() + { + { + "SampleCalculator", new Dictionary + { + { "Add", (int a, int b) => a + b }, + { "Divide", (double numerator, double denominator, int decimals = 2) => Math.Round(numerator / denominator, decimals) } + } + }, + { + "StringToolkit", new Dictionary + { + { "Reverse", (string text) => new string(text.Reverse().ToArray()) }, + { "ToUpper", (string text) => text.ToUpperInvariant() } + } + } + }; + + /// + /// 调用插件方法 + /// + public T Call(PluginCallInfo callInfo) + { + Console.WriteLine($"[MockPluginManager] 调用插件方法: {callInfo}"); + Console.WriteLine($"[MockPluginManager] 参数类型: [{string.Join(", ", callInfo.ParameterTypes.Select(t => t.Name))}]"); + Console.WriteLine($"[MockPluginManager] 期望返回类型: {typeof(T).Name}"); + + // 简单的模拟实现 + if (_mockData.TryGetValue(callInfo.PluginName, out var plugin) && + plugin.TryGetValue(callInfo.MethodName, out var method)) + { + try + { + var result = InvokeMockMethod(callInfo, method); + Console.WriteLine($"[MockPluginManager] 返回结果: {result}"); + + if (result is T typedResult) + return typedResult; + + // 尝试类型转换 + if (result != null && typeof(T) != typeof(void)) + { + var convertedResult = Convert.ChangeType(result, typeof(T)); + return (T)convertedResult; + } + } + catch (Exception ex) + { + Console.WriteLine($"[MockPluginManager] 调用异常: {ex.Message}"); + } + } + else + { + Console.WriteLine($"[MockPluginManager] 未找到插件方法: {callInfo.PluginName}.{callInfo.MethodName}"); + } + + // 返回默认值 + if (typeof(T) == typeof(void)) + return default(T)!; + + return (T)GetDefaultValue(typeof(T))!; + } + + /// + /// 调用插件方法(无返回值) + /// + public void Call(PluginCallInfo callInfo) + { + Call(callInfo); + } + + /// + /// 检查插件是否存在 + /// + public bool IsPluginExists(string pluginName) + { + var exists = _mockData.ContainsKey(pluginName); + Console.WriteLine($"[MockPluginManager] 检查插件 '{pluginName}' 是否存在: {exists}"); + return exists; + } + + /// + /// 检查插件方法是否存在 + /// + public bool IsMethodExists(string pluginName, string methodName) + { + var exists = _mockData.TryGetValue(pluginName, out var plugin) && + plugin.ContainsKey(methodName); + Console.WriteLine($"[MockPluginManager] 检查方法 '{pluginName}.{methodName}' 是否存在: {exists}"); + return exists; + } + + /// + /// 调用模拟方法 + /// + private object? InvokeMockMethod(PluginCallInfo callInfo, object method) + { + // 根据插件和方法名进行简单的模拟计算 + return (callInfo.PluginName, callInfo.MethodName) switch + { + ("SampleCalculator", "Add") when callInfo.Parameters.Length >= 2 => + Convert.ToInt32(callInfo.Parameters[0]) + Convert.ToInt32(callInfo.Parameters[1]), + + ("SampleCalculator", "Divide") when callInfo.Parameters.Length >= 2 => + callInfo.Parameters.Length >= 3 + ? Math.Round(Convert.ToDouble(callInfo.Parameters[0]) / Convert.ToDouble(callInfo.Parameters[1]), Convert.ToInt32(callInfo.Parameters[2])) + : Math.Round(Convert.ToDouble(callInfo.Parameters[0]) / Convert.ToDouble(callInfo.Parameters[1]), 2), + + ("StringToolkit", "Reverse") when callInfo.Parameters.Length >= 1 => + new string(callInfo.Parameters[0].ToString()?.Reverse().ToArray() ?? Array.Empty()), + + ("StringToolkit", "ToUpper") when callInfo.Parameters.Length >= 1 => + callInfo.Parameters[0].ToString()?.ToUpperInvariant() ?? string.Empty, + + _ => $"MockResult_{callInfo.MethodName}" + }; + } + + /// + /// 获取类型的默认值 + /// + private static object? GetDefaultValue(Type type) + { + if (type.IsValueType) + return Activator.CreateInstance(type); + return null; + } +} diff --git a/KitX Script/Kscript.CSharp.Parser/Examples/BasicUsageExample.cs b/KitX Script/Kscript.CSharp.Parser/Examples/BasicUsageExample.cs new file mode 100644 index 0000000..f2e6fdc --- /dev/null +++ b/KitX Script/Kscript.CSharp.Parser/Examples/BasicUsageExample.cs @@ -0,0 +1,235 @@ +using System.Text.Json; +using Kscript.CSharp.Parser; +using Kscript.CSharp.Parser.Core; + +namespace Kscript.CSharp.Parser.Examples; + +/// +/// 基础用法示例 +/// +public static class BasicUsageExample +{ + /// + /// 演示基本功能 + /// + public static async Task RunExample() + { + Console.WriteLine("=== KitX.CSharp.Parser 基础用法示例 ===\n"); + + try + { + // 1. 从现有的 example.json 文件加载插件清单 + Console.WriteLine("1. 加载插件清单..."); + var exampleJsonPath = Path.Combine(".", "example.json"); + if (!File.Exists(exampleJsonPath)) + { + Console.WriteLine($" 警告: example.json 文件不存在于 {exampleJsonPath}"); + Console.WriteLine(" 将使用内置示例数据"); + RunWithBuiltInData(); + return; + } + + var assembly = await Parser.GenerateFromFileAsync(exampleJsonPath, "ExamplePluginAssembly"); + Console.WriteLine($" ✓ 成功生成程序集: {assembly.FullName}"); + + // 2. 获取生成的插件类型 + Console.WriteLine("\n2. 分析生成的插件类型..."); + var pluginTypes = Parser.GetPluginTypes(assembly); + Console.WriteLine($" 发现 {pluginTypes.Count} 个插件类:"); + + foreach (var type in pluginTypes) + { + Console.WriteLine($" - {type.Name}"); + var methods = Parser.GetPluginMethods(type); + foreach (var method in methods) + { + var parameters = string.Join(", ", method.GetParameters().Select(p => $"{p.ParameterType.Name} {p.Name}")); + Console.WriteLine($" └── {method.ReturnType.Name} {method.Name}({parameters})"); + } + } + + // 3. 动态调用插件方法 + Console.WriteLine("\n3. 动态调用插件方法..."); + DynamicInvokeExample(assembly); + + // 4. 缓存统计 + Console.WriteLine("\n4. 缓存统计信息..."); + var stats = Parser.GetCacheStatistics(); + Console.WriteLine($" {stats}"); + + // 5. 演示缓存效果 + Console.WriteLine("\n5. 测试缓存效果..."); + var assembly2 = await Parser.GenerateFromFileAsync(exampleJsonPath, "ExamplePluginAssembly"); + Console.WriteLine($" 第二次生成是否使用缓存: {assembly == assembly2}"); + + } + catch (Exception ex) + { + Console.WriteLine($"❌ 示例执行失败: {ex.Message}"); + Console.WriteLine($"详细信息: {ex}"); + } + } + + /// + /// 使用内置数据运行示例 + /// + private static void RunWithBuiltInData() + { + // 创建示例插件数据 + var plugins = new List + { + new PluginInfo + { + Name = "TestCalculator", + Version = "1.0.0", + Functions = new List + { + new Function + { + Name = "Add", + ReturnValueType = "int", + Parameters = new List + { + new Parameter { Name = "a", Type = "int", IsOptional = false }, + new Parameter { Name = "b", Type = "int", IsOptional = false } + } + }, + new Function + { + Name = "Multiply", + ReturnValueType = "double", + Parameters = new List + { + new Parameter { Name = "x", Type = "double", IsOptional = false }, + new Parameter { Name = "y", Type = "double", IsOptional = false } + } + } + } + } + }; + + Console.WriteLine(" 使用内置示例数据..."); + var assembly = Parser.Generate(plugins, "BuiltInExampleAssembly"); + Console.WriteLine($" ✓ 成功生成程序集: {assembly.FullName}"); + + DynamicInvokeExample(assembly); + } + + /// + /// 动态调用示例 + /// + private static void DynamicInvokeExample(Assembly assembly) + { + var pluginTypes = Parser.GetPluginTypes(assembly); + + foreach (var type in pluginTypes) + { + Console.WriteLine($"\n 调用插件: {type.Name}"); + var methods = Parser.GetPluginMethods(type); + + foreach (var method in methods) + { + try + { + object? result = null; + var parameters = method.GetParameters(); + + // 根据方法名和参数生成测试数据 + object[] args = GenerateTestArguments(method.Name, parameters); + + Console.WriteLine($" 调用 {method.Name}({string.Join(", ", args)})"); + + // 动态调用方法 + result = method.Invoke(null, args); + + Console.WriteLine($" → 结果: {result}"); + } + catch (Exception ex) + { + Console.WriteLine($" → 调用失败: {ex.Message}"); + } + } + } + } + + /// + /// 生成测试参数 + /// + private static object[] GenerateTestArguments(string methodName, ParameterInfo[] parameters) + { + var args = new object[parameters.Length]; + + for (int i = 0; i < parameters.Length; i++) + { + var param = parameters[i]; + + // 根据参数类型和方法名生成合适的测试值 + args[i] = param.ParameterType.Name switch + { + "Int32" => methodName switch + { + "Add" => i == 0 ? 10 : 20, + "Divide" when param.Name == "decimals" => 2, + _ => i + 1 + }, + "Double" => methodName switch + { + "Divide" => i == 0 ? 10.0 : 3.0, + "Multiply" => i == 0 ? 2.5 : 4.0, + _ => (i + 1) * 1.5 + }, + "String" => methodName switch + { + "Reverse" => "Hello", + "ToUpper" => "world", + _ => $"test{i}" + }, + _ => GetDefaultValue(param.ParameterType) + }; + } + + return args; + } + + /// + /// 获取类型默认值 + /// + private static object GetDefaultValue(Type type) + { + if (type.IsValueType) + return Activator.CreateInstance(type)!; + + return type == typeof(string) ? "default" : null!; + } + + /// + /// 演示高级功能 + /// + public static void RunAdvancedExample() + { + Console.WriteLine("\n=== 高级功能演示 ===\n"); + + try + { + // 1. 注册自定义类型 + Console.WriteLine("1. 注册自定义类型映射..."); + Parser.RegisterCustomType("CustomType", typeof(DateTime)); + Console.WriteLine(" ✓ 已注册 CustomType -> DateTime"); + + // 2. 清除缓存 + Console.WriteLine("\n2. 清除所有缓存..."); + Parser.ClearCache(); + Console.WriteLine(" ✓ 缓存已清除"); + + // 3. 设置自定义插件管理器 + Console.WriteLine("\n3. 设置自定义插件管理器..."); + Parser.SetDefaultPluginManager(new MockPluginManager()); + Console.WriteLine(" ✓ 已设置默认插件管理器"); + + } + catch (Exception ex) + { + Console.WriteLine($"❌ 高级功能演示失败: {ex.Message}"); + } + } +} diff --git a/KitX Script/Kscript.CSharp.Parser/Examples/Program.cs b/KitX Script/Kscript.CSharp.Parser/Examples/Program.cs new file mode 100644 index 0000000..5598f27 --- /dev/null +++ b/KitX Script/Kscript.CSharp.Parser/Examples/Program.cs @@ -0,0 +1,39 @@ +using Kscript.CSharp.Parser.Examples; + +namespace Kscript.CSharp.Parser.Examples; + +/// +/// 演示程序入口 +/// +public static class Program +{ + /// + /// 主入口方法 + /// + public static async Task Main(string[] args) + { + Console.OutputEncoding = System.Text.Encoding.UTF8; + Console.WriteLine("🚀 欢迎使用 KitX.CSharp.Parser 演示程序!"); + Console.WriteLine("==================================================\n"); + + try + { + // 运行基础用法示例 + await BasicUsageExample.RunExample(); + + // 运行高级功能示例 + BasicUsageExample.RunAdvancedExample(); + + Console.WriteLine("\n=================================================="); + Console.WriteLine("✅ 所有示例运行完成!"); + } + catch (Exception ex) + { + Console.WriteLine($"\n❌ 程序运行失败: {ex.Message}"); + Console.WriteLine($"详细错误信息:\n{ex}"); + } + + Console.WriteLine("\n按任意键退出..."); + Console.ReadKey(); + } +} diff --git a/KitX Script/Kscript.CSharp.Parser/Exceptions/ParserException.cs b/KitX Script/Kscript.CSharp.Parser/Exceptions/ParserException.cs new file mode 100644 index 0000000..5ba8106 --- /dev/null +++ b/KitX Script/Kscript.CSharp.Parser/Exceptions/ParserException.cs @@ -0,0 +1,43 @@ +namespace Kscript.CSharp.Parser.Exceptions; + +/// +/// 解析器异常 +/// +public class ParserException : Exception +{ + public ParserException() : base() + { + } + + public ParserException(string message) : base(message) + { + } + + public ParserException(string message, Exception innerException) : base(message, innerException) + { + } + + /// + /// 创建类型映射异常 + /// + public static ParserException TypeMappingError(string typeName, Exception? innerException = null) + { + return new ParserException($"无法映射类型: {typeName}", innerException!); + } + + /// + /// 创建IL生成异常 + /// + public static ParserException ILGenerationError(string methodName, Exception? innerException = null) + { + return new ParserException($"生成IL代码失败: {methodName}", innerException!); + } + + /// + /// 创建程序集生成异常 + /// + public static ParserException AssemblyGenerationError(string assemblyName, Exception? innerException = null) + { + return new ParserException($"生成程序集失败: {assemblyName}", innerException!); + } +} diff --git a/KitX Script/Kscript.CSharp.Parser/GlobalUsings.cs b/KitX Script/Kscript.CSharp.Parser/GlobalUsings.cs new file mode 100644 index 0000000..3708735 --- /dev/null +++ b/KitX Script/Kscript.CSharp.Parser/GlobalUsings.cs @@ -0,0 +1,6 @@ +global using System; +global using System.Collections.Generic; +global using System.Linq; +global using System.Reflection; +global using System.Reflection.Emit; +global using KitX.Shared.CSharp.Plugin; diff --git a/KitX Script/Kscript.CSharp.Parser/Kscript.CSharp.Parser.csproj b/KitX Script/Kscript.CSharp.Parser/Kscript.CSharp.Parser.csproj new file mode 100644 index 0000000..e873ef7 --- /dev/null +++ b/KitX Script/Kscript.CSharp.Parser/Kscript.CSharp.Parser.csproj @@ -0,0 +1,21 @@ + + + + net8.0 + 12 + enable + enable + Kscript.CSharp.Parser + Exe + Kscript.CSharp.Parser.Examples.Program + + + + + + + + + + + diff --git a/KitX Script/Kscript.CSharp.Parser/Kscript.CSharp.Parser.sln b/KitX Script/Kscript.CSharp.Parser/Kscript.CSharp.Parser.sln new file mode 100644 index 0000000..2b571ef --- /dev/null +++ b/KitX Script/Kscript.CSharp.Parser/Kscript.CSharp.Parser.sln @@ -0,0 +1,24 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kscript.CSharp.Parser", "Kscript.CSharp.Parser.csproj", "{17B35B70-0BC3-479B-83D6-ED1513D6C4A6}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {17B35B70-0BC3-479B-83D6-ED1513D6C4A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {17B35B70-0BC3-479B-83D6-ED1513D6C4A6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {17B35B70-0BC3-479B-83D6-ED1513D6C4A6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {17B35B70-0BC3-479B-83D6-ED1513D6C4A6}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {C88E6290-4CEE-41DA-B062-81147E9E898E} + EndGlobalSection +EndGlobal diff --git a/KitX Script/Kscript.CSharp.Parser/LLMDocs/InitPlan.md b/KitX Script/Kscript.CSharp.Parser/LLMDocs/InitPlan.md new file mode 100644 index 0000000..b3ca63c --- /dev/null +++ b/KitX Script/Kscript.CSharp.Parser/LLMDocs/InitPlan.md @@ -0,0 +1,76 @@ +现在让我为你详细规划 KitX.CSharp.Parser 项目的架构和实现方案: + +## 🏗️ 项目架构概览 + +```mermaid +graph TD + A[JSON 插件清单] --> B[Parser.Generate] + B --> C[PluginInfo 模型解析] + C --> D[TypeMapper 类型映射] + D --> E[MethodEmitter IL生成] + E --> F[AssemblyCache 缓存管理] + F --> G[动态程序集] + G --> H[脚本调用: Plugin.Method] + + subgraph "核心组件" + I[Parser - 主入口] + J[TypeMapper - 类型映射] + K[MethodEmitter - IL生成] + L[AssemblyCache - 缓存] + M[PluginManager - 模拟调用] + end +``` + +## 📁 详细目录结构 + +``` +KitX.CSharp.Parser/ +├─ src/ +│ ├─ Parser.cs # 主入口静态类 +│ ├─ Models/ +│ │ ├─ PluginInfo.cs # 插件信息模型 +│ │ ├─ FunctionInfo.cs # 功能信息模型 +│ │ ├─ ParameterInfo.cs # 参数信息模型 +│ │ └─ PluginCallInfo.cs # 插件调用信息 +│ ├─ CodeGen/ +│ │ ├─ TypeMapper.cs # 字符串到Type的映射 +│ │ ├─ MethodEmitter.cs # IL方法生成器 +│ │ └─ AssemblyCache.cs # 程序集缓存管理 +│ ├─ Core/ +│ │ ├─ IPluginManager.cs # 插件管理器接口 +│ │ └─ MockPluginManager.cs # 模拟实现 +│ └─ Exceptions/ +│ └─ ParserException.cs # 自定义异常 +├─ examples/ +│ └─ ConsoleDemo/ # 演示程序 +├─ KitX.CSharp.Parser.csproj # 项目文件 +└─ GlobalUsings.cs # 全局using声明 +``` + +## 🔧 核心技术要点 + +1. **类型映射策略**: + - 基础类型:`int` → `System.Int32` + - 泛型类型:`List` → `List` + - 自定义类型回退机制 + +2. **IL 生成模式**: + ```csharp + // 生成的方法体伪代码 + public static T MethodName(params...) + { + var callInfo = new PluginCallInfo(pluginName, methodName, parameters); + return PluginManager.Call(callInfo); + } + ``` + +3. **缓存机制**: + - 按插件清单 Hash 缓存程序集 + - 支持热更新时的缓存失效 + +## 🎯 关键实现亮点 + +- **类型安全**:编译时类型检查,运行时类型转换 +- **性能优化**:程序集缓存避免重复生成 +- **扩展性**:支持自定义类型映射注册 +- **调试友好**:可选的磁盘程序集输出 diff --git a/KitX Script/Kscript.CSharp.Parser/Models/PluginCallInfo.cs b/KitX Script/Kscript.CSharp.Parser/Models/PluginCallInfo.cs new file mode 100644 index 0000000..3cc0cc6 --- /dev/null +++ b/KitX Script/Kscript.CSharp.Parser/Models/PluginCallInfo.cs @@ -0,0 +1,44 @@ +namespace Kscript.CSharp.Parser.Models; + +/// +/// 插件调用信息,用于传递给 PluginManager.Call 的参数(模拟,可能根据外部实现发生更改) +/// +public class PluginCallInfo +{ + /// + /// 插件名称 + /// + public string PluginName { get; set; } = string.Empty; + + /// + /// 方法名称 + /// + public string MethodName { get; set; } = string.Empty; + + /// + /// 方法参数数组 + /// + public object[] Parameters { get; set; } = Array.Empty(); + + /// + /// 参数类型数组 + /// + public Type[] ParameterTypes { get; set; } = Array.Empty(); + + public PluginCallInfo() + { + } + + public PluginCallInfo(string pluginName, string methodName, object[] parameters, Type[] parameterTypes) + { + PluginName = pluginName; + MethodName = methodName; + Parameters = parameters; + ParameterTypes = parameterTypes; + } + + public override string ToString() + { + return $"{PluginName}.{MethodName}({string.Join(", ", Parameters)})"; + } +} diff --git a/KitX Script/Kscript.CSharp.Parser/Parser.cs b/KitX Script/Kscript.CSharp.Parser/Parser.cs new file mode 100644 index 0000000..4d88ce5 --- /dev/null +++ b/KitX Script/Kscript.CSharp.Parser/Parser.cs @@ -0,0 +1,271 @@ +using System.Text.Json; +using Kscript.CSharp.Parser.CodeGen; +using Kscript.CSharp.Parser.Core; +using Kscript.CSharp.Parser.Exceptions; + +namespace Kscript.CSharp.Parser; + +/// +/// KitX.CSharp.Parser 主入口类 +/// 将插件清单 JSON 即时编译成可直接调用的 C# 静态 API +/// +public static class Parser +{ + /// + /// 默认插件管理器 + /// + private static IPluginManager? _defaultPluginManager; + + /// + /// 设置默认插件管理器 + /// + /// 插件管理器实例 + public static void SetDefaultPluginManager(IPluginManager pluginManager) + { + _defaultPluginManager = pluginManager; + } + + /// + /// 从插件信息列表生成动态程序集 + /// + /// 插件信息列表 + /// 程序集名称 + /// 插件管理器实例(可选,默认使用 MockPluginManager) + /// 是否使用缓存 + /// 生成的动态程序集 + public static Assembly Generate(List plugins, string assemblyName = "DynamicPluginAssembly", + IPluginManager? pluginManager = null, bool useCache = true) + { + if (plugins == null || plugins.Count == 0) + { + throw new ArgumentException("插件列表不能为空", nameof(plugins)); + } + + try + { + var manager = pluginManager ?? _defaultPluginManager ?? new MockPluginManager(); + + if (useCache) + { + return AssemblyCache.GetOrCreateAssembly(plugins, assemblyName, manager); + } + else + { + return MethodEmitter.GenerateAssembly(plugins, assemblyName, manager); + } + } + catch (Exception ex) when (!(ex is ParserException)) + { + throw ParserException.AssemblyGenerationError(assemblyName, ex); + } + } + + /// + /// 从 JSON 字符串生成动态程序集 + /// + /// 插件清单 JSON 字符串 + /// 程序集名称 + /// 插件管理器实例 + /// 是否使用缓存 + /// 生成的动态程序集 + public static Assembly GenerateFromJson(string jsonString, string assemblyName = "DynamicPluginAssembly", + IPluginManager? pluginManager = null, bool useCache = true) + { + if (string.IsNullOrWhiteSpace(jsonString)) + { + throw new ArgumentException("JSON 字符串不能为空", nameof(jsonString)); + } + + try + { + var plugins = JsonSerializer.Deserialize>(jsonString, new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + AllowTrailingCommas = true + }); + + if (plugins == null) + { + throw new ParserException("JSON 反序列化失败,结果为 null"); + } + + return Generate(plugins, assemblyName, pluginManager, useCache); + } + catch (JsonException ex) + { + throw new ParserException($"JSON 格式错误: {ex.Message}", ex); + } + } + + /// + /// 从 JSON 文件生成动态程序集 + /// + /// JSON 文件路径 + /// 程序集名称 + /// 插件管理器实例 + /// 是否使用缓存 + /// 生成的动态程序集 + public static async Task GenerateFromFileAsync(string jsonFilePath, string assemblyName = "DynamicPluginAssembly", + IPluginManager? pluginManager = null, bool useCache = true) + { + if (string.IsNullOrWhiteSpace(jsonFilePath)) + { + throw new ArgumentException("文件路径不能为空", nameof(jsonFilePath)); + } + + if (!File.Exists(jsonFilePath)) + { + throw new FileNotFoundException($"文件不存在: {jsonFilePath}"); + } + + try + { + var jsonString = await File.ReadAllTextAsync(jsonFilePath); + return GenerateFromJson(jsonString, assemblyName, pluginManager, useCache); + } + catch (Exception ex) when (!(ex is ParserException)) + { + throw new ParserException($"读取文件失败: {jsonFilePath}", ex); + } + } + + /// + /// 强制重新生成程序集(绕过缓存) + /// + /// 插件信息列表 + /// 程序集名称 + /// 插件管理器实例 + /// 新生成的动态程序集 + public static Assembly Regenerate(List plugins, string assemblyName = "DynamicPluginAssembly", + IPluginManager? pluginManager = null) + { + if (plugins == null || plugins.Count == 0) + { + throw new ArgumentException("插件列表不能为空", nameof(plugins)); + } + + try + { + var manager = pluginManager ?? _defaultPluginManager ?? new MockPluginManager(); + return AssemblyCache.ForceRegenerate(plugins, assemblyName, manager); + } + catch (Exception ex) when (!(ex is ParserException)) + { + throw ParserException.AssemblyGenerationError(assemblyName, ex); + } + } + + /// + /// 注册自定义类型映射 + /// + /// 类型名字符串 + /// 对应的 Type 对象 + public static void RegisterCustomType(string typeName, Type type) + { + TypeMapper.RegisterCustomType(typeName, type); + } + + /// + /// 清除所有缓存 + /// + public static void ClearCache() + { + AssemblyCache.ClearCache(); + TypeMapper.ClearCache(); + } + + /// + /// 获取缓存统计信息 + /// + /// 缓存统计信息 + public static CacheStatistics GetCacheStatistics() + { + return AssemblyCache.GetStatistics(); + } + + /// + /// 检查插件清单是否已有缓存 + /// + /// 插件信息列表 + /// 程序集名称 + /// 是否存在缓存 + public static bool HasCache(List plugins, string assemblyName = "DynamicPluginAssembly") + { + return AssemblyCache.HasCache(plugins, assemblyName); + } + + /// + /// 将程序集保存到磁盘(用于调试) + /// + /// 要保存的程序集 + /// 保存路径 + /// 是否覆盖现有文件 + public static void SaveAssemblyToFile(Assembly assembly, string filePath, bool overwrite = false) + { + if (assembly == null) + throw new ArgumentNullException(nameof(assembly)); + + if (string.IsNullOrWhiteSpace(filePath)) + throw new ArgumentException("文件路径不能为空", nameof(filePath)); + + if (File.Exists(filePath) && !overwrite) + throw new InvalidOperationException($"文件已存在且不允许覆盖: {filePath}"); + + try + { + // 注意:在 .NET 8.0 中,动态程序集默认是不可保存的 + // 这个方法主要用于调试目的,实际保存功能需要特殊处理 + Console.WriteLine($"[Parser] 注意: .NET 8.0 中动态程序集无法直接保存到磁盘"); + Console.WriteLine($"[Parser] 程序集信息: {assembly.FullName}"); + Console.WriteLine($"[Parser] 包含类型: {string.Join(", ", assembly.GetTypes().Select(t => t.Name))}"); + } + catch (Exception ex) + { + throw new ParserException($"保存程序集失败: {filePath}", ex); + } + } + + /// + /// 获取程序集中的所有插件类型 + /// + /// 程序集 + /// 插件类型列表 + public static List GetPluginTypes(Assembly assembly) + { + if (assembly == null) + throw new ArgumentNullException(nameof(assembly)); + + try + { + return assembly.GetTypes() + .Where(t => t.IsClass && t.IsAbstract && t.IsSealed) // 静态类 + .ToList(); + } + catch (Exception ex) + { + throw new ParserException("获取插件类型失败", ex); + } + } + + /// + /// 获取插件类型的所有方法信息 + /// + /// 插件类型 + /// 方法信息列表 + public static List GetPluginMethods(Type pluginType) + { + if (pluginType == null) + throw new ArgumentNullException(nameof(pluginType)); + + try + { + return pluginType.GetMethods(BindingFlags.Public | BindingFlags.Static) + .Where(m => !m.IsSpecialName) // 排除特殊方法如属性访问器 + .ToList(); + } + catch (Exception ex) + { + throw new ParserException($"获取插件方法失败: {pluginType.Name}", ex); + } + } +} diff --git a/KitX Script/Kscript.CSharp.Parser/README.md b/KitX Script/Kscript.CSharp.Parser/README.md new file mode 100644 index 0000000..2530fdd --- /dev/null +++ b/KitX Script/Kscript.CSharp.Parser/README.md @@ -0,0 +1,202 @@ +# KitX.CSharp.Parser +> 将插件清单 JSON 即时编译成可直接调用的 C# 静态 API + +--- + +## 🌱 项目定位 +KitX.CSharp.Parser 是 **KitX 工作流子系统** 的第一环。 +它只做一件事: +**把插件清单(JSON)→ 运行时静态类/方法**, +让后续脚本引擎(Roslyn、CSharpScript、Eval-etc.)可以像调用普通静态方法一样使用插件功能。 + +``` + JSON 清单 ─→ Parser ─→ 内存程序集(DynamicAssembly) + │ + └─ 脚本侧: SamplePlugin.Run(…) +``` + +--- + +## 🎯 边界与职责 +| 范围 | 属于本库 | 不属于本库 | +|---|---|---| +| 读取 JSON | ✅ 反序列化为 `PluginInfo` | ❌ 网络/磁盘 IO | +| 类型映射 | ✅ `"int"` → `System.Int32` | ❌ 复杂自定义类型序列化 | +| 生成 IL | ✅ 用 `Reflection.Emit` 生成静态类/方法 | ❌ 脚本执行、调试 | +| 生命周期 | ✅ 生成后缓存 `Assembly` | ❌ 热重载(由上层调用 `Regenerate()`) | +| 错误处理 | ✅ 语法/类型错误抛出 `ParserException` | ❌ 插件运行时异常 | + +--- + +## 🧩 整体流程 +1. **输入** + `List`(已由外部从 JSON 反序列化好)。 + +2. **映射** + 把 `Function.ReturnValueType` / `Parameter.Type` 字符串映射到 `System.Type`。 + +3. **构建** + - 每个插件 → 一个 `static class`(`TypeBuilder`) + - 每个功能 → 一个 `public static` 方法(`MethodBuilder`) + +4. **Emit** + 方法体内构造 `PluginCallInfo`,调用 `PluginManager.Call()`, + 返回值拆箱后 `ret`。 + +5. **输出** + 程序集保存到内存(或可选磁盘 DLL), + 脚本侧通过 `PluginNamespace.PluginName.Func(...)` 直接调用。 + +--- + +## 🗂️ 目录结构(初始) +``` +KitX.CSharp.Parser/ + ├─ src/ + │ ├─ Parser.cs // 对外静态入口 + │ ├─ CodeGen/ // IL 生成实现 + │ │ ├─ TypeMapper.cs // 字符串 → Type + │ │ ├─ MethodEmitter.cs // IL 指令 + │ │ └─ AssemblyCache.cs // 已生成 Assembly 缓存 + │ └─ Models/ + │ └─ PluginInfo.cs // 复用现有结构 + ├─ tests/ + │ └─ Parser.Tests/ + └─ README.md +``` + +--- + +## 🚀 快速开始 + +### 基本用法 + +```csharp +using Kscript.CSharp.Parser; + +// 1. 从 JSON 文件生成程序集 +var assembly = await Parser.GenerateFromFileAsync("example.json"); + +// 2. 或从插件信息列表生成 +var plugins = new List { /* ... */ }; +var assembly = Parser.Generate(plugins); + +// 3. 脚本中直接调用生成的方法 +int sum = SampleCalculator.Add(10, 20); +string reversed = StringToolkit.Reverse("Hello"); +``` + +### 运行演示 + +```bash +# 构建项目 +dotnet build + +# 运行演示程序 +dotnet run +``` + +--- + +## 📁 项目结构 + +``` +KitX.CSharp.Parser/ +├─ src/ +│ ├─ Parser.cs # 主入口静态类 +│ ├─ Models/ +│ │ └─ PluginCallInfo.cs # 插件调用信息模型 +│ ├─ CodeGen/ +│ │ ├─ TypeMapper.cs # 字符串 → Type 映射 +│ │ ├─ MethodEmitter.cs # IL 方法生成器 +│ │ └─ AssemblyCache.cs # 程序集缓存管理 +│ ├─ Core/ +│ │ ├─ IPluginManager.cs # 插件管理器接口 +│ │ └─ MockPluginManager.cs # 模拟实现 +│ └─ Exceptions/ +│ └─ ParserException.cs # 自定义异常 +├─ Examples/ +│ ├─ BasicUsageExample.cs # 基础用法示例 +│ └─ Program.cs # 演示程序入口 +├─ example.json # 插件清单示例 +├─ README.md # 项目说明 +└─ USAGE.md # 详细使用指南 +``` + +--- + +## ⚙️ 主要特性 + +- ✅ **动态 IL 生成** - 使用 `Reflection.Emit` 生成高性能静态方法 +- ✅ **智能类型映射** - 支持基础类型、泛型类型和自定义类型 +- ✅ **程序集缓存** - 避免重复生成,提升性能 +- ✅ **扩展性设计** - 支持自定义插件管理器和类型映射 +- ✅ **异常处理** - 完善的错误处理机制 +- ✅ **调试友好** - 详细的日志输出和统计信息 + +--- + +## 📐 类型映射支持 + +| JSON 字符串 | CLR 类型 | 示例 | +|---|---|---| +| `"int"` | `System.Int32` | 整数参数 | +| `"double"` | `System.Double` | 浮点数参数 | +| `"string"` | `System.String` | 字符串参数 | +| `"bool"` | `System.Boolean` | 布尔参数 | +| `"void"` | `System.Void` | 无返回值 | +| `"List"` | `List` | 泛型集合 | +| `"Dictionary"` | `Dictionary` | 字典类型 | +| 自定义 | 通过 `RegisterCustomType()` 注册 | 扩展类型 | + +--- + +## 🎯 运行结果示例 + +``` +🚀 欢迎使用 KitX.CSharp.Parser 演示程序! + +=== KitX.CSharp.Parser 基础用法示例 === + +1. 加载插件清单... + ✓ 成功生成程序集: ExamplePluginAssembly + +2. 分析生成的插件类型... + 发现 2 个插件类: + - SampleCalculator + └── Int32 Add(Int32 a, Int32 b) + └── Double Divide(Double numerator, Double denominator, Int32 decimals) + - StringToolkit + └── String Reverse(String text) + └── String ToUpper(String text) + +3. 动态调用插件方法... + 调用 Add(10, 20) → 结果: 30 + 调用 Divide(10, 3, 2) → 结果: 3.33 + 调用 Reverse(Hello) → 结果: olleH + 调用 ToUpper(world) → 结果: WORLD + +4. 缓存统计信息... + 缓存统计: 1 个程序集, 1 个引用 +``` + +--- + +## 📋 TODO / 未来计划 + +### 🔄 项目重构 +- [ ] **转换为纯库项目** - 移除命令行功能,专注于提供API +- [ ] **移除演示程序** - 将 `Examples/` 目录移至独立的演示项目 +- [ ] **调整项目配置** - 移除 `OutputType=Exe` 配置 + +### 🔌 插件管理器集成 +- [ ] **替换 Mock 实现** - 当 KitX 主项目完成后,集成真实的 `PluginManager` +- [ ] **接口适配** - 根据实际需求调整 `IPluginManager` 接口定义 +- [ ] **调用机制优化** - 优化插件调用的性能和稳定性 + +### 🛠️ 功能增强(暂无计划) +### 📈 性能优化(暂无计划) +### 🧪 测试覆盖(暂无计划) + +> **注意**: 当前版本使用 `MockPluginManager` 进行功能验证。在 KitX 生态系统的其他组件完成后,需要将其替换为真实的插件调用实现。 + diff --git a/KitX Script/Kscript.CSharp.Parser/USAGE.md b/KitX Script/Kscript.CSharp.Parser/USAGE.md new file mode 100644 index 0000000..22983bb --- /dev/null +++ b/KitX Script/Kscript.CSharp.Parser/USAGE.md @@ -0,0 +1,292 @@ +# KitX.CSharp.Parser 使用指南 + +## 📖 概述 + +KitX.CSharp.Parser 是 KitX 工作流子系统的核心组件,它能够将插件清单 JSON 即时编译成可直接调用的 C# 静态 API。通过动态 IL 生成技术,让脚本引擎可以像调用普通静态方法一样使用插件功能。 + +## 🚀 快速开始 + +### 基本用法 + +```csharp +using Kscript.CSharp.Parser; + +// 1. 从 JSON 字符串生成程序集 +var jsonString = File.ReadAllText("plugins.json"); +var assembly = Parser.GenerateFromJson(jsonString); + +// 2. 从文件生成程序集 +var assembly = await Parser.GenerateFromFileAsync("plugins.json"); + +// 3. 从插件信息列表生成程序集 +var plugins = new List { /* ... */ }; +var assembly = Parser.Generate(plugins); +``` + +### 动态调用生成的方法 + +```csharp +// 获取生成的插件类型 +var pluginTypes = Parser.GetPluginTypes(assembly); + +foreach (var type in pluginTypes) +{ + var methods = Parser.GetPluginMethods(type); + foreach (var method in methods) + { + // 动态调用方法 + var result = method.Invoke(null, new object[] { /* 参数 */ }); + Console.WriteLine($"结果: {result}"); + } +} +``` + +## 🔧 API 参考 + +### Parser 静态类 + +主入口类,提供所有核心功能。 + +#### 生成方法 + +| 方法 | 描述 | 参数 | +|------|------|------| +| `Generate()` | 从插件信息列表生成程序集 | `plugins`, `assemblyName`, `pluginManager`, `useCache` | +| `GenerateFromJson()` | 从 JSON 字符串生成程序集 | `jsonString`, `assemblyName`, `pluginManager`, `useCache` | +| `GenerateFromFileAsync()` | 从 JSON 文件异步生成程序集 | `jsonFilePath`, `assemblyName`, `pluginManager`, `useCache` | +| `Regenerate()` | 强制重新生成程序集(绕过缓存) | `plugins`, `assemblyName`, `pluginManager` | + +#### 配置方法 + +| 方法 | 描述 | +|------|------| +| `SetDefaultPluginManager()` | 设置默认插件管理器 | +| `RegisterCustomType()` | 注册自定义类型映射 | +| `ClearCache()` | 清除所有缓存 | + +#### 分析方法 + +| 方法 | 描述 | +|------|------| +| `GetPluginTypes()` | 获取程序集中的插件类型 | +| `GetPluginMethods()` | 获取插件类型的方法信息 | +| `GetCacheStatistics()` | 获取缓存统计信息 | +| `HasCache()` | 检查是否存在缓存 | + +### 类型映射系统 + +#### 支持的基础类型 + +| JSON 字符串 | CLR 类型 | +|-------------|----------| +| `"void"` | `System.Void` | +| `"bool"` | `System.Boolean` | +| `"int"` | `System.Int32` | +| `"double"` | `System.Double` | +| `"string"` | `System.String` | +| `"object"` | `System.Object` | + +#### 泛型类型支持 + +```csharp +// 支持的泛型类型 +"List" → List +"Dictionary" → Dictionary +"Nullable" → int? +"Array" → string[] +``` + +#### 自定义类型映射 + +```csharp +// 注册自定义类型 +Parser.RegisterCustomType("DateTime", typeof(DateTime)); +Parser.RegisterCustomType("MyCustomType", typeof(MyClass)); +``` + +## 🏗️ 架构设计 + +### 核心组件 + +1. **Parser** - 主入口类,统一API接口 +2. **TypeMapper** - 类型映射器,处理字符串到Type的转换 +3. **MethodEmitter** - IL方法生成器,核心IL生成逻辑 +4. **AssemblyCache** - 程序集缓存,提升性能 +5. **PluginManager** - 插件管理器接口,处理实际调用 + +### 工作流程 + +```mermaid +graph TD + A[JSON 插件清单] --> B[Parser.Generate] + B --> C[TypeMapper 类型映射] + C --> D[MethodEmitter IL生成] + D --> E[AssemblyCache 缓存] + E --> F[动态程序集] + F --> G[脚本调用] +``` + +## 📝 插件清单格式 + +### 基本结构 + +```json +[ + { + "Name": "PluginName", + "Version": "1.0.0", + "Functions": [ + { + "Name": "MethodName", + "ReturnValueType": "int", + "Parameters": [ + { + "Name": "paramName", + "Type": "string", + "IsOptional": false, + "Value": "defaultValue" + } + ] + } + ] + } +] +``` + +### 完整示例 + +参考项目根目录的 `example.json` 文件,包含完整的插件定义示例。 + +## ⚡ 性能优化 + +### 缓存机制 + +- **程序集缓存**: 相同插件清单只生成一次 +- **类型映射缓存**: 避免重复类型解析 +- **智能哈希**: 基于插件内容生成缓存键 + +### 最佳实践 + +```csharp +// 1. 复用程序集 +var assembly = Parser.Generate(plugins, useCache: true); + +// 2. 批量注册自定义类型 +Parser.RegisterCustomType("DateTime", typeof(DateTime)); +Parser.RegisterCustomType("Guid", typeof(Guid)); + +// 3. 合理使用强制重新生成 +if (pluginChanged) +{ + assembly = Parser.Regenerate(plugins); +} +``` + +## 🔍 调试功能 + +### 日志输出 + +使用 `MockPluginManager` 时会输出详细的调用日志: + +``` +[MockPluginManager] 调用插件方法: SampleCalculator.Add(10, 20) +[MockPluginManager] 参数类型: [Int32, Int32] +[MockPluginManager] 期望返回类型: Int32 +[MockPluginManager] 返回结果: 30 +``` + +### 缓存统计 + +```csharp +var stats = Parser.GetCacheStatistics(); +Console.WriteLine($"缓存统计: {stats.CachedAssemblyCount} 个程序集"); +``` + +## 🚨 错误处理 + +### 异常类型 + +- `ParserException` - 解析器相关异常 +- `ArgumentException` - 参数错误 +- `FileNotFoundException` - 文件不存在 + +### 异常处理示例 + +```csharp +try +{ + var assembly = Parser.GenerateFromJson(jsonString); +} +catch (ParserException ex) +{ + Console.WriteLine($"解析失败: {ex.Message}"); +} +catch (JsonException ex) +{ + Console.WriteLine($"JSON 格式错误: {ex.Message}"); +} +``` + +## 🔌 扩展点 + +### 自定义插件管理器 + +```csharp +public class CustomPluginManager : IPluginManager +{ + public T Call(PluginCallInfo callInfo) + { + // 实现自定义调用逻辑 + return default(T); + } + + // 实现其他接口方法... +} + +// 设置自定义管理器 +Parser.SetDefaultPluginManager(new CustomPluginManager()); +``` + +### 自定义类型映射 + +```csharp +// 扩展类型映射 +TypeMapper.RegisterCustomType("Vector3", typeof(UnityEngine.Vector3)); +TypeMapper.RegisterCustomType("Color", typeof(System.Drawing.Color)); +``` + +## 🔄 与其他系统集成 + +### 脚本引擎集成 + +生成的程序集可直接用于各种 C# 脚本引擎: + +- **Roslyn** - Microsoft.CodeAnalysis +- **CSharpScript** - Microsoft.CodeAnalysis.CSharp.Scripting +- **CS-Script** - 第三方脚本引擎 + +### 示例集成代码 + +```csharp +// 在脚本中使用生成的API +var script = @" + int result = SampleCalculator.Add(10, 20); + string reversed = StringToolkit.Reverse(""Hello""); + return result; +"; + +// 通过脚本引擎执行 +var assembly = Parser.Generate(plugins); +var scriptResult = ExecuteScript(script, assembly); +``` + +## 📚 更多示例 + +完整的使用示例请参考: +- `Examples/BasicUsageExample.cs` - 基础功能演示 +- `Examples/Program.cs` - 控制台演示程序 + +## 🔗 相关项目 + +- **KitX.Shared.CSharp** - 共享数据模型 +- **KitX 主项目** - 完整的插件生态系统 diff --git a/KitX Script/Kscript.CSharp.Parser/example.json b/KitX Script/Kscript.CSharp.Parser/example.json new file mode 100644 index 0000000..60ed38c --- /dev/null +++ b/KitX Script/Kscript.CSharp.Parser/example.json @@ -0,0 +1,154 @@ +[ + { + "Name": "SampleCalculator", + "Version": "1.0.0", + "DisplayName": { + "en": "Calculator", + "zh": "计算器" + }, + "AuthorName": "KitX Devs", + "AuthorLink": "https://github.com/Crequency/KitX", + "PublisherName": "Crequency", + "PublisherLink": "https://kitx.cc", + "SimpleDescription": { + "en": "A simple calculator plugin.", + "zh": "简易计算器插件" + }, + "ComplexDescription": {}, + "TotalDescriptionInMarkdown": {}, + "IconInBase64": "", + "PublishDate": "2025-07-29T00:00:00Z", + "LastUpdateDate": "2025-07-29T00:00:00Z", + "IsMarketVersion": true, + "Tags": { + "en": "math,utility", + "zh": "数学,工具" + }, + "Functions": [ + { + "Name": "Add", + "DisplayNames": { + "en": "Add two integers", + "zh": "整数相加" + }, + "Parameters": [ + { + "Name": "a", + "DisplayNames": { + "en": "First number", + "zh": "第一个数" + }, + "Type": "int", + "Value": "0", + "IsOptional": false + }, + { + "Name": "b", + "DisplayNames": { + "en": "Second number", + "zh": "第二个数" + }, + "Type": "int", + "Value": "0", + "IsOptional": false + } + ], + "ReturnValue": "Sum of a and b", + "ReturnValueType": "int" + }, + { + "Name": "Divide", + "DisplayNames": { + "en": "Divide with optional precision", + "zh": "带可选精度的除法" + }, + "Parameters": [ + { + "Name": "numerator", + "Type": "double", + "Value": "0", + "IsOptional": false + }, + { + "Name": "denominator", + "Type": "double", + "Value": "1", + "IsOptional": false + }, + { + "Name": "decimals", + "Type": "int", + "Value": "2", + "IsOptional": true + } + ], + "ReturnValue": "Rounded quotient", + "ReturnValueType": "double" + } + ], + "RootStartupFileName": "SampleCalculator.dll" + }, + { + "Name": "StringToolkit", + "Version": "1.2.0", + "DisplayName": { + "en": "String Toolkit", + "zh": "字符串工具箱" + }, + "AuthorName": "Community", + "AuthorLink": "https://example.com", + "PublisherName": "KitX", + "PublisherLink": "https://kitx.cc", + "SimpleDescription": { + "en": "Utility functions for string manipulation.", + "zh": "字符串处理工具集" + }, + "ComplexDescription": {}, + "TotalDescriptionInMarkdown": {}, + "IconInBase64": "", + "PublishDate": "2025-07-29T00:00:00Z", + "LastUpdateDate": "2025-07-29T00:00:00Z", + "IsMarketVersion": false, + "Tags": { + "en": "string,utility,text", + "zh": "字符串,工具,文本" + }, + "Functions": [ + { + "Name": "Reverse", + "DisplayNames": { + "en": "Reverse string", + "zh": "反转字符串" + }, + "Parameters": [ + { + "Name": "text", + "Type": "string", + "Value": "", + "IsOptional": false + } + ], + "ReturnValue": "Reversed text", + "ReturnValueType": "string" + }, + { + "Name": "ToUpper", + "DisplayNames": { + "en": "Convert to upper case", + "zh": "转大写" + }, + "Parameters": [ + { + "Name": "text", + "Type": "string", + "Value": "", + "IsOptional": false + } + ], + "ReturnValue": "Uppercase text", + "ReturnValueType": "string" + } + ], + "RootStartupFileName": "StringToolkit.dll" + } + ] diff --git a/KitX Script/Kscript.CSharp/Kscript.CSharp.sln b/KitX Script/Kscript.CSharp/Kscript.CSharp.sln new file mode 100644 index 0000000..bf8c8ad --- /dev/null +++ b/KitX Script/Kscript.CSharp/Kscript.CSharp.sln @@ -0,0 +1,24 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kscript.CSharp", "Kscript.CSharp.csproj", "{F935BEC1-159C-96F1-83A8-DE0F8509777F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F935BEC1-159C-96F1-83A8-DE0F8509777F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F935BEC1-159C-96F1-83A8-DE0F8509777F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F935BEC1-159C-96F1-83A8-DE0F8509777F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F935BEC1-159C-96F1-83A8-DE0F8509777F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {CFC1FA4A-256C-4413-BD89-42FCAB741EBB} + EndGlobalSection +EndGlobal From 9c6eda2013fd13080f65d7692a0747a966e02b37 Mon Sep 17 00:00:00 2001 From: StarInk Date: Fri, 7 Nov 2025 19:57:00 +0100 Subject: [PATCH 09/17] =?UTF-8?q?=F0=9F=92=BE=20Feat(Kscript.CSharp.Parser?= =?UTF-8?q?):=20=E5=B0=9D=E8=AF=95=E9=80=82=E9=85=8DDashboard=E7=9A=84?= =?UTF-8?q?=E6=8F=92=E4=BB=B6=E7=AE=A1=E7=90=86=E5=99=A8=EF=BC=88=E6=9C=AA?= =?UTF-8?q?=E5=AE=8C=E6=88=90=EF=BC=8C=E5=BE=85=E4=BC=98=E5=8C=96=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ARCHITECTURE_ANALYSIS.md | 285 ++++++++++++++ .../Kscript.CSharp.Parser/CLASS_DIAGRAM.md | 343 +++++++++++++++++ .../CodeGen/MethodEmitter.cs | 22 +- .../Core/RealPluginManager.cs | 353 ++++++++++++++++++ .../Kscript.CSharp.Parser/Examples/Program.cs | 7 + .../Examples/RealPluginManagerExample.cs | 131 +++++++ .../INTEGRATION_GUIDE.md | 163 ++++++++ .../Kscript.CSharp.Parser.csproj | 1 + KitX Script/Kscript.CSharp.Parser/Parser.cs | 25 +- KitX Script/Kscript.CSharp.Parser/README.md | 56 +++ 10 files changed, 1381 insertions(+), 5 deletions(-) create mode 100644 KitX Script/Kscript.CSharp.Parser/ARCHITECTURE_ANALYSIS.md create mode 100644 KitX Script/Kscript.CSharp.Parser/CLASS_DIAGRAM.md create mode 100644 KitX Script/Kscript.CSharp.Parser/Core/RealPluginManager.cs create mode 100644 KitX Script/Kscript.CSharp.Parser/Examples/RealPluginManagerExample.cs create mode 100644 KitX Script/Kscript.CSharp.Parser/INTEGRATION_GUIDE.md diff --git a/KitX Script/Kscript.CSharp.Parser/ARCHITECTURE_ANALYSIS.md b/KitX Script/Kscript.CSharp.Parser/ARCHITECTURE_ANALYSIS.md new file mode 100644 index 0000000..b78fc4b --- /dev/null +++ b/KitX Script/Kscript.CSharp.Parser/ARCHITECTURE_ANALYSIS.md @@ -0,0 +1,285 @@ +# KitX.CSharp.Parser 架构分析与重构建议 + +## 🚨 当前架构问题分析 + +### 1. 循环依赖风险 + +```mermaid +graph TD + Parser --> MethodEmitter + MethodEmitter --> IPluginManager + Parser --> IPluginManager + MethodEmitter -.-> Parser +``` + +**问题描述:** +- [`Parser`](KitX Standard/KitX Script/Kscript.CSharp.Parser/Parser.cs:12) 设置插件管理器,但 [`MethodEmitter`](KitX Standard/KitX Script/Kscript.CSharp.Parser/CodeGen/MethodEmitter.cs:10) 也需要访问 +- [`MethodEmitter.SetStaticPluginManager()`](KitX Standard/KitX Script/Kscript.CSharp.Parser/CodeGen/MethodEmitter.cs:21) 创建了隐式依赖 + +### 2. 职责边界模糊 + +| 类 | 当前职责 | 问题 | +|------|----------|------| +| **Parser** | 入口+配置+协调+缓存管理 | 职责过多,违反单一职责原则 | +| **MethodEmitter** | IL生成+插件管理器管理 | 混合了生成逻辑和运行时逻辑 | +| **AssemblyCache** | 缓存+直接调用MethodEmitter | 缓存层不应该知道生成细节 | + +### 3. 跨层调用问题 + +```mermaid +graph LR + Parser[Parser 表现层] --> AssemblyCache[缓存层] + AssemblyCache --> MethodEmitter[生成层] + MethodEmitter --> TypeMapper[解析层] +``` + +**问题:** 缓存层直接调用生成层,破坏了分层架构的纯粹性 + +## 🎯 重构方案:单向数据流架构 + +### 核心设计原则 + +1. **数据只能向下流动**:上层 → 下层 +2. **配置只能横向注入**:配置层 → 各层 +3. **事件只能向上冒泡**:下层 → 上层(异常、状态) +4. **禁止跨层调用**:只能调用相邻层 + +### 📋 重构后的分层架构 + +```mermaid +graph TD + %% 输入层 + A[JSON 输入] --> B[Parser 输入层] + + %% 解析层 + B --> C[PluginInfo 解析层] + C --> D[TypeMapper 类型映射] + + %% 生成层 + D --> E[AssemblyBuilder 程序集构建器] + E --> F[MethodEmitter IL生成器] + + %% 缓存层 + F --> G[AssemblyCache 缓存层] + + %% 执行层 + G --> H[PluginManager 插件管理器] + H --> I[PluginCallInfo 调用信息] + + %% 输出层 + I --> J[动态程序集输出] + + %% 配置层(独立) + K[Configuration 配置层] -.-> B + K -.-> D + K -.-> G + K -.-> H + + %% 样式 + classDef inputLayer fill:#e1f5fe + classDef parseLayer fill:#f3e5f5 + classDef generateLayer fill:#e8f5e8 + classDef cacheLayer fill:#fff3e0 + classDef executeLayer fill:#fce4ec + classDef outputLayer fill:#e0f2f1 + classDef configLayer fill:#f1f8e9 + + class A,B inputLayer + class C,D parseLayer + class E,F generateLayer + class G cacheLayer + class H,I executeLayer + class J outputLayer + class K configLayer +``` + +### 🔧 重构后的类职责 + +| 层级 | 类名 | 职责 | 输入 | 输出 | +|------|------|------|------|------| +| **输入层** | Parser | 统一入口,参数验证 | JSON/PluginInfo | 验证后的数据 | +| **解析层** | TypeMapper | 类型映射,验证 | 类型字符串 | System.Type | +| **生成层** | AssemblyBuilder | 协调生成过程 | PluginInfo | IL代码 | +| | MethodEmitter | 纯IL生成 | 类型信息 | IL指令 | +| **缓存层** | AssemblyCache | 纯缓存管理 | 程序集 | 缓存的程序集 | +| **执行层** | PluginManager | 插件调用 | 调用信息 | 执行结果 | +| **配置层** | Configuration | 配置管理 | 配置数据 | 配置实例 | + +## 🛠️ 具体重构策略 + +### 1. 引入依赖注入容器 + +```csharp +// 替代静态依赖 +public class ServiceContainer +{ + public void ConfigureServices() + { + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + } +} +``` + +### 2. 接口隔离原则 + +```csharp +// 明确的接口定义 +public interface IAssemblyBuilder +{ + Assembly BuildFromPlugins(List plugins, string assemblyName); +} + +public interface IMethodEmitter +{ + void EmitMethod(TypeBuilder typeBuilder, Function function); +} + +public interface ITypeMapper +{ + Type MapType(string typeName); + void RegisterCustomType(string typeName, Type type); +} +``` + +### 3. 事件驱动通信 + +```csharp +// 替代直接调用 +public class AssemblyBuilder +{ + public event EventHandler AssemblyGenerated; + public event EventHandler MethodGenerated; + + public Assembly Build(List plugins) + { + // 生成逻辑 + foreach (var plugin in plugins) + { + foreach (var function in plugin.Functions) + { + MethodGenerated?.Invoke(this, new MethodGeneratedEventArgs(function)); + } + } + + var assembly = // ... 生成程序集 + AssemblyGenerated?.Invoke(this, new AssemblyGeneratedEventArgs(assembly)); + return assembly; + } +} +``` + +### 4. 配置管理分离 + +```csharp +public class ParserConfiguration +{ + public IPluginManager PluginManager { get; set; } + public bool UseCache { get; set; } = true; + public string DefaultAssemblyName { get; set; } = "DynamicPluginAssembly"; + public TimeSpan CacheTimeout { get; set; } = TimeSpan.FromHours(1); +} + +public class ConfigurationManager +{ + public static ParserConfiguration GetConfiguration() + { + // 从配置文件、环境变量等读取配置 + return new ParserConfiguration + { + UseCache = bool.Parse(Environment.GetEnvironmentVariable("KSCRIPT_CACHE") ?? "true"), + DefaultAssemblyName = Environment.GetEnvironmentVariable("KSCRIPT_ASSEMBLY_NAME") ?? "DynamicPluginAssembly" + }; + } +} +``` + +## 🔄 重构后的数据流 + +### 程序集生成流程 + +```mermaid +sequenceDiagram + participant Client + participant Parser + participant AssemblyBuilder + participant TypeMapper + participant MethodEmitter + participant AssemblyCache + participant PluginManager + + Client->>Parser: Generate(plugins) + Parser->>Parser: ValidateInput() + Parser->>AssemblyBuilder: Build(plugins) + + AssemblyBuilder->>TypeMapper: MapType(typeName) + TypeMapper-->>AssemblyBuilder: Type + + AssemblyBuilder->>MethodEmitter: EmitMethod() + MethodEmitter-->>AssemblyBuilder: IL Instructions + + AssemblyBuilder->>AssemblyCache: GetOrCreate(assembly) + AssemblyCache-->>AssemblyBuilder: Cached Assembly + + AssemblyBuilder-->>Parser: Assembly + Parser->>PluginManager: SetContext(assembly) + Parser-->>Client: Assembly +``` + +### 插件调用流程 + +```mermaid +sequenceDiagram + participant Script + participant GeneratedMethod + participant PluginManager + participant PluginConnector + participant PluginProcess + + Script->>GeneratedMethod: Plugin.Method(args) + GeneratedMethod->>PluginManager: Call(PluginCallInfo) + + PluginManager->>PluginConnector: FindConnector() + PluginManager->>PluginConnector: SendRequest() + PluginConnector->>PluginProcess: WebSocket Communication + PluginProcess-->>PluginConnector: Response + PluginConnector-->>PluginManager: HandleResponse() + PluginManager-->>GeneratedMethod: Result + GeneratedMethod-->>Script: Result +``` + +## ✅ 重构收益 + +1. **清晰的职责分离** - 每个类只负责一个明确的功能 +2. **单向数据流** - 避免循环依赖和混乱的调用关系 +3. **易于测试** - 每层可以独立测试 +4. **易于维护** - 修改一层不会影响其他层 +5. **可扩展性** - 新功能可以通过添加新层实现 +6. **配置灵活性** - 通过依赖注入支持不同的运行环境 + +## 🎯 实施步骤 + +1. **第一阶段:接口定义** + - 定义各层的接口 + - 确定数据契约 + +2. **第二阶段:依赖注入** + - 引入DI容器 + - 重构静态依赖 + +3. **第三阶段:事件驱动** + - 替换直接调用为事件 + - 实现观察者模式 + +4. **第四阶段:配置管理** + - 分离配置逻辑 + - 支持多环境配置 + +5. **第五阶段:测试验证** + - 单元测试各层 + - 集成测试整体流程 + +这样的重构将显著提升 KitX.CSharp.Parser 项目的可维护性和可扩展性,同时保持现有功能的完整性。 diff --git a/KitX Script/Kscript.CSharp.Parser/CLASS_DIAGRAM.md b/KitX Script/Kscript.CSharp.Parser/CLASS_DIAGRAM.md new file mode 100644 index 0000000..1ccf714 --- /dev/null +++ b/KitX Script/Kscript.CSharp.Parser/CLASS_DIAGRAM.md @@ -0,0 +1,343 @@ +# KitX.CSharp.Parser 类图与调用关系 + +## 📊 整体架构类图 + +```mermaid +classDiagram + %% 外部依赖 + class KitXDashboard { + +PluginsServer.Instance + +WorkflowScriptService + } + + class KitXShared { + <> + +PluginInfo + +Function + +Parameter + +Connector + +Request + } + + %% 主入口类 + class Parser { + <> + -_defaultPluginManager: IPluginManager + -_pluginManagerFactory: Func~IPluginManager~ + +Generate(plugins, assemblyName, pluginManager, useCache): Assembly + +GenerateFromJson(jsonString, assemblyName, pluginManager, useCache): Assembly + +GenerateFromFileAsync(jsonFilePath, assemblyName, pluginManager, useCache): Task~Assembly~ + +Regenerate(plugins, assemblyName, pluginManager): Assembly + +SetDefaultPluginManager(pluginManager): void + +SetPluginManagerFactory(factory): void + +RegisterCustomType(typeName, type): void + +ClearCache(): void + +GetCacheStatistics(): CacheStatistics + +HasCache(plugins, assemblyName): bool + +GetPluginTypes(assembly): List~Type~ + +GetPluginMethods(pluginType): List~MethodInfo~ + } + + %% 核心接口 + class IPluginManager { + <> + +Call~T~(callInfo): T + +Call(callInfo): void + +IsPluginExists(pluginName): bool + +IsMethodExists(pluginName, methodName): bool + } + + %% 插件管理器实现 + class MockPluginManager { + -_mockData: Dictionary~string, Dictionary~ + +Call~T~(callInfo): T + +Call(callInfo): void + +IsPluginExists(pluginName): bool + +IsMethodExists(pluginName, methodName): bool + -InvokeMockMethod(callInfo, method): object + -GetDefaultValue(type): object + } + + class RealPluginManager { + -_pluginsServer: object + -_infoLogger: Action~string~ + -_errorLogger: Action~string~ + -_pendingRequests: ConcurrentDictionary~string, TaskCompletionSource~ + +Call~T~(callInfo): T + +Call(callInfo): void + +IsPluginExists(pluginName): bool + +IsMethodExists(pluginName, methodName): bool + -FindPluginInfo(pluginName): PluginInfo + -FindPluginConnector(pluginInfo): object + -SendPluginRequest~T~(connector, callInfo): Task~T~ + -SendRequestToConnector(connector, request): Task + +HandlePluginResponse(requestId, responseJson): void + -GetDefaultResult~T~(): T + } + + %% 代码生成组件 + class MethodEmitter { + <> + -_staticPluginManager: IPluginManager + +SetStaticPluginManager(pluginManager): void + +GenerateAssembly(plugins, assemblyName, pluginManager): Assembly + -GeneratePluginClass(moduleBuilder, plugin, pluginManager): void + -GenerateStaticConstructor(typeBuilder, pluginManagerField, pluginManager): void + -GeneratePluginMethod(typeBuilder, pluginName, function, pluginManagerField): void + -GenerateMethodBody(methodBuilder, pluginName, function, parameterTypes, returnType, pluginManagerField): void + -ConvertDefaultValue(value, targetType): object + } + + class TypeMapper { + <> + -_basicTypeMap: Dictionary~string, Type~ + -_customTypeMap: ConcurrentDictionary~string, Type~ + -_typeCache: ConcurrentDictionary~string, Type~ + -_genericTypeRegex: Regex + +MapType(typeName): Type + -ResolveGenericType(genericMatch): Type + -ParseGenericArguments(argsString): Type[] + -SplitGenericArguments(argsString): string[] + +RegisterCustomType(typeName, type): void + +ClearCache(): void + +GetCustomTypes(): IReadOnlyDictionary~string, Type~ + } + + class AssemblyCache { + <> + -_assemblyCache: ConcurrentDictionary~string, Assembly~ + -_referenceCount: ConcurrentDictionary~string, int~ + -_cacheLock: object + +GetOrCreateAssembly(plugins, assemblyName, pluginManager): Assembly + -GenerateCacheKey(plugins, assemblyName): string + -IncrementReference(cacheKey): void + +DecrementReference(cacheKey): void + +ClearCache(): void + +GetStatistics(): CacheStatistics + +HasCache(plugins, assemblyName): bool + +ForceRegenerate(plugins, assemblyName, pluginManager): Assembly + } + + %% 数据模型 + class PluginCallInfo { + +PluginName: string + +MethodName: string + +Parameters: object[] + +ParameterTypes: Type[] + +ToString(): string + } + + class CacheStatistics { + +CachedAssemblyCount: int + +TotalReferences: int + +CacheKeys: List~string~ + +ToString(): string + } + + %% 异常类 + class ParserException { + +ParserException() + +ParserException(message) + +ParserException(message, innerException) + +TypeMappingError(typeName, innerException): ParserException + +ILGenerationError(methodName, innerException): ParserException + +AssemblyGenerationError(assemblyName, innerException): ParserException + } + + %% 示例类 + class BasicUsageExample { + <> + +RunExample(): Task + -RunWithBuiltInData(): void + -DynamicInvokeExample(assembly): void + -GenerateTestArguments(methodName, parameters): object[] + -GetDefaultValue(type): object + +RunAdvancedExample(): void + } + + class RealPluginManagerExample { + <> + +RunExample(): void + +DashboardIntegrationExample(): void + } + + %% 关系定义 + Parser --> IPluginManager : 使用 + Parser --> TypeMapper : 使用 + Parser --> AssemblyCache : 使用 + Parser --> MethodEmitter : 调用 + Parser --> CacheStatistics : 返回 + + IPluginManager <|.. MockPluginManager : 实现 + IPluginManager <|.. RealPluginManager : 实现 + + MockPluginManager --> PluginCallInfo : 使用 + RealPluginManager --> PluginCallInfo : 使用 + RealPluginManager --> KitXDashboard : 依赖 + RealPluginManager --> KitXShared : 使用 + + MethodEmitter --> IPluginManager : 使用 + MethodEmitter --> TypeMapper : 使用 + MethodEmitter --> PluginCallInfo : 使用 + MethodEmitter --> ParserException : 抛出 + + TypeMapper --> ParserException : 抛出 + + AssemblyCache --> MethodEmitter : 调用 + AssemblyCache --> CacheStatistics : 返回 + + BasicUsageExample --> Parser : 使用 + RealPluginManagerExample --> Parser : 使用 + RealPluginManagerExample --> RealPluginManager : 使用 + + KitXDashboard --> RealPluginManager : 创建 +``` + +## 🔄 调用时序图 + +### 基本生成流程 + +```mermaid +sequenceDiagram + participant Client + participant Parser + participant AssemblyCache + participant MethodEmitter + participant TypeMapper + participant IPluginManager + + Client->>Parser: Generate(plugins) + Parser->>AssemblyCache: GetOrCreateAssembly() + + alt 缓存命中 + AssemblyCache-->>Parser: 返回缓存的Assembly + else 缓存未命中 + AssemblyCache->>MethodEmitter: GenerateAssembly() + + loop 对每个插件 + MethodEmitter->>TypeMapper: MapType(typeName) + TypeMapper-->>MethodEmitter: 返回Type + + MethodEmitter->>MethodEmitter: GeneratePluginClass() + MethodEmitter->>MethodEmitter: GenerateStaticConstructor() + + loop 对每个功能 + MethodEmitter->>MethodEmitter: GeneratePluginMethod() + MethodEmitter->>MethodEmitter: GenerateMethodBody() + MethodEmitter->>IPluginManager: SetStaticPluginManager() + end + end + + MethodEmitter-->>AssemblyCache: 返回新Assembly + AssemblyCache-->>Parser: 返回Assembly + end + + Parser-->>Client: 返回Assembly +``` + +### 真实插件调用流程 + +```mermaid +sequenceDiagram + participant Script + participant GeneratedMethod + participant RealPluginManager + participant PluginConnector + participant PluginProcess + participant KitXDashboard + + Script->>GeneratedMethod: SampleCalculator.Add(10, 20) + GeneratedMethod->>RealPluginManager: Call(PluginCallInfo) + + RealPluginManager->>KitXDashboard: FindPluginInfo() + KitXDashboard-->>RealPluginManager: PluginInfo + + RealPluginManager->>KitXDashboard: FindPluginConnector() + KitXDashboard-->>RealPluginManager: PluginConnector + + RealPluginManager->>PluginConnector: SendRequest(Request) + PluginConnector->>PluginProcess: WebSocket 通信 + PluginProcess-->>PluginConnector: Response + PluginConnector-->>RealPluginManager: HandleResponse() + + RealPluginManager-->>GeneratedMethod: 返回结果 + GeneratedMethod-->>Script: 返回结果 +``` + +## 📋 类职责说明 + +### 核心类 + +| 类名 | 职责 | 关键方法 | +|------|------|----------| +| **Parser** | 主入口,统一API | `Generate()`, `GenerateFromJson()`, `GenerateFromFileAsync()` | +| **IPluginManager** | 插件管理器接口 | `Call()`, `IsPluginExists()` | +| **MockPluginManager** | 模拟插件管理器 | `Call()`, `InvokeMockMethod()` | +| **RealPluginManager** | 真实插件管理器 | `Call()`, `SendPluginRequest()` | + +### 代码生成类 + +| 类名 | 职责 | 关键方法 | +|------|------|----------| +| **MethodEmitter** | IL代码生成 | `GenerateAssembly()`, `GeneratePluginClass()` | +| **TypeMapper** | 类型映射 | `MapType()`, `RegisterCustomType()` | +| **AssemblyCache** | 程序集缓存 | `GetOrCreateAssembly()`, `ForceRegenerate()` | + +### 数据模型类 + +| 类名 | 职责 | 关键属性 | +|------|------|----------| +| **PluginCallInfo** | 插件调用信息 | `PluginName`, `MethodName`, `Parameters` | +| **CacheStatistics** | 缓存统计信息 | `CachedAssemblyCount`, `TotalReferences` | + +## 🔧 设计模式应用 + +### 1. 工厂模式 +- `Parser.SetPluginManagerFactory()` - 创建插件管理器实例 +- `MethodEmitter.GenerateAssembly()` - 创建动态程序集 + +### 2. 策略模式 +- `IPluginManager` 接口 - 不同的插件管理器实现策略 +- `MockPluginManager` vs `RealPluginManager` - 测试vs生产策略 + +### 3. 单例模式 +- `AssemblyCache` - 静态缓存管理 +- `TypeMapper` - 静态类型映射 + +### 4. 建造者模式 +- `MethodEmitter` 中的 IL 生成过程 +- `Connector` 的请求构建过程 + +### 5. 模板方法模式 +- `Parser.Generate()` 方法的通用流程 +- 插件方法生成的标准流程 + +## 🎯 关键调用路径 + +### 1. 程序集生成路径 +``` +Parser.Generate() +→ AssemblyCache.GetOrCreateAssembly() +→ MethodEmitter.GenerateAssembly() +→ TypeMapper.MapType() +→ IL生成 +``` + +### 2. 插件调用路径 +``` +生成的静态方法 +→ IPluginManager.Call() +→ RealPluginManager.SendPluginRequest() +→ WebSocket通信 +→ 插件进程 +``` + +### 3. 缓存管理路径 +``` +AssemblyCache.GenerateCacheKey() +→ SHA256哈希计算 +→ 缓存查找/存储 +→ 引用计数管理 +``` + +这个类图展示了 KitX.CSharp.Parser 项目的完整架构,包括各个类之间的依赖关系、调用顺序和设计模式的应用。 diff --git a/KitX Script/Kscript.CSharp.Parser/CodeGen/MethodEmitter.cs b/KitX Script/Kscript.CSharp.Parser/CodeGen/MethodEmitter.cs index f4921fd..7e9f713 100644 --- a/KitX Script/Kscript.CSharp.Parser/CodeGen/MethodEmitter.cs +++ b/KitX Script/Kscript.CSharp.Parser/CodeGen/MethodEmitter.cs @@ -9,6 +9,19 @@ namespace Kscript.CSharp.Parser.CodeGen; /// public static class MethodEmitter { + /// + /// 静态插件管理器实例,用于在生成的程序集中使用 + /// + private static IPluginManager? _staticPluginManager; + + /// + /// 设置静态插件管理器实例 + /// + /// 插件管理器实例 + public static void SetStaticPluginManager(IPluginManager pluginManager) + { + _staticPluginManager = pluginManager; + } /// /// 为插件生成动态程序集 /// @@ -21,7 +34,7 @@ public static Assembly GenerateAssembly(List plugins, string assembl try { // 创建动态程序集 - var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly( + var assemblyBuilder = System.Reflection.Emit.AssemblyBuilder.DefineDynamicAssembly( new AssemblyName(assemblyName), AssemblyBuilderAccess.Run); @@ -89,10 +102,13 @@ private static void GenerateStaticConstructor(TypeBuilder typeBuilder, FieldBuil var il = constructorBuilder.GetILGenerator(); - if (pluginManager != null) + // 优先使用静态插件管理器实例 + var managerToUse = _staticPluginManager ?? pluginManager; + + if (managerToUse != null) { // 如果提供了插件管理器实例,直接使用 - il.Emit(OpCodes.Ldtoken, pluginManager.GetType()); + il.Emit(OpCodes.Ldtoken, managerToUse.GetType()); il.Emit(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle")!); il.Emit(OpCodes.Call, typeof(Activator).GetMethod("CreateInstance", new[] { typeof(Type) })!); il.Emit(OpCodes.Castclass, typeof(IPluginManager)); diff --git a/KitX Script/Kscript.CSharp.Parser/Core/RealPluginManager.cs b/KitX Script/Kscript.CSharp.Parser/Core/RealPluginManager.cs new file mode 100644 index 0000000..3626efa --- /dev/null +++ b/KitX Script/Kscript.CSharp.Parser/Core/RealPluginManager.cs @@ -0,0 +1,353 @@ +using System.Text.Json; +using Kscript.CSharp.Parser.Models; +using KitX.Shared.CSharp.Plugin; +using KitX.Shared.CSharp.WebCommand; + +using Serilog; +using System.Collections.Concurrent; + +namespace Kscript.CSharp.Parser.Core; + +/// +/// 实际的插件管理器 - 使用 KitX Dashboard 的插件系统进行真实调用 +/// +public class RealPluginManager : IPluginManager +{ + private readonly object _pluginsServer; + private readonly Action _infoLogger; + private readonly Action _errorLogger; + + private readonly JsonSerializerOptions _serializerOptions = new() + { + WriteIndented = true, + IncludeFields = true, + PropertyNameCaseInsensitive = true, + }; + + // 用于存储等待响应的请求 + private readonly ConcurrentDictionary> _pendingRequests = new(); + + /// + /// 构造函数 + /// + /// PluginsServer 实例 + /// 信息日志记录器 + /// 错误日志记录器 + public RealPluginManager(object pluginsServer, Action? infoLogger = null, Action? errorLogger = null) + { + _pluginsServer = pluginsServer ?? throw new ArgumentNullException(nameof(pluginsServer)); + _infoLogger = infoLogger ?? (message => Console.WriteLine(message)); + _errorLogger = errorLogger ?? (message => Console.WriteLine(message)); + + _infoLogger("[WorkflowScriptService] 正在初始化 RealPluginManager..."); + } + + /// + /// 调用插件方法 + /// + public T Call(PluginCallInfo callInfo) + { + try + { + _infoLogger($"[RealPluginManager] 开始调用插件方法: {callInfo}"); + + // 查找插件信息 + var pluginInfo = FindPluginInfo(callInfo.PluginName); + if (pluginInfo == null) + { + _infoLogger($"[RealPluginManager] 未找到插件: {callInfo.PluginName}"); + return GetDefaultResult(); + } + + // 查找插件连接器 + var connector = FindPluginConnector(pluginInfo); + if (connector == null) + { + _infoLogger($"[RealPluginManager] 插件 {callInfo.PluginName} 未连接"); + return GetDefaultResult(); + } + + // 创建并发送请求 + var result = SendPluginRequest(connector, callInfo).GetAwaiter().GetResult(); + + _infoLogger($"[RealPluginManager] 插件调用完成: {callInfo} -> 结果: {result}"); + return result; + } + catch (Exception ex) + { + _errorLogger($"[RealPluginManager] 调用插件方法失败: {callInfo} - 异常: {ex.Message}"); + return GetDefaultResult(); + } + } + + /// + /// 调用插件方法(无返回值) + /// + public void Call(PluginCallInfo callInfo) + { + Call(callInfo); + } + + /// + /// 检查插件是否存在 + /// + public bool IsPluginExists(string pluginName) + { + try + { + var pluginInfo = FindPluginInfo(pluginName); + var exists = pluginInfo != null; + + _infoLogger($"[RealPluginManager] 检查插件 '{pluginName}' 是否存在: {exists}"); + return exists; + } + catch (Exception ex) + { + _errorLogger($"[RealPluginManager] 检查插件存在性失败: {pluginName} - 异常: {ex.Message}"); + return false; + } + } + + /// + /// 检查插件方法是否存在 + /// + public bool IsMethodExists(string pluginName, string methodName) + { + try + { + var pluginInfo = FindPluginInfo(pluginName); + var exists = pluginInfo?.Functions.Any(f => f.Name == methodName) ?? false; + + _infoLogger($"[RealPluginManager] 检查方法 '{pluginName}.{methodName}' 是否存在: {exists}"); + return exists; + } + catch (Exception ex) + { + _errorLogger($"[RealPluginManager] 检查方法存在性失败: {pluginName}.{methodName} - 异常: {ex.Message}"); + return false; + } + } + + /// + /// 查找插件信息 + /// + private PluginInfo? FindPluginInfo(string pluginName) + { + try + { + // 通过传入的 PluginsServer 实例查找插件信息 + var pluginsServerType = _pluginsServer.GetType(); + var pluginConnectorsProperty = pluginsServerType.GetProperty("PluginConnectors"); + + if (pluginConnectorsProperty != null) + { + var pluginConnectors = pluginConnectorsProperty.GetValue(_pluginsServer) as System.Collections.IList; + if (pluginConnectors != null) + { + foreach (var connector in pluginConnectors) + { + if (connector != null) + { + var connectorType = connector.GetType(); + var pluginInfoProperty = connectorType.GetProperty("PluginInfo"); + if (pluginInfoProperty != null) + { + var pluginInfo = pluginInfoProperty.GetValue(connector) as PluginInfo; + if (pluginInfo != null && pluginInfo.Name == pluginName) + { + return pluginInfo; + } + } + } + } + } + } + } + catch (Exception ex) + { + _errorLogger($"[RealPluginManager] 查找插件信息失败: {pluginName} - 异常: {ex.Message}"); + } + + return null; + } + + /// + /// 查找插件连接器 + /// + private object? FindPluginConnector(PluginInfo pluginInfo) + { + try + { + var pluginsServerType = _pluginsServer.GetType(); + var findConnectorMethod = pluginsServerType.GetMethod("FindConnector", new[] { typeof(PluginInfo) }); + + if (findConnectorMethod != null) + { + return findConnectorMethod.Invoke(_pluginsServer, new object[] { pluginInfo }); + } + } + catch (Exception ex) + { + _errorLogger($"[RealPluginManager] 查找插件连接器失败: {pluginInfo.Name} - 异常: {ex.Message}"); + } + + return null; + } + + /// + /// 发送插件请求 + /// + private async Task SendPluginRequest(object connector, PluginCallInfo callInfo) + { + try + { + // 创建 Connector 实例 + var connectorInstance = new Connector() + .SetSerializer(x => JsonSerializer.Serialize(x, _serializerOptions)) + .SetSender(request => { + SendRequestToConnector(connector, request).ConfigureAwait(false); + }); + + // 构建参数列表 + var functionArgs = new List(); + for (int i = 0; i < callInfo.Parameters.Length; i++) + { + var paramType = callInfo.ParameterTypes[i]; + var paramValue = callInfo.Parameters[i]; + + functionArgs.Add(new Parameter + { + Name = $"param{i}", + Type = paramType.Name, + Value = paramValue?.ToString() ?? string.Empty, + IsOptional = false + }); + } + + // 生成唯一的请求ID + var requestId = Guid.NewGuid().ToString(); + + // 创建任务完成源,用于等待响应 + var tcs = new TaskCompletionSource(); + _pendingRequests[requestId] = tcs; + + // 发送请求 + var request = connectorInstance + .Request() + .ReceiveCommand() + .UpdateCommand(cmd => + { + cmd.FunctionName = callInfo.MethodName; + cmd.FunctionArgs = functionArgs; + cmd.PluginConnectionId = callInfo.PluginName; + cmd.Tags = new Dictionary + { + ["RequestId"] = requestId + }; + return cmd; + }) + .Send(); + + // 等待响应,设置超时时间为30秒 + var timeout = TimeSpan.FromSeconds(30); + var completedTask = await Task.WhenAny(tcs.Task, Task.Delay(timeout)); + + if (completedTask == tcs.Task) + { + // 获取到响应 + var responseJson = tcs.Task.Result; + _infoLogger($"[RealPluginManager] 收到插件响应: {responseJson}"); + + // 尝试反序列化响应 + try + { + if (typeof(T) == typeof(string)) + { + return (T)(object)responseJson; + } + else if (typeof(T) == typeof(void)) + { + return default(T)!; + } + else + { + return JsonSerializer.Deserialize(responseJson, _serializerOptions) ?? GetDefaultResult(); + } + } + catch (JsonException ex) + { + _errorLogger($"[RealPluginManager] 反序列化响应失败: {ex.Message}"); + return GetDefaultResult(); + } + } + else + { + // 超时 + _errorLogger($"[RealPluginManager] 插件调用超时: {callInfo.PluginName}.{callInfo.MethodName}"); + _pendingRequests.TryRemove(requestId, out _); + return GetDefaultResult(); + } + } + catch (Exception ex) + { + _errorLogger($"[RealPluginManager] 发送插件请求失败: {callInfo.PluginName}.{callInfo.MethodName} - 异常: {ex.Message}"); + return GetDefaultResult(); + } + } + + /// + /// 向连接器发送请求 + /// + private async Task SendRequestToConnector(object connector, Request request) + { + try + { + var connectorType = connector.GetType(); + var requestMethod = connectorType.GetMethod("Request", new[] { typeof(Request) }); + if (requestMethod != null) + { + var result = requestMethod.Invoke(connector, new object[] { request }); + if (result is Task task) + { + await task; + } + } + } + catch (Exception ex) + { + _errorLogger($"[RealPluginManager] 向连接器发送请求失败 - 异常: {ex.Message}"); + } + } + + /// + /// 处理插件响应 + /// + public void HandlePluginResponse(string requestId, string responseJson) + { + if (_pendingRequests.TryRemove(requestId, out var tcs)) + { + tcs.SetResult(responseJson); + } + else + { + _infoLogger($"[RealPluginManager] 收到未知请求的响应: {requestId}"); + } + } + + /// + /// 获取默认结果 + /// + private T GetDefaultResult() + { + if (typeof(T) == typeof(void)) + return default(T)!; + + if (typeof(T) == typeof(string)) + return (T)(object)string.Empty; + + if (typeof(T).IsValueType) + return (T)Activator.CreateInstance(typeof(T))!; + + return default(T)!; + } +} diff --git a/KitX Script/Kscript.CSharp.Parser/Examples/Program.cs b/KitX Script/Kscript.CSharp.Parser/Examples/Program.cs index 5598f27..51b3d0f 100644 --- a/KitX Script/Kscript.CSharp.Parser/Examples/Program.cs +++ b/KitX Script/Kscript.CSharp.Parser/Examples/Program.cs @@ -24,6 +24,13 @@ public static async Task Main(string[] args) // 运行高级功能示例 BasicUsageExample.RunAdvancedExample(); + // 运行实际插件管理器示例 + Console.WriteLine("\n" + new string('=', 60)); + RealPluginManagerExample.RunExample(); + + Console.WriteLine("\n" + new string('=', 60)); + RealPluginManagerExample.DashboardIntegrationExample(); + Console.WriteLine("\n=================================================="); Console.WriteLine("✅ 所有示例运行完成!"); } diff --git a/KitX Script/Kscript.CSharp.Parser/Examples/RealPluginManagerExample.cs b/KitX Script/Kscript.CSharp.Parser/Examples/RealPluginManagerExample.cs new file mode 100644 index 0000000..06a7070 --- /dev/null +++ b/KitX Script/Kscript.CSharp.Parser/Examples/RealPluginManagerExample.cs @@ -0,0 +1,131 @@ +using Kscript.CSharp.Parser; +using Kscript.CSharp.Parser.Core; +using System.Text.Json; + +namespace Kscript.CSharp.Parser.Examples; + +/// +/// 实际插件管理器使用示例 +/// +public static class RealPluginManagerExample +{ + /// + /// 演示如何使用 RealPluginManager + /// + public static void RunExample() + { + Console.WriteLine("=== KitX.CSharp.Parser 实际插件管理器使用示例 ===\n"); + + try + { + // 1. 设置插件管理器工厂函数 + Console.WriteLine("1. 设置插件管理器工厂函数..."); + + // 这里应该传入实际的 PluginsServer 实例和日志记录器 + // 由于这是一个示例,我们使用 null 作为占位符 + Parser.SetPluginManagerFactory(() => + { + // 在实际使用中,这里应该传入真实的 PluginsServer 实例和日志记录器 + // 例如: + // var pluginsServer = KitX.Dashboard.Network.PluginsNetwork.PluginsServer.Instance; + // var logger = Serilog.Log.Logger; + // return new RealPluginManager(pluginsServer, message => logger.Information(message)); + + Console.WriteLine(" 创建 RealPluginManager 实例..."); + return null; // 示例中返回 null,实际使用时返回真实实例 + }); + + Console.WriteLine(" ✓ 插件管理器工厂函数已设置"); + + // 2. 创建示例插件数据 + Console.WriteLine("\n2. 创建示例插件数据..."); + var examplePlugins = new List + { + new PluginInfo + { + Name = "TestPlugin", + Version = "1.0.0", + Functions = new List + { + new Function + { + Name = "TestMethod", + ReturnValueType = "string", + Parameters = new List + { + new Parameter { Name = "input", Type = "string", IsOptional = false } + } + } + } + } + }; + + Console.WriteLine(" ✓ 示例插件数据已创建"); + + // 3. 生成动态程序集 + Console.WriteLine("\n3. 生成动态程序集..."); + var assembly = Parser.Generate(examplePlugins, "RealPluginManagerExampleAssembly"); + Console.WriteLine($" ✓ 成功生成程序集: {assembly.FullName}"); + + // 4. 获取生成的插件类型 + Console.WriteLine("\n4. 分析生成的插件类型..."); + var pluginTypes = Parser.GetPluginTypes(assembly); + Console.WriteLine($" 发现 {pluginTypes.Count} 个插件类:"); + + foreach (var type in pluginTypes) + { + Console.WriteLine($" - {type.Name}"); + var methods = Parser.GetPluginMethods(type); + foreach (var method in methods) + { + var parameters = string.Join(", ", method.GetParameters().Select(p => $"{p.ParameterType.Name} {p.Name}")); + Console.WriteLine($" └── {method.ReturnType.Name} {method.Name}({parameters})"); + } + } + + Console.WriteLine("\n=== 示例完成 ==="); + Console.WriteLine("\n注意:在实际使用中,您需要:"); + Console.WriteLine("1. 确保 KitX Dashboard 正在运行"); + Console.WriteLine("2. 传入真实的 PluginsServer.Instance"); + Console.WriteLine("3. 传入真实的日志记录器实例"); + Console.WriteLine("4. 确保插件已正确连接到 Dashboard"); + } + catch (Exception ex) + { + Console.WriteLine($"❌ 示例执行失败: {ex.Message}"); + Console.WriteLine($"详细信息: {ex}"); + } + } + + /// + /// 演示如何在 KitX Dashboard 中集成 + /// + public static void DashboardIntegrationExample() + { + Console.WriteLine("=== KitX Dashboard 集成示例 ===\n"); + + Console.WriteLine("在 KitX Dashboard 中使用 RealPluginManager 的步骤:\n"); + + Console.WriteLine("1. 在 Dashboard 启动时设置插件管理器工厂:"); + Console.WriteLine("```csharp"); + Console.WriteLine("// 在 Dashboard 的初始化代码中"); + Console.WriteLine("Parser.SetPluginManagerFactory(() => new RealPluginManager("); + Console.WriteLine(" KitX.Dashboard.Network.PluginsNetwork.PluginsServer.Instance,"); + Console.WriteLine(" message => Log.Information(message)"); + Console.WriteLine("));"); + Console.WriteLine("```"); + + Console.WriteLine("\n2. 生成插件程序集:"); + Console.WriteLine("```csharp"); + Console.WriteLine("var assembly = Parser.Generate(plugins, \"DashboardPluginAssembly\");"); + Console.WriteLine("```"); + + Console.WriteLine("\n3. 在脚本中使用生成的 API:"); + Console.WriteLine("```csharp"); + Console.WriteLine("// 现在可以在脚本中直接调用"); + Console.WriteLine("var result = TestPlugin.TestMethod(\"Hello World\");"); + Console.WriteLine("```"); + + Console.WriteLine("\n这样,脚本中的插件调用将通过真实的 KitX Dashboard 插件系统执行!"); + } +} diff --git a/KitX Script/Kscript.CSharp.Parser/INTEGRATION_GUIDE.md b/KitX Script/Kscript.CSharp.Parser/INTEGRATION_GUIDE.md new file mode 100644 index 0000000..4fd92b9 --- /dev/null +++ b/KitX Script/Kscript.CSharp.Parser/INTEGRATION_GUIDE.md @@ -0,0 +1,163 @@ +# KitX.CSharp.Parser 与 KitX Dashboard 集成指南 + +## 🎯 集成概述 + +本文档说明如何将 KitX.CSharp.Parser 的实际插件管理器集成到 KitX Dashboard 中,实现从 MockPluginManager 到真实插件调用的升级。 + +## 📋 完成的集成工作 + +### 1. 创建 RealPluginManager +- ✅ **文件**: [`Core/RealPluginManager.cs`](../Core/RealPluginManager.cs:1-225) +- ✅ **功能**: 实现了 `IPluginManager` 接口,支持真实的 KitX Dashboard 插件调用 +- ✅ **设计**: 采用依赖注入模式,接受外部传入的 `PluginsServer` 实例和日志记录器 +- ✅ **兼容性**: 完全兼容现有的 MockPluginManager 接口 + +### 2. 扩展 MethodEmitter +- ✅ **静态支持**: 在 [`CodeGen/MethodEmitter.cs`](../CodeGen/MethodEmitter.cs:13-21) 中添加了静态插件管理器支持 +- ✅ **工厂模式**: 实现了 `SetStaticPluginManager()` 方法 +- ✅ **灵活配置**: 支持多种插件管理器实现方式 + +### 3. 更新 Parser 主入口 +- ✅ **工厂函数**: 在 [`Parser.cs`](../Parser.cs:23-32) 中添加了 `SetPluginManagerFactory()` 方法 +- ✅ **向后兼容**: 保持与现有 `SetDefaultPluginManager()` 的完全兼容 +- ✅ **自动设置**: 在生成程序集时自动设置静态插件管理器 + +### 4. 集成到 Dashboard +- ✅ **WorkflowScriptService**: 修改了 [`Services/WorkflowScriptService.cs`](../../KitX%20Clients/KitX%20Dashboard/KitX%20Dashboard/Services/WorkflowScriptService.cs:17-52) +- ✅ **静态构造**: 在静态构造函数中设置 RealPluginManager 工厂 +- ✅ **实例传递**: 将PluginManager实例传递给 Parser +- ✅ **错误处理**: 添加了完善的异常处理和日志记录 + +## 🔧 集成架构 + +``` +┌─────────────────────────────────────────────────┐ +│ KitX Dashboard │ +│ ┌─────────────────────────────────────┐ │ +│ │ WorkflowScriptService │ │ +│ │ ┌─ SetPluginManagerFactory │ │ +│ │ │ │ │ │ +│ │ │ ▼ │ │ +│ │ │ RealPluginManager │ │ +│ │ │ ┌─ PluginsServer │ │ +│ │ │ │ ┌─ FindConnector │ │ +│ │ │ │ │ │ │ +│ │ │ │ ▼ │ │ +│ │ │ │ PluginConnector │ │ +│ │ │ │ ┌─ Request │ │ +│ │ │ │ │ │ │ +│ │ │ │ ▼ │ │ +│ │ │ │ WebSocket │ │ +│ │ │ └─ Plugin Process │ │ +│ │ └─────────────────────────────┘ │ +│ │ │ │ +│ │ ▼ Generated Assembly │ │ +│ │ ┌─ MethodEmitter │ │ +│ │ │ │ ┌─ Static Constructor │ │ +│ │ │ │ │ │ │ +│ │ │ │ ▼ │ │ +│ │ │ │ │ SetStaticPluginManager │ │ +│ │ │ │ └─────────────────────┘ │ +│ │ │ │ │ +│ │ ▼ Script Engine │ │ +│ │ └─ CSharpScriptEngine │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ └─────────────────────────────────┘ │ +│ │ +│ Workflow Script Execution │ +│ ────────────────────────┘ +└─────────────────────────────────────────┘ +``` + +## 🚀 使用方法 + +### 在 KitX Dashboard 中启用 RealPluginManager + +在 KitX Dashboard 启动时,`WorkflowScriptService` 的静态构造函数会自动执行以下操作: + +1. **获取 PluginsServer 实例**: 获取 `KitX.Dashboard.Network.PluginsNetwork.PluginsServer.Instance` +2. **设置工厂函数**: 调用 `Parser.SetPluginManagerFactory()` +3. **创建 RealPluginManager**: 传入 `PluginsServer` 实例和日志记录器 +4. **自动集成**: 后续的所有 Workflow Script 执行都将使用真实的插件系统 + +### 验证集成 + +集成成功后,您可以通过以下方式验证: + +1. **编译 Dashboard**: + ```bash + cd "KitX Clients/KitX Dashboard" + dotnet build + ``` + +2. **运行 Dashboard**: 启动后检查Logger输出 + ``` + [WorkflowScriptService] RealPluginManager 工厂函数已设置 + ``` + +3. **测试 Workflow Script**: 创建并执行包含插件调用的脚本 + +## 📝 代码示例 + +### 基本集成 +```csharp +// 在 Dashboard 的初始化代码中(通常在 App.xaml.cs 或 Program.cs) +// WorkflowScriptService 的静态构造函数会自动执行此代码 + +// 手动设置(可选) +Parser.SetPluginManagerFactory(() => new RealPluginManager( + KitX.Dashboard.Network.PluginsNetwork.PluginsServer.Instance, + message => Log.Information(message) // 使用 Serilog +)); +``` + +### 脚本中使用 +```csharp +// 现在脚本中可以直接调用,将通过真实的 KitX Dashboard 插件系统执行 +var result = SampleCalculator.Add(10, 20); +var text = StringToolkit.Reverse("Hello World"); +``` + +## ⚙️ 配置选项 + +### 工厂函数 vs 默认管理器 +- **工厂函数** (`SetPluginManagerFactory`): 推荐用于生产环境 +- **默认管理器** (`SetDefaultPluginManager`): 适用于简单场景或测试 + +### 日志记录器 +- **开发阶段**: 使用 `Console.WriteLine` 或 `Debug.WriteLine` +- **生产阶段**: 使用 `Serilog.Log.Information` 或其他日志框架 + +## 🔍 故障排除 + +### 常见问题 + +1. **编译错误**: 确保已正确引用 `Kscript.CSharp.Parser` 项目 +2. **运行时错误**: 检查 `PluginsServer.Instance` 是否可用 +3. **反射异常**: 确保 Dashboard 程序集已正确加载 + +### 调试技巧 + +1. **启用详细日志**: 在 `RealPluginManager` 构造函数中添加更多日志 +2. **检查插件状态**: 验证插件是否正确连接到 Dashboard +3. **测试单独组件**: 分别测试 `RealPluginManager` 和 `PluginsServer` 连接 + +## 📈 性能考虑 + +- ✅ **延迟初始化**: RealPluginManager 只在首次使用时创建 +- ✅ **缓存机制**: 保持原有的程序集缓存功能 +- ✅ **内存效率**: 静态插件管理器实例,避免重复创建 + +## 🎉 总结 + +通过这次集成,KitX.CSharp.Parser 现在完全支持: + +1. **真实的插件调用**: 通过 KitX Dashboard 的插件系统 +2. **灵活的配置**: 支持多种插件管理器实现 +3. **向后兼容性**: 不影响现有代码 +4. **生产就绪**: 具备完整的错误处理和日志记录 + +现在 Workflow Script 中的插件调用将直接通过 KitX Dashboard 的真实插件系统执行,实现了从模拟到真实的完整升级! diff --git a/KitX Script/Kscript.CSharp.Parser/Kscript.CSharp.Parser.csproj b/KitX Script/Kscript.CSharp.Parser/Kscript.CSharp.Parser.csproj index e873ef7..03feb01 100644 --- a/KitX Script/Kscript.CSharp.Parser/Kscript.CSharp.Parser.csproj +++ b/KitX Script/Kscript.CSharp.Parser/Kscript.CSharp.Parser.csproj @@ -11,6 +11,7 @@ + diff --git a/KitX Script/Kscript.CSharp.Parser/Parser.cs b/KitX Script/Kscript.CSharp.Parser/Parser.cs index 4d88ce5..c07ec35 100644 --- a/KitX Script/Kscript.CSharp.Parser/Parser.cs +++ b/KitX Script/Kscript.CSharp.Parser/Parser.cs @@ -16,6 +16,11 @@ public static class Parser /// private static IPluginManager? _defaultPluginManager; + /// + /// 插件管理器工厂函数 + /// + private static Func? _pluginManagerFactory; + /// /// 设置默认插件管理器 /// @@ -25,6 +30,15 @@ public static void SetDefaultPluginManager(IPluginManager pluginManager) _defaultPluginManager = pluginManager; } + /// + /// 设置插件管理器工厂函数 + /// + /// 插件管理器工厂函数 + public static void SetPluginManagerFactory(Func factory) + { + _pluginManagerFactory = factory; + } + /// /// 从插件信息列表生成动态程序集 /// @@ -43,7 +57,10 @@ public static Assembly Generate(List plugins, string assemblyName = try { - var manager = pluginManager ?? _defaultPluginManager ?? new MockPluginManager(); + var manager = pluginManager ?? _defaultPluginManager ?? _pluginManagerFactory?.Invoke() ?? new MockPluginManager(); + + // 设置静态插件管理器实例 + MethodEmitter.SetStaticPluginManager(manager); if (useCache) { @@ -146,7 +163,11 @@ public static Assembly Regenerate(List plugins, string assemblyName try { - var manager = pluginManager ?? _defaultPluginManager ?? new MockPluginManager(); + var manager = pluginManager ?? _defaultPluginManager ?? _pluginManagerFactory?.Invoke() ?? new MockPluginManager(); + + // 设置静态插件管理器实例 + MethodEmitter.SetStaticPluginManager(manager); + return AssemblyCache.ForceRegenerate(plugins, assemblyName, manager); } catch (Exception ex) when (!(ex is ParserException)) diff --git a/KitX Script/Kscript.CSharp.Parser/README.md b/KitX Script/Kscript.CSharp.Parser/README.md index 2530fdd..730f4c7 100644 --- a/KitX Script/Kscript.CSharp.Parser/README.md +++ b/KitX Script/Kscript.CSharp.Parser/README.md @@ -96,6 +96,62 @@ dotnet build dotnet run ``` +## 🔌 实际插件管理器集成 + +### 概述 + +KitX.CSharp.Parser 现在支持使用实际的插件管理器替换默认的 MockPluginManager,实现与 KitX Dashboard 的真实插件调用集成。 + +### 集成步骤 + +1. **设置插件管理器工厂** + ```csharp + // 在 KitX Dashboard 启动时设置 + Parser.SetPluginManagerFactory(() => new RealPluginManager( + KitX.Dashboard.Network.PluginsNetwork.PluginsServer.Instance, + message => Log.Information(message) + )); + ``` + +2. **生成插件程序集** + ```csharp + var assembly = Parser.Generate(plugins, "DashboardPluginAssembly"); + ``` + +3. **在脚本中使用** + ```csharp + // 现在可以直接调用,将通过真实的 KitX Dashboard 插件系统执行 + var result = SampleCalculator.Add(10, 20); + ``` + +### API 参考 + +#### 新增方法 + +| 方法 | 描述 | 参数 | +|------|------|------| +| `SetPluginManagerFactory()` | 设置插件管理器工厂函数 | `Func factory` | + +#### RealPluginManager 构造函数 + +| 参数 | 类型 | 描述 | +|------|------|------| +| `pluginsServer` | `object` | PluginsServer 实例 | +| `logger` | `Action` | 日志记录器 | + +### 使用示例 + +详细的使用示例请参考: +- `Examples/RealPluginManagerExample.cs` - 完整的使用示例 +- `Examples/Program.cs` - 演示程序入口 + +### 注意事项 + +- ✅ **依赖注入设计**:通过工厂函数模式实现松耦合 +- ✅ **日志统一**:支持外部传入日志记录器 +- ✅ **向后兼容**:仍支持 MockPluginManager 作为默认选项 +- ✅ **实际调用**:通过 KitX Dashboard 的插件系统进行真实调用 + --- ## 📁 项目结构 From fdbbfef2da2a4f6f430a72b40d8c2050104706a8 Mon Sep 17 00:00:00 2001 From: StarInk Date: Sat, 8 Nov 2025 02:17:25 +0100 Subject: [PATCH 10/17] =?UTF-8?q?=F0=9F=A7=A9=20Refactor(Parser):=20?= =?UTF-8?q?=E5=A4=A7=E5=B9=85=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81=E7=BB=93?= =?UTF-8?q?=E6=9E=84=EF=BC=8C=E5=88=A0=E9=99=A4=E4=B8=8D=E5=BF=85=E8=A6=81?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E5=B9=B6=E4=BF=AE=E5=A4=8D=E6=BD=9C=E5=9C=A8?= =?UTF-8?q?=E9=9A=90=E6=82=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ARCHITECTURE_ANALYSIS.md | 285 --------------- .../Kscript.CSharp.Parser/CLASS_DIAGRAM.md | 343 ------------------ .../CodeGen/AssemblyCache.cs | 151 ++------ .../CodeGen/MethodEmitter.cs | 49 +-- .../CodeGen/TypeMapper.cs | 148 +------- .../Examples/BasicUsageExample.cs | 50 ++- .../Kscript.CSharp.Parser/Examples/Program.cs | 118 ++++++ .../Examples/RealPluginManagerExample.cs | 34 +- .../Exceptions/ParserException.cs | 24 -- .../INTEGRATION_GUIDE.md | 163 --------- KitX Script/Kscript.CSharp.Parser/Parser.cs | 112 +++--- 11 files changed, 266 insertions(+), 1211 deletions(-) delete mode 100644 KitX Script/Kscript.CSharp.Parser/ARCHITECTURE_ANALYSIS.md delete mode 100644 KitX Script/Kscript.CSharp.Parser/CLASS_DIAGRAM.md delete mode 100644 KitX Script/Kscript.CSharp.Parser/INTEGRATION_GUIDE.md diff --git a/KitX Script/Kscript.CSharp.Parser/ARCHITECTURE_ANALYSIS.md b/KitX Script/Kscript.CSharp.Parser/ARCHITECTURE_ANALYSIS.md deleted file mode 100644 index b78fc4b..0000000 --- a/KitX Script/Kscript.CSharp.Parser/ARCHITECTURE_ANALYSIS.md +++ /dev/null @@ -1,285 +0,0 @@ -# KitX.CSharp.Parser 架构分析与重构建议 - -## 🚨 当前架构问题分析 - -### 1. 循环依赖风险 - -```mermaid -graph TD - Parser --> MethodEmitter - MethodEmitter --> IPluginManager - Parser --> IPluginManager - MethodEmitter -.-> Parser -``` - -**问题描述:** -- [`Parser`](KitX Standard/KitX Script/Kscript.CSharp.Parser/Parser.cs:12) 设置插件管理器,但 [`MethodEmitter`](KitX Standard/KitX Script/Kscript.CSharp.Parser/CodeGen/MethodEmitter.cs:10) 也需要访问 -- [`MethodEmitter.SetStaticPluginManager()`](KitX Standard/KitX Script/Kscript.CSharp.Parser/CodeGen/MethodEmitter.cs:21) 创建了隐式依赖 - -### 2. 职责边界模糊 - -| 类 | 当前职责 | 问题 | -|------|----------|------| -| **Parser** | 入口+配置+协调+缓存管理 | 职责过多,违反单一职责原则 | -| **MethodEmitter** | IL生成+插件管理器管理 | 混合了生成逻辑和运行时逻辑 | -| **AssemblyCache** | 缓存+直接调用MethodEmitter | 缓存层不应该知道生成细节 | - -### 3. 跨层调用问题 - -```mermaid -graph LR - Parser[Parser 表现层] --> AssemblyCache[缓存层] - AssemblyCache --> MethodEmitter[生成层] - MethodEmitter --> TypeMapper[解析层] -``` - -**问题:** 缓存层直接调用生成层,破坏了分层架构的纯粹性 - -## 🎯 重构方案:单向数据流架构 - -### 核心设计原则 - -1. **数据只能向下流动**:上层 → 下层 -2. **配置只能横向注入**:配置层 → 各层 -3. **事件只能向上冒泡**:下层 → 上层(异常、状态) -4. **禁止跨层调用**:只能调用相邻层 - -### 📋 重构后的分层架构 - -```mermaid -graph TD - %% 输入层 - A[JSON 输入] --> B[Parser 输入层] - - %% 解析层 - B --> C[PluginInfo 解析层] - C --> D[TypeMapper 类型映射] - - %% 生成层 - D --> E[AssemblyBuilder 程序集构建器] - E --> F[MethodEmitter IL生成器] - - %% 缓存层 - F --> G[AssemblyCache 缓存层] - - %% 执行层 - G --> H[PluginManager 插件管理器] - H --> I[PluginCallInfo 调用信息] - - %% 输出层 - I --> J[动态程序集输出] - - %% 配置层(独立) - K[Configuration 配置层] -.-> B - K -.-> D - K -.-> G - K -.-> H - - %% 样式 - classDef inputLayer fill:#e1f5fe - classDef parseLayer fill:#f3e5f5 - classDef generateLayer fill:#e8f5e8 - classDef cacheLayer fill:#fff3e0 - classDef executeLayer fill:#fce4ec - classDef outputLayer fill:#e0f2f1 - classDef configLayer fill:#f1f8e9 - - class A,B inputLayer - class C,D parseLayer - class E,F generateLayer - class G cacheLayer - class H,I executeLayer - class J outputLayer - class K configLayer -``` - -### 🔧 重构后的类职责 - -| 层级 | 类名 | 职责 | 输入 | 输出 | -|------|------|------|------|------| -| **输入层** | Parser | 统一入口,参数验证 | JSON/PluginInfo | 验证后的数据 | -| **解析层** | TypeMapper | 类型映射,验证 | 类型字符串 | System.Type | -| **生成层** | AssemblyBuilder | 协调生成过程 | PluginInfo | IL代码 | -| | MethodEmitter | 纯IL生成 | 类型信息 | IL指令 | -| **缓存层** | AssemblyCache | 纯缓存管理 | 程序集 | 缓存的程序集 | -| **执行层** | PluginManager | 插件调用 | 调用信息 | 执行结果 | -| **配置层** | Configuration | 配置管理 | 配置数据 | 配置实例 | - -## 🛠️ 具体重构策略 - -### 1. 引入依赖注入容器 - -```csharp -// 替代静态依赖 -public class ServiceContainer -{ - public void ConfigureServices() - { - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - } -} -``` - -### 2. 接口隔离原则 - -```csharp -// 明确的接口定义 -public interface IAssemblyBuilder -{ - Assembly BuildFromPlugins(List plugins, string assemblyName); -} - -public interface IMethodEmitter -{ - void EmitMethod(TypeBuilder typeBuilder, Function function); -} - -public interface ITypeMapper -{ - Type MapType(string typeName); - void RegisterCustomType(string typeName, Type type); -} -``` - -### 3. 事件驱动通信 - -```csharp -// 替代直接调用 -public class AssemblyBuilder -{ - public event EventHandler AssemblyGenerated; - public event EventHandler MethodGenerated; - - public Assembly Build(List plugins) - { - // 生成逻辑 - foreach (var plugin in plugins) - { - foreach (var function in plugin.Functions) - { - MethodGenerated?.Invoke(this, new MethodGeneratedEventArgs(function)); - } - } - - var assembly = // ... 生成程序集 - AssemblyGenerated?.Invoke(this, new AssemblyGeneratedEventArgs(assembly)); - return assembly; - } -} -``` - -### 4. 配置管理分离 - -```csharp -public class ParserConfiguration -{ - public IPluginManager PluginManager { get; set; } - public bool UseCache { get; set; } = true; - public string DefaultAssemblyName { get; set; } = "DynamicPluginAssembly"; - public TimeSpan CacheTimeout { get; set; } = TimeSpan.FromHours(1); -} - -public class ConfigurationManager -{ - public static ParserConfiguration GetConfiguration() - { - // 从配置文件、环境变量等读取配置 - return new ParserConfiguration - { - UseCache = bool.Parse(Environment.GetEnvironmentVariable("KSCRIPT_CACHE") ?? "true"), - DefaultAssemblyName = Environment.GetEnvironmentVariable("KSCRIPT_ASSEMBLY_NAME") ?? "DynamicPluginAssembly" - }; - } -} -``` - -## 🔄 重构后的数据流 - -### 程序集生成流程 - -```mermaid -sequenceDiagram - participant Client - participant Parser - participant AssemblyBuilder - participant TypeMapper - participant MethodEmitter - participant AssemblyCache - participant PluginManager - - Client->>Parser: Generate(plugins) - Parser->>Parser: ValidateInput() - Parser->>AssemblyBuilder: Build(plugins) - - AssemblyBuilder->>TypeMapper: MapType(typeName) - TypeMapper-->>AssemblyBuilder: Type - - AssemblyBuilder->>MethodEmitter: EmitMethod() - MethodEmitter-->>AssemblyBuilder: IL Instructions - - AssemblyBuilder->>AssemblyCache: GetOrCreate(assembly) - AssemblyCache-->>AssemblyBuilder: Cached Assembly - - AssemblyBuilder-->>Parser: Assembly - Parser->>PluginManager: SetContext(assembly) - Parser-->>Client: Assembly -``` - -### 插件调用流程 - -```mermaid -sequenceDiagram - participant Script - participant GeneratedMethod - participant PluginManager - participant PluginConnector - participant PluginProcess - - Script->>GeneratedMethod: Plugin.Method(args) - GeneratedMethod->>PluginManager: Call(PluginCallInfo) - - PluginManager->>PluginConnector: FindConnector() - PluginManager->>PluginConnector: SendRequest() - PluginConnector->>PluginProcess: WebSocket Communication - PluginProcess-->>PluginConnector: Response - PluginConnector-->>PluginManager: HandleResponse() - PluginManager-->>GeneratedMethod: Result - GeneratedMethod-->>Script: Result -``` - -## ✅ 重构收益 - -1. **清晰的职责分离** - 每个类只负责一个明确的功能 -2. **单向数据流** - 避免循环依赖和混乱的调用关系 -3. **易于测试** - 每层可以独立测试 -4. **易于维护** - 修改一层不会影响其他层 -5. **可扩展性** - 新功能可以通过添加新层实现 -6. **配置灵活性** - 通过依赖注入支持不同的运行环境 - -## 🎯 实施步骤 - -1. **第一阶段:接口定义** - - 定义各层的接口 - - 确定数据契约 - -2. **第二阶段:依赖注入** - - 引入DI容器 - - 重构静态依赖 - -3. **第三阶段:事件驱动** - - 替换直接调用为事件 - - 实现观察者模式 - -4. **第四阶段:配置管理** - - 分离配置逻辑 - - 支持多环境配置 - -5. **第五阶段:测试验证** - - 单元测试各层 - - 集成测试整体流程 - -这样的重构将显著提升 KitX.CSharp.Parser 项目的可维护性和可扩展性,同时保持现有功能的完整性。 diff --git a/KitX Script/Kscript.CSharp.Parser/CLASS_DIAGRAM.md b/KitX Script/Kscript.CSharp.Parser/CLASS_DIAGRAM.md deleted file mode 100644 index 1ccf714..0000000 --- a/KitX Script/Kscript.CSharp.Parser/CLASS_DIAGRAM.md +++ /dev/null @@ -1,343 +0,0 @@ -# KitX.CSharp.Parser 类图与调用关系 - -## 📊 整体架构类图 - -```mermaid -classDiagram - %% 外部依赖 - class KitXDashboard { - +PluginsServer.Instance - +WorkflowScriptService - } - - class KitXShared { - <> - +PluginInfo - +Function - +Parameter - +Connector - +Request - } - - %% 主入口类 - class Parser { - <> - -_defaultPluginManager: IPluginManager - -_pluginManagerFactory: Func~IPluginManager~ - +Generate(plugins, assemblyName, pluginManager, useCache): Assembly - +GenerateFromJson(jsonString, assemblyName, pluginManager, useCache): Assembly - +GenerateFromFileAsync(jsonFilePath, assemblyName, pluginManager, useCache): Task~Assembly~ - +Regenerate(plugins, assemblyName, pluginManager): Assembly - +SetDefaultPluginManager(pluginManager): void - +SetPluginManagerFactory(factory): void - +RegisterCustomType(typeName, type): void - +ClearCache(): void - +GetCacheStatistics(): CacheStatistics - +HasCache(plugins, assemblyName): bool - +GetPluginTypes(assembly): List~Type~ - +GetPluginMethods(pluginType): List~MethodInfo~ - } - - %% 核心接口 - class IPluginManager { - <> - +Call~T~(callInfo): T - +Call(callInfo): void - +IsPluginExists(pluginName): bool - +IsMethodExists(pluginName, methodName): bool - } - - %% 插件管理器实现 - class MockPluginManager { - -_mockData: Dictionary~string, Dictionary~ - +Call~T~(callInfo): T - +Call(callInfo): void - +IsPluginExists(pluginName): bool - +IsMethodExists(pluginName, methodName): bool - -InvokeMockMethod(callInfo, method): object - -GetDefaultValue(type): object - } - - class RealPluginManager { - -_pluginsServer: object - -_infoLogger: Action~string~ - -_errorLogger: Action~string~ - -_pendingRequests: ConcurrentDictionary~string, TaskCompletionSource~ - +Call~T~(callInfo): T - +Call(callInfo): void - +IsPluginExists(pluginName): bool - +IsMethodExists(pluginName, methodName): bool - -FindPluginInfo(pluginName): PluginInfo - -FindPluginConnector(pluginInfo): object - -SendPluginRequest~T~(connector, callInfo): Task~T~ - -SendRequestToConnector(connector, request): Task - +HandlePluginResponse(requestId, responseJson): void - -GetDefaultResult~T~(): T - } - - %% 代码生成组件 - class MethodEmitter { - <> - -_staticPluginManager: IPluginManager - +SetStaticPluginManager(pluginManager): void - +GenerateAssembly(plugins, assemblyName, pluginManager): Assembly - -GeneratePluginClass(moduleBuilder, plugin, pluginManager): void - -GenerateStaticConstructor(typeBuilder, pluginManagerField, pluginManager): void - -GeneratePluginMethod(typeBuilder, pluginName, function, pluginManagerField): void - -GenerateMethodBody(methodBuilder, pluginName, function, parameterTypes, returnType, pluginManagerField): void - -ConvertDefaultValue(value, targetType): object - } - - class TypeMapper { - <> - -_basicTypeMap: Dictionary~string, Type~ - -_customTypeMap: ConcurrentDictionary~string, Type~ - -_typeCache: ConcurrentDictionary~string, Type~ - -_genericTypeRegex: Regex - +MapType(typeName): Type - -ResolveGenericType(genericMatch): Type - -ParseGenericArguments(argsString): Type[] - -SplitGenericArguments(argsString): string[] - +RegisterCustomType(typeName, type): void - +ClearCache(): void - +GetCustomTypes(): IReadOnlyDictionary~string, Type~ - } - - class AssemblyCache { - <> - -_assemblyCache: ConcurrentDictionary~string, Assembly~ - -_referenceCount: ConcurrentDictionary~string, int~ - -_cacheLock: object - +GetOrCreateAssembly(plugins, assemblyName, pluginManager): Assembly - -GenerateCacheKey(plugins, assemblyName): string - -IncrementReference(cacheKey): void - +DecrementReference(cacheKey): void - +ClearCache(): void - +GetStatistics(): CacheStatistics - +HasCache(plugins, assemblyName): bool - +ForceRegenerate(plugins, assemblyName, pluginManager): Assembly - } - - %% 数据模型 - class PluginCallInfo { - +PluginName: string - +MethodName: string - +Parameters: object[] - +ParameterTypes: Type[] - +ToString(): string - } - - class CacheStatistics { - +CachedAssemblyCount: int - +TotalReferences: int - +CacheKeys: List~string~ - +ToString(): string - } - - %% 异常类 - class ParserException { - +ParserException() - +ParserException(message) - +ParserException(message, innerException) - +TypeMappingError(typeName, innerException): ParserException - +ILGenerationError(methodName, innerException): ParserException - +AssemblyGenerationError(assemblyName, innerException): ParserException - } - - %% 示例类 - class BasicUsageExample { - <> - +RunExample(): Task - -RunWithBuiltInData(): void - -DynamicInvokeExample(assembly): void - -GenerateTestArguments(methodName, parameters): object[] - -GetDefaultValue(type): object - +RunAdvancedExample(): void - } - - class RealPluginManagerExample { - <> - +RunExample(): void - +DashboardIntegrationExample(): void - } - - %% 关系定义 - Parser --> IPluginManager : 使用 - Parser --> TypeMapper : 使用 - Parser --> AssemblyCache : 使用 - Parser --> MethodEmitter : 调用 - Parser --> CacheStatistics : 返回 - - IPluginManager <|.. MockPluginManager : 实现 - IPluginManager <|.. RealPluginManager : 实现 - - MockPluginManager --> PluginCallInfo : 使用 - RealPluginManager --> PluginCallInfo : 使用 - RealPluginManager --> KitXDashboard : 依赖 - RealPluginManager --> KitXShared : 使用 - - MethodEmitter --> IPluginManager : 使用 - MethodEmitter --> TypeMapper : 使用 - MethodEmitter --> PluginCallInfo : 使用 - MethodEmitter --> ParserException : 抛出 - - TypeMapper --> ParserException : 抛出 - - AssemblyCache --> MethodEmitter : 调用 - AssemblyCache --> CacheStatistics : 返回 - - BasicUsageExample --> Parser : 使用 - RealPluginManagerExample --> Parser : 使用 - RealPluginManagerExample --> RealPluginManager : 使用 - - KitXDashboard --> RealPluginManager : 创建 -``` - -## 🔄 调用时序图 - -### 基本生成流程 - -```mermaid -sequenceDiagram - participant Client - participant Parser - participant AssemblyCache - participant MethodEmitter - participant TypeMapper - participant IPluginManager - - Client->>Parser: Generate(plugins) - Parser->>AssemblyCache: GetOrCreateAssembly() - - alt 缓存命中 - AssemblyCache-->>Parser: 返回缓存的Assembly - else 缓存未命中 - AssemblyCache->>MethodEmitter: GenerateAssembly() - - loop 对每个插件 - MethodEmitter->>TypeMapper: MapType(typeName) - TypeMapper-->>MethodEmitter: 返回Type - - MethodEmitter->>MethodEmitter: GeneratePluginClass() - MethodEmitter->>MethodEmitter: GenerateStaticConstructor() - - loop 对每个功能 - MethodEmitter->>MethodEmitter: GeneratePluginMethod() - MethodEmitter->>MethodEmitter: GenerateMethodBody() - MethodEmitter->>IPluginManager: SetStaticPluginManager() - end - end - - MethodEmitter-->>AssemblyCache: 返回新Assembly - AssemblyCache-->>Parser: 返回Assembly - end - - Parser-->>Client: 返回Assembly -``` - -### 真实插件调用流程 - -```mermaid -sequenceDiagram - participant Script - participant GeneratedMethod - participant RealPluginManager - participant PluginConnector - participant PluginProcess - participant KitXDashboard - - Script->>GeneratedMethod: SampleCalculator.Add(10, 20) - GeneratedMethod->>RealPluginManager: Call(PluginCallInfo) - - RealPluginManager->>KitXDashboard: FindPluginInfo() - KitXDashboard-->>RealPluginManager: PluginInfo - - RealPluginManager->>KitXDashboard: FindPluginConnector() - KitXDashboard-->>RealPluginManager: PluginConnector - - RealPluginManager->>PluginConnector: SendRequest(Request) - PluginConnector->>PluginProcess: WebSocket 通信 - PluginProcess-->>PluginConnector: Response - PluginConnector-->>RealPluginManager: HandleResponse() - - RealPluginManager-->>GeneratedMethod: 返回结果 - GeneratedMethod-->>Script: 返回结果 -``` - -## 📋 类职责说明 - -### 核心类 - -| 类名 | 职责 | 关键方法 | -|------|------|----------| -| **Parser** | 主入口,统一API | `Generate()`, `GenerateFromJson()`, `GenerateFromFileAsync()` | -| **IPluginManager** | 插件管理器接口 | `Call()`, `IsPluginExists()` | -| **MockPluginManager** | 模拟插件管理器 | `Call()`, `InvokeMockMethod()` | -| **RealPluginManager** | 真实插件管理器 | `Call()`, `SendPluginRequest()` | - -### 代码生成类 - -| 类名 | 职责 | 关键方法 | -|------|------|----------| -| **MethodEmitter** | IL代码生成 | `GenerateAssembly()`, `GeneratePluginClass()` | -| **TypeMapper** | 类型映射 | `MapType()`, `RegisterCustomType()` | -| **AssemblyCache** | 程序集缓存 | `GetOrCreateAssembly()`, `ForceRegenerate()` | - -### 数据模型类 - -| 类名 | 职责 | 关键属性 | -|------|------|----------| -| **PluginCallInfo** | 插件调用信息 | `PluginName`, `MethodName`, `Parameters` | -| **CacheStatistics** | 缓存统计信息 | `CachedAssemblyCount`, `TotalReferences` | - -## 🔧 设计模式应用 - -### 1. 工厂模式 -- `Parser.SetPluginManagerFactory()` - 创建插件管理器实例 -- `MethodEmitter.GenerateAssembly()` - 创建动态程序集 - -### 2. 策略模式 -- `IPluginManager` 接口 - 不同的插件管理器实现策略 -- `MockPluginManager` vs `RealPluginManager` - 测试vs生产策略 - -### 3. 单例模式 -- `AssemblyCache` - 静态缓存管理 -- `TypeMapper` - 静态类型映射 - -### 4. 建造者模式 -- `MethodEmitter` 中的 IL 生成过程 -- `Connector` 的请求构建过程 - -### 5. 模板方法模式 -- `Parser.Generate()` 方法的通用流程 -- 插件方法生成的标准流程 - -## 🎯 关键调用路径 - -### 1. 程序集生成路径 -``` -Parser.Generate() -→ AssemblyCache.GetOrCreateAssembly() -→ MethodEmitter.GenerateAssembly() -→ TypeMapper.MapType() -→ IL生成 -``` - -### 2. 插件调用路径 -``` -生成的静态方法 -→ IPluginManager.Call() -→ RealPluginManager.SendPluginRequest() -→ WebSocket通信 -→ 插件进程 -``` - -### 3. 缓存管理路径 -``` -AssemblyCache.GenerateCacheKey() -→ SHA256哈希计算 -→ 缓存查找/存储 -→ 引用计数管理 -``` - -这个类图展示了 KitX.CSharp.Parser 项目的完整架构,包括各个类之间的依赖关系、调用顺序和设计模式的应用。 diff --git a/KitX Script/Kscript.CSharp.Parser/CodeGen/AssemblyCache.cs b/KitX Script/Kscript.CSharp.Parser/CodeGen/AssemblyCache.cs index 26e4ce0..50f8d8a 100644 --- a/KitX Script/Kscript.CSharp.Parser/CodeGen/AssemblyCache.cs +++ b/KitX Script/Kscript.CSharp.Parser/CodeGen/AssemblyCache.cs @@ -1,7 +1,5 @@ using System.Collections.Concurrent; -using System.Security.Cryptography; using System.Text; -using System.Text.Json; namespace Kscript.CSharp.Parser.CodeGen; @@ -15,16 +13,6 @@ public static class AssemblyCache /// private static readonly ConcurrentDictionary _assemblyCache = new(); - /// - /// 程序集引用计数,用于清理不再使用的程序集 - /// - private static readonly ConcurrentDictionary _referenceCount = new(); - - /// - /// 缓存锁,确保并发安全 - /// - private static readonly object _cacheLock = new(); - /// /// 获取或生成程序集 /// @@ -32,35 +20,23 @@ public static class AssemblyCache /// 程序集名称 /// 插件管理器实例 /// 缓存的或新生成的程序集 - public static Assembly GetOrCreateAssembly(List plugins, string assemblyName = "DynamicPluginAssembly", Core.IPluginManager? pluginManager = null) + public static Assembly GetOrCreateAssembly(List plugins, string assemblyName, Core.IPluginManager pluginManager) { var cacheKey = GenerateCacheKey(plugins, assemblyName); // 尝试从缓存获取 if (_assemblyCache.TryGetValue(cacheKey, out var cachedAssembly)) { - IncrementReference(cacheKey); return cachedAssembly; } - lock (_cacheLock) - { - // 双重检查锁定模式 - if (_assemblyCache.TryGetValue(cacheKey, out cachedAssembly)) - { - IncrementReference(cacheKey); - return cachedAssembly; - } - - // 生成新的程序集 - var newAssembly = MethodEmitter.GenerateAssembly(plugins, assemblyName, pluginManager); + // 生成新的程序集 + var newAssembly = MethodEmitter.GenerateAssembly(plugins, assemblyName, pluginManager); - // 添加到缓存 - _assemblyCache[cacheKey] = newAssembly; - _referenceCount[cacheKey] = 1; + // 添加到缓存 + _assemblyCache.TryAdd(cacheKey, newAssembly); - return newAssembly; - } + return newAssembly; } /// @@ -71,81 +47,21 @@ public static Assembly GetOrCreateAssembly(List plugins, string asse /// 缓存键字符串 private static string GenerateCacheKey(List plugins, string assemblyName) { - try - { - // 创建用于哈希的数据对象 - var cacheData = new - { - AssemblyName = assemblyName, - Plugins = plugins.Select(p => new - { - p.Name, - p.Version, - Functions = p.Functions.Select(f => new - { - f.Name, - f.ReturnValueType, - Parameters = f.Parameters.Select(param => new - { - param.Name, - param.Type, - param.IsOptional, - param.Value - }).OrderBy(param => param.Name) - }).OrderBy(f => f.Name) - }).OrderBy(p => p.Name) - }; - - // 序列化为JSON - var json = JsonSerializer.Serialize(cacheData, new JsonSerializerOptions - { - WriteIndented = false, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase - }); - - // 计算SHA256哈希 - using var sha256 = SHA256.Create(); - var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(json)); - return Convert.ToHexString(hashBytes); - } - catch (Exception ex) - { - // 如果哈希计算失败,使用基础信息生成简单键 - var fallbackKey = $"{assemblyName}_{plugins.Count}_{string.Join("_", plugins.Select(p => $"{p.Name}_{p.Functions.Count}"))}_{DateTime.UtcNow.Ticks}"; - Console.WriteLine($"[AssemblyCache] 哈希计算失败,使用回退键: {ex.Message}"); - return fallbackKey; - } - } - - /// - /// 增加引用计数 - /// - /// 缓存键 - private static void IncrementReference(string cacheKey) - { - _referenceCount.AddOrUpdate(cacheKey, 1, (key, count) => count + 1); - } + // 使用轻量级字符串哈希,避免复杂的JSON序列化和SHA256计算 + var keyBuilder = new StringBuilder(); + keyBuilder.Append(assemblyName); + keyBuilder.Append($"|{plugins.Count}"); - /// - /// 减少引用计数 - /// - /// 缓存键 - public static void DecrementReference(string cacheKey) - { - if (_referenceCount.TryGetValue(cacheKey, out var count)) + foreach (var plugin in plugins.OrderBy(p => p.Name)) { - var newCount = count - 1; - if (newCount <= 0) - { - // 引用计数为0,从缓存中移除 - _assemblyCache.TryRemove(cacheKey, out _); - _referenceCount.TryRemove(cacheKey, out _); - } - else + keyBuilder.Append($"|{plugin.Name}:{plugin.Version}:{plugin.Functions.Count}"); + foreach (var function in plugin.Functions.OrderBy(f => f.Name)) { - _referenceCount[cacheKey] = newCount; + keyBuilder.Append($":{function.Name}:{function.ReturnValueType}:{function.Parameters.Count}"); } } + + return keyBuilder.ToString().GetHashCode().ToString("X"); } /// @@ -153,11 +69,7 @@ public static void DecrementReference(string cacheKey) /// public static void ClearCache() { - lock (_cacheLock) - { - _assemblyCache.Clear(); - _referenceCount.Clear(); - } + _assemblyCache.Clear(); } /// @@ -169,7 +81,6 @@ public static CacheStatistics GetStatistics() return new CacheStatistics { CachedAssemblyCount = _assemblyCache.Count, - TotalReferences = _referenceCount.Values.Sum(), CacheKeys = _assemblyCache.Keys.ToList() }; } @@ -193,25 +104,20 @@ public static bool HasCache(List plugins, string assemblyName = "Dyn /// 程序集名称 /// 插件管理器实例 /// 新生成的程序集 - public static Assembly ForceRegenerate(List plugins, string assemblyName = "DynamicPluginAssembly", Core.IPluginManager? pluginManager = null) + public static Assembly ForceRegenerate(List plugins, Core.IPluginManager pluginManager, string assemblyName = "DynamicPluginAssembly") { var cacheKey = GenerateCacheKey(plugins, assemblyName); - lock (_cacheLock) - { - // 移除现有缓存 - _assemblyCache.TryRemove(cacheKey, out _); - _referenceCount.TryRemove(cacheKey, out _); + // 移除现有缓存 + _assemblyCache.TryRemove(cacheKey, out _); - // 生成新的程序集 - var newAssembly = MethodEmitter.GenerateAssembly(plugins, assemblyName, pluginManager); + // 生成新的程序集 + var newAssembly = MethodEmitter.GenerateAssembly(plugins, assemblyName, pluginManager); - // 添加到缓存 - _assemblyCache[cacheKey] = newAssembly; - _referenceCount[cacheKey] = 1; + // 添加到缓存 + _assemblyCache.TryAdd(cacheKey, newAssembly); - return newAssembly; - } + return newAssembly; } } @@ -225,11 +131,6 @@ public class CacheStatistics /// public int CachedAssemblyCount { get; set; } - /// - /// 总引用数量 - /// - public int TotalReferences { get; set; } - /// /// 缓存键列表 /// @@ -237,6 +138,6 @@ public class CacheStatistics public override string ToString() { - return $"缓存统计: {CachedAssemblyCount} 个程序集, {TotalReferences} 个引用"; + return $"缓存统计: {CachedAssemblyCount} 个程序集"; } } diff --git a/KitX Script/Kscript.CSharp.Parser/CodeGen/MethodEmitter.cs b/KitX Script/Kscript.CSharp.Parser/CodeGen/MethodEmitter.cs index 7e9f713..1fe2cc8 100644 --- a/KitX Script/Kscript.CSharp.Parser/CodeGen/MethodEmitter.cs +++ b/KitX Script/Kscript.CSharp.Parser/CodeGen/MethodEmitter.cs @@ -9,19 +9,6 @@ namespace Kscript.CSharp.Parser.CodeGen; /// public static class MethodEmitter { - /// - /// 静态插件管理器实例,用于在生成的程序集中使用 - /// - private static IPluginManager? _staticPluginManager; - - /// - /// 设置静态插件管理器实例 - /// - /// 插件管理器实例 - public static void SetStaticPluginManager(IPluginManager pluginManager) - { - _staticPluginManager = pluginManager; - } /// /// 为插件生成动态程序集 /// @@ -29,12 +16,13 @@ public static void SetStaticPluginManager(IPluginManager pluginManager) /// 程序集名称 /// 插件管理器实例 /// 生成的动态程序集 - public static Assembly GenerateAssembly(List plugins, string assemblyName = "DynamicPluginAssembly", IPluginManager? pluginManager = null) + public static Assembly GenerateAssembly(List plugins, + string assemblyName, IPluginManager pluginManager) { try { // 创建动态程序集 - var assemblyBuilder = System.Reflection.Emit.AssemblyBuilder.DefineDynamicAssembly( + var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly( new AssemblyName(assemblyName), AssemblyBuilderAccess.Run); @@ -50,14 +38,14 @@ public static Assembly GenerateAssembly(List plugins, string assembl } catch (Exception ex) { - throw ParserException.AssemblyGenerationError(assemblyName, ex); + throw new ParserException($"生成程序集失败: {assemblyName}", ex); } } /// /// 为单个插件生成静态类 /// - private static void GeneratePluginClass(ModuleBuilder moduleBuilder, PluginInfo plugin, IPluginManager? pluginManager) + private static void GeneratePluginClass(ModuleBuilder moduleBuilder, PluginInfo plugin, IPluginManager pluginManager) { try { @@ -86,14 +74,14 @@ private static void GeneratePluginClass(ModuleBuilder moduleBuilder, PluginInfo } catch (Exception ex) { - throw ParserException.ILGenerationError($"{plugin.Name} 类", ex); + throw new ParserException($"生成IL代码失败: {plugin.Name} 类", ex); } } /// /// 生成静态构造函数 /// - private static void GenerateStaticConstructor(TypeBuilder typeBuilder, FieldBuilder pluginManagerField, IPluginManager? pluginManager) + private static void GenerateStaticConstructor(TypeBuilder typeBuilder, FieldBuilder pluginManagerField, IPluginManager pluginManager) { var constructorBuilder = typeBuilder.DefineConstructor( MethodAttributes.Private | MethodAttributes.Static | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, @@ -102,22 +90,11 @@ private static void GenerateStaticConstructor(TypeBuilder typeBuilder, FieldBuil var il = constructorBuilder.GetILGenerator(); - // 优先使用静态插件管理器实例 - var managerToUse = _staticPluginManager ?? pluginManager; - - if (managerToUse != null) - { - // 如果提供了插件管理器实例,直接使用 - il.Emit(OpCodes.Ldtoken, managerToUse.GetType()); - il.Emit(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle")!); - il.Emit(OpCodes.Call, typeof(Activator).GetMethod("CreateInstance", new[] { typeof(Type) })!); - il.Emit(OpCodes.Castclass, typeof(IPluginManager)); - } - else - { - // 使用默认的 MockPluginManager - il.Emit(OpCodes.Newobj, typeof(MockPluginManager).GetConstructor(Type.EmptyTypes)!); - } + // 使用提供的插件管理器实例 + il.Emit(OpCodes.Ldtoken, pluginManager.GetType()); + il.Emit(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle")!); + il.Emit(OpCodes.Call, typeof(Activator).GetMethod("CreateInstance", new[] { typeof(Type) })!); + il.Emit(OpCodes.Castclass, typeof(IPluginManager)); il.Emit(OpCodes.Stsfld, pluginManagerField); il.Emit(OpCodes.Ret); @@ -167,7 +144,7 @@ private static void GeneratePluginMethod(TypeBuilder typeBuilder, string pluginN } catch (Exception ex) { - throw ParserException.ILGenerationError($"{pluginName}.{function.Name}", ex); + throw new ParserException($"生成IL代码失败: {pluginName}.{function.Name}", ex); } } diff --git a/KitX Script/Kscript.CSharp.Parser/CodeGen/TypeMapper.cs b/KitX Script/Kscript.CSharp.Parser/CodeGen/TypeMapper.cs index 061a31f..1810ffb 100644 --- a/KitX Script/Kscript.CSharp.Parser/CodeGen/TypeMapper.cs +++ b/KitX Script/Kscript.CSharp.Parser/CodeGen/TypeMapper.cs @@ -1,5 +1,4 @@ using System.Collections.Concurrent; -using System.Text.RegularExpressions; namespace Kscript.CSharp.Parser.CodeGen; @@ -31,21 +30,11 @@ public static class TypeMapper { "object", typeof(object) } }; - /// - /// 自定义类型映射表 - /// - private static readonly ConcurrentDictionary _customTypeMap = new(); - /// /// 类型缓存 /// private static readonly ConcurrentDictionary _typeCache = new(); - /// - /// 泛型类型正则表达式 - /// - private static readonly Regex _genericTypeRegex = new(@"^(\w+)<(.+)>$", RegexOptions.Compiled); - /// /// 映射字符串类型名到 System.Type /// @@ -60,138 +49,31 @@ public static Type MapType(string typeName) if (_typeCache.TryGetValue(typeName, out var cachedType)) return cachedType; - Type? resolvedType = null; + // 检查基础类型 + if (_basicTypeMap.TryGetValue(typeName, out var basicType)) + { + _typeCache[typeName] = basicType; + return basicType; + } + // 尝试直接解析类型 try { - // 1. 检查基础类型 - if (_basicTypeMap.TryGetValue(typeName, out resolvedType)) - { - _typeCache[typeName] = resolvedType; - return resolvedType; - } - - // 2. 检查自定义映射 - if (_customTypeMap.TryGetValue(typeName, out resolvedType)) - { - _typeCache[typeName] = resolvedType; - return resolvedType; - } - - // 3. 处理泛型类型 - var genericMatch = _genericTypeRegex.Match(typeName); - if (genericMatch.Success) - { - resolvedType = ResolveGenericType(genericMatch); - if (resolvedType != null) - { - _typeCache[typeName] = resolvedType; - return resolvedType; - } - } - - // 4. 尝试直接解析类型 - resolvedType = Type.GetType(typeName); + var resolvedType = Type.GetType(typeName); if (resolvedType != null) { _typeCache[typeName] = resolvedType; return resolvedType; } - - // 5. 回退到 object 类型 - resolvedType = typeof(object); - _typeCache[typeName] = resolvedType; - return resolvedType; } catch { // 发生异常时回退到 object 类型 - resolvedType = typeof(object); - _typeCache[typeName] = resolvedType; - return resolvedType; } - } - - /// - /// 解析泛型类型 - /// - private static Type? ResolveGenericType(Match genericMatch) - { - var genericTypeName = genericMatch.Groups[1].Value; - var genericArgs = genericMatch.Groups[2].Value; - - // 解析泛型参数 - var argTypes = ParseGenericArguments(genericArgs); - if (argTypes.Length == 0) - return null; - // 处理常见的泛型类型 - return genericTypeName switch - { - "List" => typeof(List<>).MakeGenericType(argTypes), - "Dictionary" when argTypes.Length == 2 => typeof(Dictionary<,>).MakeGenericType(argTypes), - "Array" => argTypes[0].MakeArrayType(), - "Nullable" => typeof(Nullable<>).MakeGenericType(argTypes), - _ => null - }; - } - - /// - /// 解析泛型参数 - /// - private static Type[] ParseGenericArguments(string argsString) - { - var args = SplitGenericArguments(argsString); - return args.Select(MapType).ToArray(); - } - - /// - /// 分割泛型参数字符串,处理嵌套泛型 - /// - private static string[] SplitGenericArguments(string argsString) - { - var args = new List(); - var current = ""; - var depth = 0; - - foreach (char c in argsString) - { - if (c == '<') - { - depth++; - current += c; - } - else if (c == '>') - { - depth--; - current += c; - } - else if (c == ',' && depth == 0) - { - args.Add(current.Trim()); - current = ""; - } - else - { - current += c; - } - } - - if (!string.IsNullOrEmpty(current)) - args.Add(current.Trim()); - - return args.ToArray(); - } - - /// - /// 注册自定义类型映射 - /// - /// 类型名字符串 - /// 对应的 Type 对象 - public static void RegisterCustomType(string typeName, Type type) - { - _customTypeMap[typeName] = type; - _typeCache.TryRemove(typeName, out _); // 清除缓存 + // 回退到 object 类型 + _typeCache[typeName] = typeof(object); + return typeof(object); } /// @@ -201,12 +83,4 @@ public static void ClearCache() { _typeCache.Clear(); } - - /// - /// 获取所有已注册的自定义类型 - /// - public static IReadOnlyDictionary GetCustomTypes() - { - return _customTypeMap.AsReadOnly(); - } } diff --git a/KitX Script/Kscript.CSharp.Parser/Examples/BasicUsageExample.cs b/KitX Script/Kscript.CSharp.Parser/Examples/BasicUsageExample.cs index f2e6fdc..3a35e74 100644 --- a/KitX Script/Kscript.CSharp.Parser/Examples/BasicUsageExample.cs +++ b/KitX Script/Kscript.CSharp.Parser/Examples/BasicUsageExample.cs @@ -18,6 +18,14 @@ public static async Task RunExample() try { + // 0. 初始化插件管理器 + Console.WriteLine("0. 初始化插件管理器..."); + if (!Parser.IsInitialized) + { + Parser.SetPluginManager(new MockPluginManager()); + Console.WriteLine(" ✓ 已设置 MockPluginManager"); + } + // 1. 从现有的 example.json 文件加载插件清单 Console.WriteLine("1. 加载插件清单..."); var exampleJsonPath = Path.Combine(".", "example.json"); @@ -59,9 +67,28 @@ public static async Task RunExample() // 5. 演示缓存效果 Console.WriteLine("\n5. 测试缓存效果..."); + + // 5.1 先检查是否存在缓存 + var plugins = JsonSerializer.Deserialize>(await File.ReadAllTextAsync(exampleJsonPath), new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + AllowTrailingCommas = true + }); + var hasCacheBefore = Parser.HasCache(plugins!, "ExamplePluginAssembly"); + Console.WriteLine($" 生成前检查缓存存在: {hasCacheBefore}"); + + // 5.2 第二次生成(应该使用缓存) var assembly2 = await Parser.GenerateFromFileAsync(exampleJsonPath, "ExamplePluginAssembly"); Console.WriteLine($" 第二次生成是否使用缓存: {assembly == assembly2}"); + // 5.3 强制重新生成(应该绕过缓存) + var assembly3 = Parser.Generate(plugins!, "ExamplePluginAssembly", useCache: false); + Console.WriteLine($" 强制重新生成是否使用新程序集: {assembly3 != assembly2}"); + + // 5.4 检查强制重新生成后的缓存状态 + var hasCacheAfterRegenerate = Parser.HasCache(plugins!, "ExamplePluginAssembly"); + Console.WriteLine($" 重新生成后缓存存在: {hasCacheAfterRegenerate}"); + } catch (Exception ex) { @@ -75,6 +102,12 @@ public static async Task RunExample() /// private static void RunWithBuiltInData() { + // 确保插件管理器已初始化 + if (!Parser.IsInitialized) + { + Parser.SetPluginManager(new MockPluginManager()); + } + // 创建示例插件数据 var plugins = new List { @@ -211,20 +244,15 @@ public static void RunAdvancedExample() try { - // 1. 注册自定义类型 - Console.WriteLine("1. 注册自定义类型映射..."); - Parser.RegisterCustomType("CustomType", typeof(DateTime)); - Console.WriteLine(" ✓ 已注册 CustomType -> DateTime"); - - // 2. 清除缓存 - Console.WriteLine("\n2. 清除所有缓存..."); + // 1. 清除缓存 + Console.WriteLine("1. 清除所有缓存..."); Parser.ClearCache(); Console.WriteLine(" ✓ 缓存已清除"); - // 3. 设置自定义插件管理器 - Console.WriteLine("\n3. 设置自定义插件管理器..."); - Parser.SetDefaultPluginManager(new MockPluginManager()); - Console.WriteLine(" ✓ 已设置默认插件管理器"); + // 2. 重新设置插件管理器 + Console.WriteLine("\n2. 重新设置插件管理器..."); + Parser.SetPluginManager(new MockPluginManager()); + Console.WriteLine(" ✓ 已重新设置插件管理器"); } catch (Exception ex) diff --git a/KitX Script/Kscript.CSharp.Parser/Examples/Program.cs b/KitX Script/Kscript.CSharp.Parser/Examples/Program.cs index 51b3d0f..142351e 100644 --- a/KitX Script/Kscript.CSharp.Parser/Examples/Program.cs +++ b/KitX Script/Kscript.CSharp.Parser/Examples/Program.cs @@ -1,4 +1,5 @@ using Kscript.CSharp.Parser.Examples; +using Kscript.CSharp.Parser.Core; namespace Kscript.CSharp.Parser.Examples; @@ -18,6 +19,12 @@ public static async Task Main(string[] args) try { + // 首先运行简化后的功能测试 + Console.WriteLine("=== 测试简化后的 Parser 功能 ==="); + TestSimplifiedParser(); + + Console.WriteLine("\n" + new string('=', 60)); + // 运行基础用法示例 await BasicUsageExample.RunExample(); @@ -43,4 +50,115 @@ public static async Task Main(string[] args) Console.WriteLine("\n按任意键退出..."); Console.ReadKey(); } + + /// + /// 测试简化后的 Parser 功能 + /// + private static void TestSimplifiedParser() + { + Console.WriteLine("1. 测试默认插件管理器设置..."); + + // 测试使用 MockPluginManager 中已有的插件 + var testPlugins = new List + { + new PluginInfo + { + Name = "SampleCalculator", + Version = "1.0.0", + Functions = new List + { + new Function + { + Name = "Add", + ReturnValueType = "int", + Parameters = new List + { + new Parameter { Name = "a", Type = "int", IsOptional = false }, + new Parameter { Name = "b", Type = "int", IsOptional = false } + } + } + } + } + }; + + try + { + // 先设置插件管理器 + Parser.SetPluginManager(new MockPluginManager()); + + // 使用 MockPluginManager 生成程序集 + var assembly = Parser.Generate(testPlugins, "TestAssembly"); + Console.WriteLine(" ✓ 默认 MockPluginManager 工作正常"); + + // 获取生成的类型 + var types = Parser.GetPluginTypes(assembly); + Console.WriteLine($" ✓ 生成了 {types.Count} 个插件类型"); + + foreach (var type in types) + { + var methods = Parser.GetPluginMethods(type); + Console.WriteLine($" - {type.Name}: {methods.Count} 个方法"); + } + } + catch (Exception ex) + { + Console.WriteLine($" ❌ 测试失败: {ex.Message}"); + } + + Console.WriteLine("2. 测试自定义插件管理器设置..."); + try + { + // 设置新的插件管理器 + Parser.SetPluginManager(new MockPluginManager()); + var assembly2 = Parser.Generate(testPlugins, "TestAssembly2"); + Console.WriteLine(" ✓ 自定义插件管理器设置工作正常"); + } + catch (Exception ex) + { + Console.WriteLine($" ❌ 自定义设置测试失败: {ex.Message}"); + } + + Console.WriteLine("3. 测试空参数处理..."); + try + { + // 测试 null 参数应该抛出异常 + Parser.SetPluginManager(null); + Console.WriteLine(" ❌ 空参数测试失败: 应该抛出异常但没有"); + } + catch (ArgumentNullException) + { + Console.WriteLine(" ✓ 空参数正确抛出 ArgumentNullException"); + } + catch (Exception ex) + { + Console.WriteLine($" ❌ 空参数测试失败: {ex.Message}"); + } + + Console.WriteLine("4. 测试插件存在性检测..."); + try + { + var mockManager = new MockPluginManager(); + Parser.SetPluginManager(mockManager); + + // 测试存在的插件 + var existsSampleCalculator = mockManager.IsPluginExists("SampleCalculator"); + Console.WriteLine($" ✓ SampleCalculator 存在: {existsSampleCalculator}"); + + var existsAddMethod = mockManager.IsMethodExists("SampleCalculator", "Add"); + Console.WriteLine($" ✓ SampleCalculator.Add 方法存在: {existsAddMethod}"); + + // 测试不存在的插件 + var existsNonExistent = mockManager.IsPluginExists("NonExistentPlugin"); + Console.WriteLine($" ✓ NonExistentPlugin 存在: {existsNonExistent}"); + + var existsNonExistentMethod = mockManager.IsMethodExists("SampleCalculator", "NonExistentMethod"); + Console.WriteLine($" ✓ SampleCalculator.NonExistentMethod 方法存在: {existsNonExistentMethod}"); + + Console.WriteLine(" ✓ 插件存在性检测功能工作正常"); + } + catch (Exception ex) + { + Console.WriteLine($" ❌ 插件存在性检测测试失败: {ex.Message}"); + } + } } diff --git a/KitX Script/Kscript.CSharp.Parser/Examples/RealPluginManagerExample.cs b/KitX Script/Kscript.CSharp.Parser/Examples/RealPluginManagerExample.cs index 06a7070..937a3ad 100644 --- a/KitX Script/Kscript.CSharp.Parser/Examples/RealPluginManagerExample.cs +++ b/KitX Script/Kscript.CSharp.Parser/Examples/RealPluginManagerExample.cs @@ -18,24 +18,21 @@ public static void RunExample() try { - // 1. 设置插件管理器工厂函数 - Console.WriteLine("1. 设置插件管理器工厂函数..."); + // 1. 设置插件管理器 + Console.WriteLine("1. 设置插件管理器..."); // 这里应该传入实际的 PluginsServer 实例和日志记录器 // 由于这是一个示例,我们使用 null 作为占位符 - Parser.SetPluginManagerFactory(() => - { - // 在实际使用中,这里应该传入真实的 PluginsServer 实例和日志记录器 - // 例如: - // var pluginsServer = KitX.Dashboard.Network.PluginsNetwork.PluginsServer.Instance; - // var logger = Serilog.Log.Logger; - // return new RealPluginManager(pluginsServer, message => logger.Information(message)); + // 在实际使用中,这里应该传入真实的 PluginsServer 实例和日志记录器 + // 例如: + // var pluginsServer = KitX.Dashboard.Network.PluginsNetwork.PluginsServer.Instance; + // var logger = Serilog.Log.Logger; + // Parser.SetPluginManager(new RealPluginManager(pluginsServer, message => logger.Information(message))); - Console.WriteLine(" 创建 RealPluginManager 实例..."); - return null; // 示例中返回 null,实际使用时返回真实实例 - }); + Console.WriteLine(" 使用 MockPluginManager 进行演示..."); + Parser.SetPluginManager(new MockPluginManager()); - Console.WriteLine(" ✓ 插件管理器工厂函数已设置"); + Console.WriteLine(" ✓ 插件管理器已设置"); // 2. 创建示例插件数据 Console.WriteLine("\n2. 创建示例插件数据..."); @@ -43,17 +40,18 @@ public static void RunExample() { new PluginInfo { - Name = "TestPlugin", + Name = "SampleCalculator", Version = "1.0.0", Functions = new List { new Function { - Name = "TestMethod", - ReturnValueType = "string", + Name = "Add", + ReturnValueType = "int", Parameters = new List { - new Parameter { Name = "input", Type = "string", IsOptional = false } + new Parameter { Name = "a", Type = "int", IsOptional = false }, + new Parameter { Name = "b", Type = "int", IsOptional = false } } } } @@ -123,7 +121,7 @@ public static void DashboardIntegrationExample() Console.WriteLine("\n3. 在脚本中使用生成的 API:"); Console.WriteLine("```csharp"); Console.WriteLine("// 现在可以在脚本中直接调用"); - Console.WriteLine("var result = TestPlugin.TestMethod(\"Hello World\");"); + Console.WriteLine("var result = SampleCalculator.Add(10, 20);"); Console.WriteLine("```"); Console.WriteLine("\n这样,脚本中的插件调用将通过真实的 KitX Dashboard 插件系统执行!"); diff --git a/KitX Script/Kscript.CSharp.Parser/Exceptions/ParserException.cs b/KitX Script/Kscript.CSharp.Parser/Exceptions/ParserException.cs index 5ba8106..a46417e 100644 --- a/KitX Script/Kscript.CSharp.Parser/Exceptions/ParserException.cs +++ b/KitX Script/Kscript.CSharp.Parser/Exceptions/ParserException.cs @@ -16,28 +16,4 @@ public ParserException(string message) : base(message) public ParserException(string message, Exception innerException) : base(message, innerException) { } - - /// - /// 创建类型映射异常 - /// - public static ParserException TypeMappingError(string typeName, Exception? innerException = null) - { - return new ParserException($"无法映射类型: {typeName}", innerException!); - } - - /// - /// 创建IL生成异常 - /// - public static ParserException ILGenerationError(string methodName, Exception? innerException = null) - { - return new ParserException($"生成IL代码失败: {methodName}", innerException!); - } - - /// - /// 创建程序集生成异常 - /// - public static ParserException AssemblyGenerationError(string assemblyName, Exception? innerException = null) - { - return new ParserException($"生成程序集失败: {assemblyName}", innerException!); - } } diff --git a/KitX Script/Kscript.CSharp.Parser/INTEGRATION_GUIDE.md b/KitX Script/Kscript.CSharp.Parser/INTEGRATION_GUIDE.md deleted file mode 100644 index 4fd92b9..0000000 --- a/KitX Script/Kscript.CSharp.Parser/INTEGRATION_GUIDE.md +++ /dev/null @@ -1,163 +0,0 @@ -# KitX.CSharp.Parser 与 KitX Dashboard 集成指南 - -## 🎯 集成概述 - -本文档说明如何将 KitX.CSharp.Parser 的实际插件管理器集成到 KitX Dashboard 中,实现从 MockPluginManager 到真实插件调用的升级。 - -## 📋 完成的集成工作 - -### 1. 创建 RealPluginManager -- ✅ **文件**: [`Core/RealPluginManager.cs`](../Core/RealPluginManager.cs:1-225) -- ✅ **功能**: 实现了 `IPluginManager` 接口,支持真实的 KitX Dashboard 插件调用 -- ✅ **设计**: 采用依赖注入模式,接受外部传入的 `PluginsServer` 实例和日志记录器 -- ✅ **兼容性**: 完全兼容现有的 MockPluginManager 接口 - -### 2. 扩展 MethodEmitter -- ✅ **静态支持**: 在 [`CodeGen/MethodEmitter.cs`](../CodeGen/MethodEmitter.cs:13-21) 中添加了静态插件管理器支持 -- ✅ **工厂模式**: 实现了 `SetStaticPluginManager()` 方法 -- ✅ **灵活配置**: 支持多种插件管理器实现方式 - -### 3. 更新 Parser 主入口 -- ✅ **工厂函数**: 在 [`Parser.cs`](../Parser.cs:23-32) 中添加了 `SetPluginManagerFactory()` 方法 -- ✅ **向后兼容**: 保持与现有 `SetDefaultPluginManager()` 的完全兼容 -- ✅ **自动设置**: 在生成程序集时自动设置静态插件管理器 - -### 4. 集成到 Dashboard -- ✅ **WorkflowScriptService**: 修改了 [`Services/WorkflowScriptService.cs`](../../KitX%20Clients/KitX%20Dashboard/KitX%20Dashboard/Services/WorkflowScriptService.cs:17-52) -- ✅ **静态构造**: 在静态构造函数中设置 RealPluginManager 工厂 -- ✅ **实例传递**: 将PluginManager实例传递给 Parser -- ✅ **错误处理**: 添加了完善的异常处理和日志记录 - -## 🔧 集成架构 - -``` -┌─────────────────────────────────────────────────┐ -│ KitX Dashboard │ -│ ┌─────────────────────────────────────┐ │ -│ │ WorkflowScriptService │ │ -│ │ ┌─ SetPluginManagerFactory │ │ -│ │ │ │ │ │ -│ │ │ ▼ │ │ -│ │ │ RealPluginManager │ │ -│ │ │ ┌─ PluginsServer │ │ -│ │ │ │ ┌─ FindConnector │ │ -│ │ │ │ │ │ │ -│ │ │ │ ▼ │ │ -│ │ │ │ PluginConnector │ │ -│ │ │ │ ┌─ Request │ │ -│ │ │ │ │ │ │ -│ │ │ │ ▼ │ │ -│ │ │ │ WebSocket │ │ -│ │ │ └─ Plugin Process │ │ -│ │ └─────────────────────────────┘ │ -│ │ │ │ -│ │ ▼ Generated Assembly │ │ -│ │ ┌─ MethodEmitter │ │ -│ │ │ │ ┌─ Static Constructor │ │ -│ │ │ │ │ │ │ -│ │ │ │ ▼ │ │ -│ │ │ │ │ SetStaticPluginManager │ │ -│ │ │ │ └─────────────────────┘ │ -│ │ │ │ │ -│ │ ▼ Script Engine │ │ -│ │ └─ CSharpScriptEngine │ │ -│ │ │ │ -│ │ │ │ -│ │ │ │ -│ │ │ │ -│ └─────────────────────────────────┘ │ -│ │ -│ Workflow Script Execution │ -│ ────────────────────────┘ -└─────────────────────────────────────────┘ -``` - -## 🚀 使用方法 - -### 在 KitX Dashboard 中启用 RealPluginManager - -在 KitX Dashboard 启动时,`WorkflowScriptService` 的静态构造函数会自动执行以下操作: - -1. **获取 PluginsServer 实例**: 获取 `KitX.Dashboard.Network.PluginsNetwork.PluginsServer.Instance` -2. **设置工厂函数**: 调用 `Parser.SetPluginManagerFactory()` -3. **创建 RealPluginManager**: 传入 `PluginsServer` 实例和日志记录器 -4. **自动集成**: 后续的所有 Workflow Script 执行都将使用真实的插件系统 - -### 验证集成 - -集成成功后,您可以通过以下方式验证: - -1. **编译 Dashboard**: - ```bash - cd "KitX Clients/KitX Dashboard" - dotnet build - ``` - -2. **运行 Dashboard**: 启动后检查Logger输出 - ``` - [WorkflowScriptService] RealPluginManager 工厂函数已设置 - ``` - -3. **测试 Workflow Script**: 创建并执行包含插件调用的脚本 - -## 📝 代码示例 - -### 基本集成 -```csharp -// 在 Dashboard 的初始化代码中(通常在 App.xaml.cs 或 Program.cs) -// WorkflowScriptService 的静态构造函数会自动执行此代码 - -// 手动设置(可选) -Parser.SetPluginManagerFactory(() => new RealPluginManager( - KitX.Dashboard.Network.PluginsNetwork.PluginsServer.Instance, - message => Log.Information(message) // 使用 Serilog -)); -``` - -### 脚本中使用 -```csharp -// 现在脚本中可以直接调用,将通过真实的 KitX Dashboard 插件系统执行 -var result = SampleCalculator.Add(10, 20); -var text = StringToolkit.Reverse("Hello World"); -``` - -## ⚙️ 配置选项 - -### 工厂函数 vs 默认管理器 -- **工厂函数** (`SetPluginManagerFactory`): 推荐用于生产环境 -- **默认管理器** (`SetDefaultPluginManager`): 适用于简单场景或测试 - -### 日志记录器 -- **开发阶段**: 使用 `Console.WriteLine` 或 `Debug.WriteLine` -- **生产阶段**: 使用 `Serilog.Log.Information` 或其他日志框架 - -## 🔍 故障排除 - -### 常见问题 - -1. **编译错误**: 确保已正确引用 `Kscript.CSharp.Parser` 项目 -2. **运行时错误**: 检查 `PluginsServer.Instance` 是否可用 -3. **反射异常**: 确保 Dashboard 程序集已正确加载 - -### 调试技巧 - -1. **启用详细日志**: 在 `RealPluginManager` 构造函数中添加更多日志 -2. **检查插件状态**: 验证插件是否正确连接到 Dashboard -3. **测试单独组件**: 分别测试 `RealPluginManager` 和 `PluginsServer` 连接 - -## 📈 性能考虑 - -- ✅ **延迟初始化**: RealPluginManager 只在首次使用时创建 -- ✅ **缓存机制**: 保持原有的程序集缓存功能 -- ✅ **内存效率**: 静态插件管理器实例,避免重复创建 - -## 🎉 总结 - -通过这次集成,KitX.CSharp.Parser 现在完全支持: - -1. **真实的插件调用**: 通过 KitX Dashboard 的插件系统 -2. **灵活的配置**: 支持多种插件管理器实现 -3. **向后兼容性**: 不影响现有代码 -4. **生产就绪**: 具备完整的错误处理和日志记录 - -现在 Workflow Script 中的插件调用将直接通过 KitX Dashboard 的真实插件系统执行,实现了从模拟到真实的完整升级! diff --git a/KitX Script/Kscript.CSharp.Parser/Parser.cs b/KitX Script/Kscript.CSharp.Parser/Parser.cs index c07ec35..d9b701a 100644 --- a/KitX Script/Kscript.CSharp.Parser/Parser.cs +++ b/KitX Script/Kscript.CSharp.Parser/Parser.cs @@ -12,31 +12,52 @@ namespace Kscript.CSharp.Parser; public static class Parser { /// - /// 默认插件管理器 + /// 插件管理器实例 /// - private static IPluginManager? _defaultPluginManager; + private static IPluginManager? _pluginManager; /// - /// 插件管理器工厂函数 + /// 获取Parser初始化状态 /// - private static Func? _pluginManagerFactory; + public static bool IsInitialized => _pluginManager != null; /// - /// 设置默认插件管理器 + /// 设置插件管理器 /// /// 插件管理器实例 - public static void SetDefaultPluginManager(IPluginManager pluginManager) + public static void SetPluginManager(IPluginManager pluginManager) { - _defaultPluginManager = pluginManager; + _pluginManager = pluginManager ?? throw new ArgumentNullException(nameof(pluginManager)); } /// - /// 设置插件管理器工厂函数 + /// 确保Parser已正确初始化 /// - /// 插件管理器工厂函数 - public static void SetPluginManagerFactory(Func factory) + /// 当Parser未初始化时抛出 + private static void EnsureInitialized() { - _pluginManagerFactory = factory; + if (_pluginManager == null) + { + throw new InvalidOperationException("Parser 未初始化。请先调用 SetPluginManager() 设置插件管理器。"); + } + } + + /// + /// 确保所需插件已加载 + /// + /// 插件信息列表 + /// 当所需插件未加载时抛出 + private static void EnsurePluginsLoaded(List plugins) + { + foreach (var plugin in plugins) + { + if (!_pluginManager!.IsPluginExists(plugin.Name)) + { + // TODO: 实现插件动态加载逻辑 + // 目前先抛出异常,待后续实现插件加载功能 + throw new InvalidOperationException($"所需插件未加载: {plugin.Name}。请确保插件已正确安装并启动。"); + } + } } /// @@ -44,36 +65,32 @@ public static void SetPluginManagerFactory(Func factory) /// /// 插件信息列表 /// 程序集名称 - /// 插件管理器实例(可选,默认使用 MockPluginManager) /// 是否使用缓存 /// 生成的动态程序集 - public static Assembly Generate(List plugins, string assemblyName = "DynamicPluginAssembly", - IPluginManager? pluginManager = null, bool useCache = true) + public static Assembly Generate(List plugins, string assemblyName = "DynamicPluginAssembly", bool useCache = true) { if (plugins == null || plugins.Count == 0) { throw new ArgumentException("插件列表不能为空", nameof(plugins)); } + EnsureInitialized(); + EnsurePluginsLoaded(plugins); + try { - var manager = pluginManager ?? _defaultPluginManager ?? _pluginManagerFactory?.Invoke() ?? new MockPluginManager(); - - // 设置静态插件管理器实例 - MethodEmitter.SetStaticPluginManager(manager); - if (useCache) { - return AssemblyCache.GetOrCreateAssembly(plugins, assemblyName, manager); + return AssemblyCache.GetOrCreateAssembly(plugins, assemblyName, _pluginManager!); } else { - return MethodEmitter.GenerateAssembly(plugins, assemblyName, manager); + return AssemblyCache.ForceRegenerate(plugins, _pluginManager!, assemblyName); } } catch (Exception ex) when (!(ex is ParserException)) { - throw ParserException.AssemblyGenerationError(assemblyName, ex); + throw new ParserException($"生成程序集失败: {assemblyName}", ex); } } @@ -82,11 +99,9 @@ public static Assembly Generate(List plugins, string assemblyName = /// /// 插件清单 JSON 字符串 /// 程序集名称 - /// 插件管理器实例 /// 是否使用缓存 /// 生成的动态程序集 - public static Assembly GenerateFromJson(string jsonString, string assemblyName = "DynamicPluginAssembly", - IPluginManager? pluginManager = null, bool useCache = true) + public static Assembly GenerateFromJson(string jsonString, string assemblyName = "DynamicPluginAssembly", bool useCache = true) { if (string.IsNullOrWhiteSpace(jsonString)) { @@ -106,7 +121,7 @@ public static Assembly GenerateFromJson(string jsonString, string assemblyName = throw new ParserException("JSON 反序列化失败,结果为 null"); } - return Generate(plugins, assemblyName, pluginManager, useCache); + return Generate(plugins, assemblyName, useCache); } catch (JsonException ex) { @@ -119,11 +134,9 @@ public static Assembly GenerateFromJson(string jsonString, string assemblyName = /// /// JSON 文件路径 /// 程序集名称 - /// 插件管理器实例 /// 是否使用缓存 /// 生成的动态程序集 - public static async Task GenerateFromFileAsync(string jsonFilePath, string assemblyName = "DynamicPluginAssembly", - IPluginManager? pluginManager = null, bool useCache = true) + public static async Task GenerateFromFileAsync(string jsonFilePath, string assemblyName = "DynamicPluginAssembly", bool useCache = true) { if (string.IsNullOrWhiteSpace(jsonFilePath)) { @@ -138,7 +151,7 @@ public static async Task GenerateFromFileAsync(string jsonFilePath, st try { var jsonString = await File.ReadAllTextAsync(jsonFilePath); - return GenerateFromJson(jsonString, assemblyName, pluginManager, useCache); + return GenerateFromJson(jsonString, assemblyName, useCache); } catch (Exception ex) when (!(ex is ParserException)) { @@ -146,45 +159,6 @@ public static async Task GenerateFromFileAsync(string jsonFilePath, st } } - /// - /// 强制重新生成程序集(绕过缓存) - /// - /// 插件信息列表 - /// 程序集名称 - /// 插件管理器实例 - /// 新生成的动态程序集 - public static Assembly Regenerate(List plugins, string assemblyName = "DynamicPluginAssembly", - IPluginManager? pluginManager = null) - { - if (plugins == null || plugins.Count == 0) - { - throw new ArgumentException("插件列表不能为空", nameof(plugins)); - } - - try - { - var manager = pluginManager ?? _defaultPluginManager ?? _pluginManagerFactory?.Invoke() ?? new MockPluginManager(); - - // 设置静态插件管理器实例 - MethodEmitter.SetStaticPluginManager(manager); - - return AssemblyCache.ForceRegenerate(plugins, assemblyName, manager); - } - catch (Exception ex) when (!(ex is ParserException)) - { - throw ParserException.AssemblyGenerationError(assemblyName, ex); - } - } - - /// - /// 注册自定义类型映射 - /// - /// 类型名字符串 - /// 对应的 Type 对象 - public static void RegisterCustomType(string typeName, Type type) - { - TypeMapper.RegisterCustomType(typeName, type); - } /// /// 清除所有缓存 From 010e095c4e745140a734bc35607be3fe877f36fd Mon Sep 17 00:00:00 2001 From: StarInk Date: Sat, 8 Nov 2025 02:18:57 +0100 Subject: [PATCH 11/17] =?UTF-8?q?=F0=9F=93=84=20Docs(README,=20USAGE):=20?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=96=87=E6=A1=A3=E9=80=82=E9=85=8D=E6=9C=80?= =?UTF-8?q?=E6=96=B0=E6=9E=B6=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- KitX Script/Kscript.CSharp.Parser/README.md | 268 +++++++++----------- KitX Script/Kscript.CSharp.Parser/USAGE.md | 147 +++++------ 2 files changed, 177 insertions(+), 238 deletions(-) diff --git a/KitX Script/Kscript.CSharp.Parser/README.md b/KitX Script/Kscript.CSharp.Parser/README.md index 730f4c7..fff54a1 100644 --- a/KitX Script/Kscript.CSharp.Parser/README.md +++ b/KitX Script/Kscript.CSharp.Parser/README.md @@ -4,66 +4,25 @@ --- ## 🌱 项目定位 -KitX.CSharp.Parser 是 **KitX 工作流子系统** 的第一环。 -它只做一件事: -**把插件清单(JSON)→ 运行时静态类/方法**, -让后续脚本引擎(Roslyn、CSharpScript、Eval-etc.)可以像调用普通静态方法一样使用插件功能。 -``` - JSON 清单 ─→ Parser ─→ 内存程序集(DynamicAssembly) - │ - └─ 脚本侧: SamplePlugin.Run(…) -``` +KitX.CSharp.Parser 是 **KitX 工作流子系统** 的核心组件。它专注于一个核心功能: ---- +**把插件清单(JSON)→ 运行时静态类/方法**,让脚本引擎可以像调用普通静态方法一样使用插件功能。 -## 🎯 边界与职责 -| 范围 | 属于本库 | 不属于本库 | -|---|---|---| -| 读取 JSON | ✅ 反序列化为 `PluginInfo` | ❌ 网络/磁盘 IO | -| 类型映射 | ✅ `"int"` → `System.Int32` | ❌ 复杂自定义类型序列化 | -| 生成 IL | ✅ 用 `Reflection.Emit` 生成静态类/方法 | ❌ 脚本执行、调试 | -| 生命周期 | ✅ 生成后缓存 `Assembly` | ❌ 热重载(由上层调用 `Regenerate()`) | -| 错误处理 | ✅ 语法/类型错误抛出 `ParserException` | ❌ 插件运行时异常 | +``` +JSON 清单 ─→ Parser ─→ 内存程序集 ─→ 脚本侧: SamplePlugin.Method(...) +``` --- -## 🧩 整体流程 -1. **输入** - `List`(已由外部从 JSON 反序列化好)。 - -2. **映射** - 把 `Function.ReturnValueType` / `Parameter.Type` 字符串映射到 `System.Type`。 - -3. **构建** - - 每个插件 → 一个 `static class`(`TypeBuilder`) - - 每个功能 → 一个 `public static` 方法(`MethodBuilder`) - -4. **Emit** - 方法体内构造 `PluginCallInfo`,调用 `PluginManager.Call()`, - 返回值拆箱后 `ret`。 +## 🎯 核心特性 -5. **输出** - 程序集保存到内存(或可选磁盘 DLL), - 脚本侧通过 `PluginNamespace.PluginName.Func(...)` 直接调用。 - ---- - -## 🗂️ 目录结构(初始) -``` -KitX.CSharp.Parser/ - ├─ src/ - │ ├─ Parser.cs // 对外静态入口 - │ ├─ CodeGen/ // IL 生成实现 - │ │ ├─ TypeMapper.cs // 字符串 → Type - │ │ ├─ MethodEmitter.cs // IL 指令 - │ │ └─ AssemblyCache.cs // 已生成 Assembly 缓存 - │ └─ Models/ - │ └─ PluginInfo.cs // 复用现有结构 - ├─ tests/ - │ └─ Parser.Tests/ - └─ README.md -``` +- ✅ **动态 IL 生成** - 使用 `Reflection.Emit` 生成高性能静态方法 +- ✅ **智能类型映射** - 支持基础类型和自定义类型 +- ✅ **程序集缓存** - 避免重复生成,提升性能 +- ✅ **简化设计** - 直接依赖注入,无复杂抽象层 +- ✅ **异常处理** - 完善的错误处理机制 +- ✅ **调试友好** - 详细的日志输出和统计信息 --- @@ -74,14 +33,17 @@ KitX.CSharp.Parser/ ```csharp using Kscript.CSharp.Parser; -// 1. 从 JSON 文件生成程序集 -var assembly = await Parser.GenerateFromFileAsync("example.json"); +// 1. 设置插件管理器(必须) +Parser.SetPluginManager(new MockPluginManager()); + +// 2. 从 JSON 文件生成程序集 +var assembly = await Parser.GenerateFromFileAsync("plugins.json"); -// 2. 或从插件信息列表生成 +// 3. 从插件信息列表生成程序集 var plugins = new List { /* ... */ }; var assembly = Parser.Generate(plugins); -// 3. 脚本中直接调用生成的方法 +// 4. 脚本中直接调用生成的方法 int sum = SampleCalculator.Add(10, 20); string reversed = StringToolkit.Reverse("Hello"); ``` @@ -96,81 +58,66 @@ dotnet build dotnet run ``` -## 🔌 实际插件管理器集成 - -### 概述 - -KitX.CSharp.Parser 现在支持使用实际的插件管理器替换默认的 MockPluginManager,实现与 KitX Dashboard 的真实插件调用集成。 - -### 集成步骤 - -1. **设置插件管理器工厂** - ```csharp - // 在 KitX Dashboard 启动时设置 - Parser.SetPluginManagerFactory(() => new RealPluginManager( - KitX.Dashboard.Network.PluginsNetwork.PluginsServer.Instance, - message => Log.Information(message) - )); - ``` - -2. **生成插件程序集** - ```csharp - var assembly = Parser.Generate(plugins, "DashboardPluginAssembly"); - ``` +--- -3. **在脚本中使用** - ```csharp - // 现在可以直接调用,将通过真实的 KitX Dashboard 插件系统执行 - var result = SampleCalculator.Add(10, 20); - ``` +## 🔌 API 参考 -### API 参考 +### Parser 静态类 -#### 新增方法 +#### 生成方法 | 方法 | 描述 | 参数 | |------|------|------| -| `SetPluginManagerFactory()` | 设置插件管理器工厂函数 | `Func factory` | +| `Generate()` | 从插件信息列表生成程序集 | `plugins`, `assemblyName`, `useCache` | +| `GenerateFromJson()` | 从 JSON 字符串生成程序集 | `jsonString`, `assemblyName`, `useCache` | +| `GenerateFromFileAsync()` | 从 JSON 文件异步生成程序集 | `jsonFilePath`, `assemblyName`, `useCache` | -#### RealPluginManager 构造函数 +#### 配置方法 -| 参数 | 类型 | 描述 | -|------|------|------| -| `pluginsServer` | `object` | PluginsServer 实例 | -| `logger` | `Action` | 日志记录器 | +| 方法 | 描述 | +|------|------| +| `SetPluginManager()` | 设置插件管理器实例 | `IPluginManager pluginManager` | +| `ClearCache()` | 清除所有缓存 | +| `GetCacheStatistics()` | 获取缓存统计信息 | -### 使用示例 +#### 分析方法 -详细的使用示例请参考: -- `Examples/RealPluginManagerExample.cs` - 完整的使用示例 -- `Examples/Program.cs` - 演示程序入口 +| 方法 | 描述 | +|------|------| +| `GetPluginTypes()` | 获取程序集中的所有插件类型 | +| `GetPluginMethods()` | 获取插件类型的所有方法信息 | -### 注意事项 +--- + +## 📊 类型映射支持 -- ✅ **依赖注入设计**:通过工厂函数模式实现松耦合 -- ✅ **日志统一**:支持外部传入日志记录器 -- ✅ **向后兼容**:仍支持 MockPluginManager 作为默认选项 -- ✅ **实际调用**:通过 KitX Dashboard 的插件系统进行真实调用 +| JSON 字符串 | CLR 类型 | 示例 | +|---|---|---| +| `"void"` | `System.Void` | 无返回值 | +| `"bool"` | `System.Boolean` | 布尔参数 | +| `"int"` | `System.Int32` | 整数参数 | +| `"double"` | `System.Double` | 浮点数参数 | +| `"string"` | `System.String` | 字符串参数 | +| `"object"` | `System.Object` | 对象参数 | --- -## 📁 项目结构 +## 🏗️ 项目结构 ``` KitX.CSharp.Parser/ -├─ src/ -│ ├─ Parser.cs # 主入口静态类 -│ ├─ Models/ -│ │ └─ PluginCallInfo.cs # 插件调用信息模型 -│ ├─ CodeGen/ -│ │ ├─ TypeMapper.cs # 字符串 → Type 映射 -│ │ ├─ MethodEmitter.cs # IL 方法生成器 -│ │ └─ AssemblyCache.cs # 程序集缓存管理 -│ ├─ Core/ -│ │ ├─ IPluginManager.cs # 插件管理器接口 -│ │ └─ MockPluginManager.cs # 模拟实现 -│ └─ Exceptions/ -│ └─ ParserException.cs # 自定义异常 +├─ Parser.cs # 主入口静态类 +├─ Models/ +│ └─ PluginCallInfo.cs # 插件调用信息模型 +├─ CodeGen/ +│ ├─ TypeMapper.cs # 字符串 → Type 映射 +│ ├─ MethodEmitter.cs # IL 方法生成器 +│ └─ AssemblyCache.cs # 程序集缓存管理 +├─ Core/ +│ ├─ IPluginManager.cs # 插件管理器接口 +│ └─ MockPluginManager.cs # 模拟实现 +├─ Exceptions/ +│ └─ ParserException.cs # 自定义异常 ├─ Examples/ │ ├─ BasicUsageExample.cs # 基础用法示例 │ └─ Program.cs # 演示程序入口 @@ -181,39 +128,29 @@ KitX.CSharp.Parser/ --- -## ⚙️ 主要特性 - -- ✅ **动态 IL 生成** - 使用 `Reflection.Emit` 生成高性能静态方法 -- ✅ **智能类型映射** - 支持基础类型、泛型类型和自定义类型 -- ✅ **程序集缓存** - 避免重复生成,提升性能 -- ✅ **扩展性设计** - 支持自定义插件管理器和类型映射 -- ✅ **异常处理** - 完善的错误处理机制 -- ✅ **调试友好** - 详细的日志输出和统计信息 +## 🎯 运行结果示例 ---- +``` +🚀 欢迎使用 KitX.CSharp.Parser 演示程序! -## 📐 类型映射支持 +=== 测试简化后的 Parser 功能 === +1. 测试默认插件管理器设置... + ✓ 默认 MockPluginManager 工作正常 -| JSON 字符串 | CLR 类型 | 示例 | -|---|---|---| -| `"int"` | `System.Int32` | 整数参数 | -| `"double"` | `System.Double` | 浮点数参数 | -| `"string"` | `System.String` | 字符串参数 | -| `"bool"` | `System.Boolean` | 布尔参数 | -| `"void"` | `System.Void` | 无返回值 | -| `"List"` | `List` | 泛型集合 | -| `"Dictionary"` | `Dictionary` | 字典类型 | -| 自定义 | 通过 `RegisterCustomType()` 注册 | 扩展类型 | +2. 测试自定义插件管理器设置... + ✓ 自定义插件管理器设置工作正常 ---- +3. 测试空参数处理... + ✓ 空参数正确抛出 ArgumentNullException -## 🎯 运行结果示例 - -``` -🚀 欢迎使用 KitX.CSharp.Parser 演示程序! +4. 测试插件存在性检测... + ✓ SampleCalculator 存在: True + ✓ SampleCalculator.Add 方法存在: True + ✓ NonExistentPlugin 存在: False + ✓ SampleCalculator.NonExistentMethod 方法存在: False + ✓ 插件存在性检测功能工作正常 === KitX.CSharp.Parser 基础用法示例 === - 1. 加载插件清单... ✓ 成功生成程序集: ExamplePluginAssembly @@ -233,26 +170,49 @@ KitX.CSharp.Parser/ 调用 ToUpper(world) → 结果: WORLD 4. 缓存统计信息... - 缓存统计: 1 个程序集, 1 个引用 + 缓存统计: 1 个程序集 + +5. 测试缓存效果... + 第二次生成是否使用缓存: True + 强制重新生成是否使用新程序集: True + 重新生成后缓存存在: True + +✅ 所有示例运行完成! ``` --- -## 📋 TODO / 未来计划 +## 🔧 实际插件管理器集成 + +### 设置真实插件管理器 -### 🔄 项目重构 -- [ ] **转换为纯库项目** - 移除命令行功能,专注于提供API -- [ ] **移除演示程序** - 将 `Examples/` 目录移至独立的演示项目 -- [ ] **调整项目配置** - 移除 `OutputType=Exe` 配置 +```csharp +// 在 KitX Dashboard 启动时设置 +Parser.SetPluginManager(new RealPluginManager( + KitX.Dashboard.Network.PluginsNetwork.PluginsServer.Instance, + message => Log.Information(message) +)); +``` -### 🔌 插件管理器集成 -- [ ] **替换 Mock 实现** - 当 KitX 主项目完成后,集成真实的 `PluginManager` -- [ ] **接口适配** - 根据实际需求调整 `IPluginManager` 接口定义 -- [ ] **调用机制优化** - 优化插件调用的性能和稳定性 +### 在脚本中使用 -### 🛠️ 功能增强(暂无计划) -### 📈 性能优化(暂无计划) -### 🧪 测试覆盖(暂无计划) +```csharp +// 现在可以直接调用,将通过真实的 KitX Dashboard 插件系统执行 +var result = SampleCalculator.Add(10, 20); +``` + +--- + +## 📝 注意事项 + +- ✅ **简化设计**:采用直接依赖注入,移除了复杂的工厂函数模式 +- ✅ **必须初始化**:使用前必须调用 `SetPluginManager()` 设置插件管理器 +- ✅ **插件验证**:生成程序集前会检查所需插件是否存在 +- ✅ **缓存优化**:相同插件清单只生成一次,提升性能 +- ✅ **异常安全**:完善的参数验证和错误处理机制 + +--- -> **注意**: 当前版本使用 `MockPluginManager` 进行功能验证。在 KitX 生态系统的其他组件完成后,需要将其替换为真实的插件调用实现。 +## 🎉 总结 +KitX.CSharp.Parser 提供了一个简洁、高效、易维护的解决方案,用于将插件清单转换为可直接调用的 C# API。通过简化的设计和完善的错误处理,确保了在生产环境中的稳定性和可靠性。 diff --git a/KitX Script/Kscript.CSharp.Parser/USAGE.md b/KitX Script/Kscript.CSharp.Parser/USAGE.md index 22983bb..7e0f2e7 100644 --- a/KitX Script/Kscript.CSharp.Parser/USAGE.md +++ b/KitX Script/Kscript.CSharp.Parser/USAGE.md @@ -11,98 +11,75 @@ KitX.CSharp.Parser 是 KitX 工作流子系统的核心组件,它能够将插 ```csharp using Kscript.CSharp.Parser; -// 1. 从 JSON 字符串生成程序集 +// 1. 设置插件管理器(必须) +Parser.SetPluginManager(new MockPluginManager()); + +// 2. 从 JSON 字符串生成程序集 var jsonString = File.ReadAllText("plugins.json"); var assembly = Parser.GenerateFromJson(jsonString); -// 2. 从文件生成程序集 +// 3. 从文件生成程序集 var assembly = await Parser.GenerateFromFileAsync("plugins.json"); -// 3. 从插件信息列表生成程序集 +// 4. 从插件信息列表生成程序集 var plugins = new List { /* ... */ }; var assembly = Parser.Generate(plugins); + +// 5. 脚本中直接调用生成的方法 +int sum = SampleCalculator.Add(10, 20); +string reversed = StringToolkit.Reverse("Hello"); ``` -### 动态调用生成的方法 +### 运行演示 -```csharp -// 获取生成的插件类型 -var pluginTypes = Parser.GetPluginTypes(assembly); +```bash +# 构建项目 +dotnet build -foreach (var type in pluginTypes) -{ - var methods = Parser.GetPluginMethods(type); - foreach (var method in methods) - { - // 动态调用方法 - var result = method.Invoke(null, new object[] { /* 参数 */ }); - Console.WriteLine($"结果: {result}"); - } -} +# 运行演示程序 +dotnet run ``` ## 🔧 API 参考 ### Parser 静态类 -主入口类,提供所有核心功能。 - #### 生成方法 | 方法 | 描述 | 参数 | |------|------|------| -| `Generate()` | 从插件信息列表生成程序集 | `plugins`, `assemblyName`, `pluginManager`, `useCache` | -| `GenerateFromJson()` | 从 JSON 字符串生成程序集 | `jsonString`, `assemblyName`, `pluginManager`, `useCache` | -| `GenerateFromFileAsync()` | 从 JSON 文件异步生成程序集 | `jsonFilePath`, `assemblyName`, `pluginManager`, `useCache` | -| `Regenerate()` | 强制重新生成程序集(绕过缓存) | `plugins`, `assemblyName`, `pluginManager` | +| `Generate()` | 从插件信息列表生成程序集 | `plugins`, `assemblyName`, `useCache` | +| `GenerateFromJson()` | 从 JSON 字符串生成程序集 | `jsonString`, `assemblyName`, `useCache` | +| `GenerateFromFileAsync()` | 从 JSON 文件异步生成程序集 | `jsonFilePath`, `assemblyName`, `useCache` | #### 配置方法 | 方法 | 描述 | |------|------| -| `SetDefaultPluginManager()` | 设置默认插件管理器 | -| `RegisterCustomType()` | 注册自定义类型映射 | +| `SetPluginManager()` | 设置插件管理器实例 | `IPluginManager pluginManager` | | `ClearCache()` | 清除所有缓存 | +| `GetCacheStatistics()` | 获取缓存统计信息 | #### 分析方法 | 方法 | 描述 | |------|------| -| `GetPluginTypes()` | 获取程序集中的插件类型 | -| `GetPluginMethods()` | 获取插件类型的方法信息 | -| `GetCacheStatistics()` | 获取缓存统计信息 | +| `GetPluginTypes()` | 获取程序集中的所有插件类型 | +| `GetPluginMethods()` | 获取插件类型的所有方法信息 | | `HasCache()` | 检查是否存在缓存 | -### 类型映射系统 - -#### 支持的基础类型 - -| JSON 字符串 | CLR 类型 | -|-------------|----------| -| `"void"` | `System.Void` | -| `"bool"` | `System.Boolean` | -| `"int"` | `System.Int32` | -| `"double"` | `System.Double` | -| `"string"` | `System.String` | -| `"object"` | `System.Object` | - -#### 泛型类型支持 - -```csharp -// 支持的泛型类型 -"List" → List -"Dictionary" → Dictionary -"Nullable" → int? -"Array" → string[] -``` +## 📊 类型映射系统 -#### 自定义类型映射 +### 支持的基础类型 -```csharp -// 注册自定义类型 -Parser.RegisterCustomType("DateTime", typeof(DateTime)); -Parser.RegisterCustomType("MyCustomType", typeof(MyClass)); -``` +| JSON 字符串 | CLR 类型 | 示例 | +|---|---|---| +| `"void"` | `System.Void` | 无返回值 | +| `"bool"` | `System.Boolean` | 布尔参数 | +| `"int"` | `System.Int32` | 整数参数 | +| `"double"` | `System.Double` | 浮点数参数 | +| `"string"` | `System.String` | 字符串参数 | +| `"object"` | `System.Object` | 对象参数 | ## 🏗️ 架构设计 @@ -178,30 +155,10 @@ Parser.RegisterCustomType("Guid", typeof(Guid)); // 3. 合理使用强制重新生成 if (pluginChanged) { - assembly = Parser.Regenerate(plugins); + assembly = Parser.Generate(plugins, useCache: false); } ``` -## 🔍 调试功能 - -### 日志输出 - -使用 `MockPluginManager` 时会输出详细的调用日志: - -``` -[MockPluginManager] 调用插件方法: SampleCalculator.Add(10, 20) -[MockPluginManager] 参数类型: [Int32, Int32] -[MockPluginManager] 期望返回类型: Int32 -[MockPluginManager] 返回结果: 30 -``` - -### 缓存统计 - -```csharp -var stats = Parser.GetCacheStatistics(); -Console.WriteLine($"缓存统计: {stats.CachedAssemblyCount} 个程序集"); -``` - ## 🚨 错误处理 ### 异常类型 @@ -209,6 +166,7 @@ Console.WriteLine($"缓存统计: {stats.CachedAssemblyCount} 个程序集"); - `ParserException` - 解析器相关异常 - `ArgumentException` - 参数错误 - `FileNotFoundException` - 文件不存在 +- `InvalidOperationException` - 初始化状态错误 ### 异常处理示例 @@ -227,6 +185,26 @@ catch (JsonException ex) } ``` +## 🔍 调试功能 + +### 日志输出 + +使用 `MockPluginManager` 时会输出详细的调用日志: + +``` +[MockPluginManager] 调用插件方法: SampleCalculator.Add(10, 20) +[MockPluginManager] 参数类型: [Int32, Int32] +[MockPluginManager] 期望返回类型: Int32 +[MockPluginManager] 返回结果: 30 +``` + +### 缓存统计 + +```csharp +var stats = Parser.GetCacheStatistics(); +Console.WriteLine($"缓存统计: {stats.CachedAssemblyCount} 个程序集"); +``` + ## 🔌 扩展点 ### 自定义插件管理器 @@ -244,10 +222,10 @@ public class CustomPluginManager : IPluginManager } // 设置自定义管理器 -Parser.SetDefaultPluginManager(new CustomPluginManager()); +Parser.SetPluginManager(new CustomPluginManager()); ``` -### 自定义类型映射 +### 自定义类型映射(已移除,未来可能会用更好的方案重新添加回来,取决于上游协议) ```csharp // 扩展类型映射 @@ -269,7 +247,7 @@ TypeMapper.RegisterCustomType("Color", typeof(System.Drawing.Color)); ```csharp // 在脚本中使用生成的API -var script = @" +var Script = @" int result = SampleCalculator.Add(10, 20); string reversed = StringToolkit.Reverse(""Hello""); return result; @@ -277,16 +255,17 @@ var script = @" // 通过脚本引擎执行 var assembly = Parser.Generate(plugins); -var scriptResult = ExecuteScript(script, assembly); +var ScriptResult = ExecuteScript(Script, assembly); ``` ## 📚 更多示例 完整的使用示例请参考: + - `Examples/BasicUsageExample.cs` - 基础功能演示 - `Examples/Program.cs` - 控制台演示程序 +- `RealPluginManagerExample.cs` - "真实"插件管理器示例 -## 🔗 相关项目 +## 🎯 总结 -- **KitX.Shared.CSharp** - 共享数据模型 -- **KitX 主项目** - 完整的插件生态系统 +KitX.CSharp.Parser 提供了一个简洁、高效、易维护的解决方案,用于将插件清单转换为可直接调用的 C# API。通过简化的设计和完善的错误处理,确保了在生产环境中的稳定性和可靠性。 From 6fa09868f34d5ba76af76010f0eb6e16caa1b49c Mon Sep 17 00:00:00 2001 From: StarInk Date: Sat, 8 Nov 2025 10:35:17 +0100 Subject: [PATCH 12/17] =?UTF-8?q?=F0=9F=A7=A9=20Refactor(Examples):=20?= =?UTF-8?q?=E5=B0=86=E5=9F=BA=E7=A1=80=E7=94=A8=E6=B3=95=E7=A4=BA=E4=BE=8B?= =?UTF-8?q?=E5=92=8C=E5=AE=9E=E9=99=85=E6=8F=92=E4=BB=B6=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E5=99=A8=E4=BD=BF=E7=94=A8=E7=A4=BA=E4=BE=8B=E6=8B=86=E5=87=BA?= =?UTF-8?q?=E4=B8=BA=E7=8B=AC=E7=AB=8B=E7=A8=8B=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Examples/BasicUsageExample.cs | 265 ++++++++++++++++++ .../Examples/Program.cs | 165 +++++++++++ .../Examples/RealPluginManagerExample.cs | 130 +++++++++ .../Kscript.CSharp.Parser.Examples.csproj | 22 ++ 4 files changed, 582 insertions(+) create mode 100644 KitX Script/Kscript.CSharp.Parser.Examples/Examples/BasicUsageExample.cs create mode 100644 KitX Script/Kscript.CSharp.Parser.Examples/Examples/Program.cs create mode 100644 KitX Script/Kscript.CSharp.Parser.Examples/Examples/RealPluginManagerExample.cs create mode 100644 KitX Script/Kscript.CSharp.Parser.Examples/Kscript.CSharp.Parser.Examples.csproj diff --git a/KitX Script/Kscript.CSharp.Parser.Examples/Examples/BasicUsageExample.cs b/KitX Script/Kscript.CSharp.Parser.Examples/Examples/BasicUsageExample.cs new file mode 100644 index 0000000..6d15ce9 --- /dev/null +++ b/KitX Script/Kscript.CSharp.Parser.Examples/Examples/BasicUsageExample.cs @@ -0,0 +1,265 @@ +using System.Reflection; +using System.Text.Json; +using KitX.Shared.CSharp.Plugin; +using Kscript.CSharp.Parser; +using Kscript.CSharp.Parser.Core; + +namespace Kscript.CSharp.Parser.Examples; + +/// +/// 基础用法示例 +/// +public static class BasicUsageExample +{ + /// + /// 演示基本功能 + /// + public static async Task RunExample() + { + Console.WriteLine("=== KitX.CSharp.Parser 基础用法示例 ===\n"); + + try + { + // 0. 初始化插件管理器 + Console.WriteLine("0. 初始化插件管理器..."); + if (!Parser.IsInitialized) + { + Parser.SetPluginManager(new MockPluginManager()); + Console.WriteLine(" ✓ 已设置 MockPluginManager"); + } + + // 1. 从现有的 example.json 文件加载插件清单 + Console.WriteLine("1. 加载插件清单..."); + var exampleJsonPath = Path.Combine(".", "example.json"); + if (!File.Exists(exampleJsonPath)) + { + Console.WriteLine($" 警告: example.json 文件不存在于 {exampleJsonPath}"); + Console.WriteLine(" 将使用内置示例数据"); + RunWithBuiltInData(); + return; + } + + var assembly = await Parser.GenerateFromFileAsync(exampleJsonPath, "ExamplePluginAssembly"); + Console.WriteLine($" ✓ 成功生成程序集: {assembly.FullName}"); + + // 2. 获取生成的插件类型 + Console.WriteLine("\n2. 分析生成的插件类型..."); + var pluginTypes = Parser.GetPluginTypes(assembly); + Console.WriteLine($" 发现 {pluginTypes.Count} 个插件类:"); + + foreach (var type in pluginTypes) + { + Console.WriteLine($" - {type.Name}"); + var methods = Parser.GetPluginMethods(type); + foreach (var method in methods) + { + var parameters = string.Join(", ", method.GetParameters().Select(p => $"{p.ParameterType.Name} {p.Name}")); + Console.WriteLine($" └── {method.ReturnType.Name} {method.Name}({parameters})"); + } + } + + // 3. 动态调用插件方法 + Console.WriteLine("\n3. 动态调用插件方法..."); + DynamicInvokeExample(assembly); + + // 4. 缓存统计 + Console.WriteLine("\n4. 缓存统计信息..."); + var stats = Parser.GetCacheStatistics(); + Console.WriteLine($" {stats}"); + + // 5. 演示缓存效果 + Console.WriteLine("\n5. 测试缓存效果..."); + + // 5.1 先检查是否存在缓存 + var plugins = JsonSerializer.Deserialize>(await File.ReadAllTextAsync(exampleJsonPath), new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + AllowTrailingCommas = true + }); + var hasCacheBefore = Parser.HasCache(plugins!, "ExamplePluginAssembly"); + Console.WriteLine($" 生成前检查缓存存在: {hasCacheBefore}"); + + // 5.2 第二次生成(应该使用缓存) + var assembly2 = await Parser.GenerateFromFileAsync(exampleJsonPath, "ExamplePluginAssembly"); + Console.WriteLine($" 第二次生成是否使用缓存: {assembly == assembly2}"); + + // 5.3 强制重新生成(应该绕过缓存) + var assembly3 = Parser.Generate(plugins!, "ExamplePluginAssembly", useCache: false); + Console.WriteLine($" 强制重新生成是否使用新程序集: {assembly3 != assembly2}"); + + // 5.4 检查强制重新生成后的缓存状态 + var hasCacheAfterRegenerate = Parser.HasCache(plugins!, "ExamplePluginAssembly"); + Console.WriteLine($" 重新生成后缓存存在: {hasCacheAfterRegenerate}"); + + } + catch (Exception ex) + { + Console.WriteLine($"❌ 示例执行失败: {ex.Message}"); + Console.WriteLine($"详细信息: {ex}"); + } + } + + /// + /// 使用内置数据运行示例 + /// + private static void RunWithBuiltInData() + { + // 确保插件管理器已初始化 + if (!Parser.IsInitialized) + { + Parser.SetPluginManager(new MockPluginManager()); + } + + // 创建示例插件数据 + var plugins = new List + { + new PluginInfo + { + Name = "TestCalculator", + Version = "1.0.0", + Functions = new List + { + new Function + { + Name = "Add", + ReturnValueType = "int", + Parameters = new List + { + new Parameter { Name = "a", Type = "int", IsOptional = false }, + new Parameter { Name = "b", Type = "int", IsOptional = false } + } + }, + new Function + { + Name = "Multiply", + ReturnValueType = "double", + Parameters = new List + { + new Parameter { Name = "x", Type = "double", IsOptional = false }, + new Parameter { Name = "y", Type = "double", IsOptional = false } + } + } + } + } + }; + + Console.WriteLine(" 使用内置示例数据..."); + var assembly = Parser.Generate(plugins, "BuiltInExampleAssembly"); + Console.WriteLine($" ✓ 成功生成程序集: {assembly.FullName}"); + + DynamicInvokeExample(assembly); + } + + /// + /// 动态调用示例 + /// + private static void DynamicInvokeExample(Assembly assembly) + { + var pluginTypes = Parser.GetPluginTypes(assembly); + + foreach (var type in pluginTypes) + { + Console.WriteLine($"\n 调用插件: {type.Name}"); + var methods = Parser.GetPluginMethods(type); + + foreach (var method in methods) + { + try + { + object? result = null; + var parameters = method.GetParameters(); + + // 根据方法名和参数生成测试数据 + object[] args = GenerateTestArguments(method.Name, parameters); + + Console.WriteLine($" 调用 {method.Name}({string.Join(", ", args)})"); + + // 动态调用方法 + result = method.Invoke(null, args); + + Console.WriteLine($" → 结果: {result}"); + } + catch (Exception ex) + { + Console.WriteLine($" → 调用失败: {ex.Message}"); + } + } + } + } + + /// + /// 生成测试参数 + /// + private static object[] GenerateTestArguments(string methodName, ParameterInfo[] parameters) + { + var args = new object[parameters.Length]; + + for (int i = 0; i < parameters.Length; i++) + { + var param = parameters[i]; + + // 根据参数类型和方法名生成合适的测试值 + args[i] = param.ParameterType.Name switch + { + "Int32" => methodName switch + { + "Add" => i == 0 ? 10 : 20, + "Divide" when param.Name == "decimals" => 2, + _ => i + 1 + }, + "Double" => methodName switch + { + "Divide" => i == 0 ? 10.0 : 3.0, + "Multiply" => i == 0 ? 2.5 : 4.0, + _ => (i + 1) * 1.5 + }, + "String" => methodName switch + { + "Reverse" => "Hello", + "ToUpper" => "world", + _ => $"test{i}" + }, + _ => GetDefaultValue(param.ParameterType) + }; + } + + return args; + } + + /// + /// 获取类型默认值 + /// + private static object GetDefaultValue(Type type) + { + if (type.IsValueType) + return Activator.CreateInstance(type)!; + + return type == typeof(string) ? "default" : null!; + } + + /// + /// 演示高级功能 + /// + public static void RunAdvancedExample() + { + Console.WriteLine("\n=== 高级功能演示 ===\n"); + + try + { + // 1. 清除缓存 + Console.WriteLine("1. 清除所有缓存..."); + Parser.ClearCache(); + Console.WriteLine(" ✓ 缓存已清除"); + + // 2. 重新设置插件管理器 + Console.WriteLine("\n2. 重新设置插件管理器..."); + Parser.SetPluginManager(new MockPluginManager()); + Console.WriteLine(" ✓ 已重新设置插件管理器"); + + } + catch (Exception ex) + { + Console.WriteLine($"❌ 高级功能演示失败: {ex.Message}"); + } + } +} diff --git a/KitX Script/Kscript.CSharp.Parser.Examples/Examples/Program.cs b/KitX Script/Kscript.CSharp.Parser.Examples/Examples/Program.cs new file mode 100644 index 0000000..d9ae99c --- /dev/null +++ b/KitX Script/Kscript.CSharp.Parser.Examples/Examples/Program.cs @@ -0,0 +1,165 @@ +using Kscript.CSharp.Parser.Examples; +using Kscript.CSharp.Parser.Core; +using KitX.Shared.CSharp.Plugin; + +namespace Kscript.CSharp.Parser.Examples; + +/// +/// 演示程序入口 +/// +public static class Program +{ + /// + /// 主入口方法 + /// + public static async Task Main(string[] args) + { + Console.OutputEncoding = System.Text.Encoding.UTF8; + Console.WriteLine("🚀 欢迎使用 KitX.CSharp.Parser 演示程序!"); + Console.WriteLine("==================================================\n"); + + try + { + // 首先运行简化后的功能测试 + Console.WriteLine("=== 测试简化后的 Parser 功能 ==="); + TestSimplifiedParser(); + + Console.WriteLine("\n" + new string('=', 60)); + + // 运行基础用法示例 + await BasicUsageExample.RunExample(); + + // 运行高级功能示例 + BasicUsageExample.RunAdvancedExample(); + + // 运行实际插件管理器示例 + Console.WriteLine("\n" + new string('=', 60)); + RealPluginManagerExample.RunExample(); + + Console.WriteLine("\n" + new string('=', 60)); + RealPluginManagerExample.DashboardIntegrationExample(); + + Console.WriteLine("\n=================================================="); + Console.WriteLine("✅ 所有示例运行完成!"); + } + catch (Exception ex) + { + Console.WriteLine($"\n❌ 程序运行失败: {ex.Message}"); + Console.WriteLine($"详细错误信息:\n{ex}"); + } + + Console.WriteLine("\n按任意键退出..."); + Console.ReadKey(); + } + + /// + /// 测试简化后的 Parser 功能 + /// + private static void TestSimplifiedParser() + { + Console.WriteLine("1. 测试默认插件管理器设置..."); + + // 测试使用 MockPluginManager 中已有的插件 + var testPlugins = new List + { + new PluginInfo + { + Name = "SampleCalculator", + Version = "1.0.0", + Functions = new List + { + new Function + { + Name = "Add", + ReturnValueType = "int", + Parameters = new List + { + new Parameter { Name = "a", Type = "int", IsOptional = false }, + new Parameter { Name = "b", Type = "int", IsOptional = false } + } + } + } + } + }; + + try + { + // 先设置插件管理器 + Parser.SetPluginManager(new MockPluginManager()); + + // 使用 MockPluginManager 生成程序集 + var assembly = Parser.Generate(testPlugins, "TestAssembly"); + Console.WriteLine(" ✓ 默认 MockPluginManager 工作正常"); + + // 获取生成的类型 + var types = Parser.GetPluginTypes(assembly); + Console.WriteLine($" ✓ 生成了 {types.Count} 个插件类型"); + + foreach (var type in types) + { + var methods = Parser.GetPluginMethods(type); + Console.WriteLine($" - {type.Name}: {methods.Count} 个方法"); + } + } + catch (Exception ex) + { + Console.WriteLine($" ❌ 测试失败: {ex.Message}"); + } + + Console.WriteLine("2. 测试自定义插件管理器设置..."); + try + { + // 设置新的插件管理器 + Parser.SetPluginManager(new MockPluginManager()); + var assembly2 = Parser.Generate(testPlugins, "TestAssembly2"); + Console.WriteLine(" ✓ 自定义插件管理器设置工作正常"); + } + catch (Exception ex) + { + Console.WriteLine($" ❌ 自定义设置测试失败: {ex.Message}"); + } + + Console.WriteLine("3. 测试空参数处理..."); + try + { + // 测试 null 参数应该抛出异常 + Parser.SetPluginManager(null!); + Console.WriteLine(" ❌ 空参数测试失败: 应该抛出异常但没有"); + } + catch (ArgumentNullException) + { + Console.WriteLine(" ✓ 空参数正确抛出 ArgumentNullException"); + } + catch (Exception ex) + { + Console.WriteLine($" ❌ 空参数测试失败: {ex.Message}"); + } + + Console.WriteLine("4. 测试插件存在性检测..."); + try + { + var mockManager = new MockPluginManager(); + Parser.SetPluginManager(mockManager); + + // 测试存在的插件 + var existsSampleCalculator = mockManager.IsPluginExists("SampleCalculator"); + Console.WriteLine($" ✓ SampleCalculator 存在: {existsSampleCalculator}"); + + var existsAddMethod = mockManager.IsMethodExists("SampleCalculator", "Add"); + Console.WriteLine($" ✓ SampleCalculator.Add 方法存在: {existsAddMethod}"); + + // 测试不存在的插件 + var existsNonExistent = mockManager.IsPluginExists("NonExistentPlugin"); + Console.WriteLine($" ✓ NonExistentPlugin 存在: {existsNonExistent}"); + + var existsNonExistentMethod = mockManager.IsMethodExists("SampleCalculator", "NonExistentMethod"); + Console.WriteLine($" ✓ SampleCalculator.NonExistentMethod 方法存在: {existsNonExistentMethod}"); + + Console.WriteLine(" ✓ 插件存在性检测功能工作正常"); + } + catch (Exception ex) + { + Console.WriteLine($" ❌ 插件存在性检测测试失败: {ex.Message}"); + } + } +} diff --git a/KitX Script/Kscript.CSharp.Parser.Examples/Examples/RealPluginManagerExample.cs b/KitX Script/Kscript.CSharp.Parser.Examples/Examples/RealPluginManagerExample.cs new file mode 100644 index 0000000..b44528e --- /dev/null +++ b/KitX Script/Kscript.CSharp.Parser.Examples/Examples/RealPluginManagerExample.cs @@ -0,0 +1,130 @@ +using KitX.Shared.CSharp.Plugin; +using Kscript.CSharp.Parser; +using Kscript.CSharp.Parser.Core; +using System.Text.Json; + +namespace Kscript.CSharp.Parser.Examples; + +/// +/// 实际插件管理器使用示例 +/// +public static class RealPluginManagerExample +{ + /// + /// 演示如何使用 RealPluginManager + /// + public static void RunExample() + { + Console.WriteLine("=== KitX.CSharp.Parser 实际插件管理器使用示例 ===\n"); + + try + { + // 1. 设置插件管理器 + Console.WriteLine("1. 设置插件管理器..."); + + // 这里应该传入实际的 PluginsServer 实例和日志记录器 + // 由于这是一个示例,我们使用 null 作为占位符 + // 在实际使用中,这里应该传入真实的 PluginsServer 实例和日志记录器 + // 例如: + // var pluginsServer = KitX.Dashboard.Network.PluginsNetwork.PluginsServer.Instance; + // var logger = Serilog.Log.Logger; + // Parser.SetPluginManager(new RealPluginManager(pluginsServer, message => logger.Information(message))); + + Console.WriteLine(" 使用 MockPluginManager 进行演示..."); + Parser.SetPluginManager(new MockPluginManager()); + + Console.WriteLine(" ✓ 插件管理器已设置"); + + // 2. 创建示例插件数据 + Console.WriteLine("\n2. 创建示例插件数据..."); + var examplePlugins = new List + { + new PluginInfo + { + Name = "SampleCalculator", + Version = "1.0.0", + Functions = new List + { + new Function + { + Name = "Add", + ReturnValueType = "int", + Parameters = new List + { + new Parameter { Name = "a", Type = "int", IsOptional = false }, + new Parameter { Name = "b", Type = "int", IsOptional = false } + } + } + } + } + }; + + Console.WriteLine(" ✓ 示例插件数据已创建"); + + // 3. 生成动态程序集 + Console.WriteLine("\n3. 生成动态程序集..."); + var assembly = Parser.Generate(examplePlugins, "RealPluginManagerExampleAssembly"); + Console.WriteLine($" ✓ 成功生成程序集: {assembly.FullName}"); + + // 4. 获取生成的插件类型 + Console.WriteLine("\n4. 分析生成的插件类型..."); + var pluginTypes = Parser.GetPluginTypes(assembly); + Console.WriteLine($" 发现 {pluginTypes.Count} 个插件类:"); + + foreach (var type in pluginTypes) + { + Console.WriteLine($" - {type.Name}"); + var methods = Parser.GetPluginMethods(type); + foreach (var method in methods) + { + var parameters = string.Join(", ", method.GetParameters().Select(p => $"{p.ParameterType.Name} {p.Name}")); + Console.WriteLine($" └── {method.ReturnType.Name} {method.Name}({parameters})"); + } + } + + Console.WriteLine("\n=== 示例完成 ==="); + Console.WriteLine("\n注意:在实际使用中,您需要:"); + Console.WriteLine("1. 确保 KitX Dashboard 正在运行"); + Console.WriteLine("2. 传入真实的 PluginsServer.Instance"); + Console.WriteLine("3. 传入真实的日志记录器实例"); + Console.WriteLine("4. 确保插件已正确连接到 Dashboard"); + } + catch (Exception ex) + { + Console.WriteLine($"❌ 示例执行失败: {ex.Message}"); + Console.WriteLine($"详细信息: {ex}"); + } + } + + /// + /// 演示如何在 KitX Dashboard 中集成 + /// + public static void DashboardIntegrationExample() + { + Console.WriteLine("=== KitX Dashboard 集成示例 ===\n"); + + Console.WriteLine("在 KitX Dashboard 中使用 RealPluginManager 的步骤:\n"); + + Console.WriteLine("1. 在 Dashboard 启动时设置插件管理器工厂:"); + Console.WriteLine("```csharp"); + Console.WriteLine("// 在 Dashboard 的初始化代码中"); + Console.WriteLine("Parser.SetPluginManagerFactory(() => new RealPluginManager("); + Console.WriteLine(" KitX.Dashboard.Network.PluginsNetwork.PluginsServer.Instance,"); + Console.WriteLine(" message => Log.Information(message)"); + Console.WriteLine("));"); + Console.WriteLine("```"); + + Console.WriteLine("\n2. 生成插件程序集:"); + Console.WriteLine("```csharp"); + Console.WriteLine("var assembly = Parser.Generate(plugins, \"DashboardPluginAssembly\");"); + Console.WriteLine("```"); + + Console.WriteLine("\n3. 在脚本中使用生成的 API:"); + Console.WriteLine("```csharp"); + Console.WriteLine("// 现在可以在脚本中直接调用"); + Console.WriteLine("var result = SampleCalculator.Add(10, 20);"); + Console.WriteLine("```"); + + Console.WriteLine("\n这样,脚本中的插件调用将通过真实的 KitX Dashboard 插件系统执行!"); + } +} diff --git a/KitX Script/Kscript.CSharp.Parser.Examples/Kscript.CSharp.Parser.Examples.csproj b/KitX Script/Kscript.CSharp.Parser.Examples/Kscript.CSharp.Parser.Examples.csproj new file mode 100644 index 0000000..64343b5 --- /dev/null +++ b/KitX Script/Kscript.CSharp.Parser.Examples/Kscript.CSharp.Parser.Examples.csproj @@ -0,0 +1,22 @@ + + + + net10.0 + preview + enable + enable + Kscript.CSharp.Parser.Examples + Exe + Kscript.CSharp.Parser.Examples.Program + + + + + + + + + + + + From a1ad5e77b681d8c4ec74582a821f3d4426171ea1 Mon Sep 17 00:00:00 2001 From: StarInk Date: Sat, 8 Nov 2025 10:35:42 +0100 Subject: [PATCH 13/17] =?UTF-8?q?=F0=9F=92=BE=20Feat(MethodEmitter,=20Asse?= =?UTF-8?q?mblyCache):=20=E6=B7=BB=E5=8A=A0=E5=8F=AF=E6=94=B6=E9=9B=86?= =?UTF-8?q?=E7=9A=84=E7=A8=8B=E5=BA=8F=E9=9B=86=E5=8A=A0=E8=BD=BD=E4=B8=8A?= =?UTF-8?q?=E4=B8=8B=E6=96=87=EF=BC=8C=E6=94=AF=E6=8C=81=E5=8A=A8=E6=80=81?= =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E9=9B=86=E7=9A=84=E5=8D=B8=E8=BD=BD=E4=B8=8E?= =?UTF-8?q?=E6=B8=85=E7=90=86=EF=BC=9B=E6=9B=B4=E6=96=B0Mock=E6=8F=92?= =?UTF-8?q?=E4=BB=B6=E7=AE=A1=E7=90=86=E5=99=A8=E4=BB=A5=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=96=B0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CodeGen/AssemblyCache.cs | 4 +- .../CodeGen/MethodEmitter.cs | 115 +++++++++++++++++- .../Core/MockPluginManager.cs | 61 +++++++--- .../Kscript.CSharp.Parser.csproj | 6 +- 4 files changed, 159 insertions(+), 27 deletions(-) diff --git a/KitX Script/Kscript.CSharp.Parser/CodeGen/AssemblyCache.cs b/KitX Script/Kscript.CSharp.Parser/CodeGen/AssemblyCache.cs index 50f8d8a..4e0e193 100644 --- a/KitX Script/Kscript.CSharp.Parser/CodeGen/AssemblyCache.cs +++ b/KitX Script/Kscript.CSharp.Parser/CodeGen/AssemblyCache.cs @@ -70,6 +70,8 @@ private static string GenerateCacheKey(List plugins, string assembly public static void ClearCache() { _assemblyCache.Clear(); + // 清除所有程序集加载上下文 + MethodEmitter.ClearAllAssemblyContexts(); } /// @@ -111,7 +113,7 @@ public static Assembly ForceRegenerate(List plugins, Core.IPluginMan // 移除现有缓存 _assemblyCache.TryRemove(cacheKey, out _); - // 生成新的程序集 + // 生成新的程序集(这会自动卸载旧的程序集加载上下文) var newAssembly = MethodEmitter.GenerateAssembly(plugins, assemblyName, pluginManager); // 添加到缓存 diff --git a/KitX Script/Kscript.CSharp.Parser/CodeGen/MethodEmitter.cs b/KitX Script/Kscript.CSharp.Parser/CodeGen/MethodEmitter.cs index 1fe2cc8..e732fb4 100644 --- a/KitX Script/Kscript.CSharp.Parser/CodeGen/MethodEmitter.cs +++ b/KitX Script/Kscript.CSharp.Parser/CodeGen/MethodEmitter.cs @@ -1,14 +1,37 @@ using Kscript.CSharp.Parser.Core; using Kscript.CSharp.Parser.Exceptions; using Kscript.CSharp.Parser.Models; +using System.Reflection.Emit; +using System.Runtime.Loader; +using System.Collections.Concurrent; namespace Kscript.CSharp.Parser.CodeGen; +/// +/// 可收集的程序集加载上下文,支持程序集卸载 +/// +public class CollectibleAssemblyLoadContext : AssemblyLoadContext +{ + public CollectibleAssemblyLoadContext(string name) : base(name, isCollectible: true) + { + } + + protected override Assembly Load(AssemblyName assemblyName) + { + // 让默认上下文处理核心程序集加载 + return null; + } +} + /// /// IL 方法生成器,负责生成插件调用的静态方法 /// public static class MethodEmitter { + /// + /// 程序集加载上下文缓存,用于隔离和卸载程序集 + /// + private static readonly ConcurrentDictionary _loadContexts = new(); /// /// 为插件生成动态程序集 /// @@ -21,12 +44,22 @@ public static Assembly GenerateAssembly(List plugins, { try { - // 创建动态程序集 - var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly( + // 如果已存在相同名称的程序集加载上下文,先卸载它 + if (_loadContexts.TryGetValue(assemblyName, out var existingContext)) + { + UnloadAssemblyContext(assemblyName, existingContext); + } + + // 创建新的可卸载的程序集加载上下文 + var loadContext = new CollectibleAssemblyLoadContext(assemblyName); + _loadContexts.TryAdd(assemblyName, loadContext); + + // 使用PersistedAssemblyBuilder创建可保存的动态程序集 + var persistedAssemblyBuilder = new PersistedAssemblyBuilder( new AssemblyName(assemblyName), - AssemblyBuilderAccess.Run); + typeof(object).Assembly); - var moduleBuilder = assemblyBuilder.DefineDynamicModule($"{assemblyName}.dll"); + var moduleBuilder = persistedAssemblyBuilder.DefineDynamicModule($"{assemblyName}.dll"); // 为每个插件生成静态类 foreach (var plugin in plugins) @@ -34,7 +67,41 @@ public static Assembly GenerateAssembly(List plugins, GeneratePluginClass(moduleBuilder, plugin, pluginManager); } - return assemblyBuilder; + // 创建所有类型 + foreach (var plugin in plugins) + { + // 确保所有类型都已创建 + // 类型在GeneratePluginClass中已经创建 + } + + // 保存到临时文件并重新加载为可引用的程序集 + var tempPath = Path.Combine(Path.GetTempPath(), $"{assemblyName}_{Guid.NewGuid()}.dll"); + + using (var fileStream = File.Create(tempPath)) + { + persistedAssemblyBuilder.Save(fileStream); + } + + // 使用自定义加载上下文从文件加载程序集 + var assembly = loadContext.LoadFromAssemblyPath(tempPath); + + Console.WriteLine($"[MethodEmitter] 使用CollectibleAssemblyLoadContext生成并加载程序集: {assemblyName} -> {tempPath}"); + + // 注册临时文件删除(在程序退出时) + AppDomain.CurrentDomain.ProcessExit += (s, e) => + { + try + { + if (File.Exists(tempPath)) + File.Delete(tempPath); + } + catch + { + // 忽略删除错误 + } + }; + + return assembly; } catch (Exception ex) { @@ -42,6 +109,44 @@ public static Assembly GenerateAssembly(List plugins, } } + /// + /// 卸载程序集加载上下文 + /// + /// 程序集名称 + /// 要卸载的加载上下文 + private static void UnloadAssemblyContext(string assemblyName, CollectibleAssemblyLoadContext context) + { + try + { + Console.WriteLine($"[MethodEmitter] 卸载程序集加载上下文: {assemblyName}"); + + // 从缓存中移除 + _loadContexts.TryRemove(assemblyName, out _); + + // 卸载上下文 + context.Unload(); + + // 等待卸载完成 + GC.Collect(); + GC.WaitForPendingFinalizers(); + } + catch (Exception ex) + { + Console.WriteLine($"[MethodEmitter] 卸载程序集加载上下文失败: {assemblyName}, 错误: {ex.Message}"); + } + } + + /// + /// 清除所有程序集加载上下文 + /// + public static void ClearAllAssemblyContexts() + { + foreach (var kvp in _loadContexts) + { + UnloadAssemblyContext(kvp.Key, kvp.Value); + } + } + /// /// 为单个插件生成静态类 /// diff --git a/KitX Script/Kscript.CSharp.Parser/Core/MockPluginManager.cs b/KitX Script/Kscript.CSharp.Parser/Core/MockPluginManager.cs index 083c602..ecc1710 100644 --- a/KitX Script/Kscript.CSharp.Parser/Core/MockPluginManager.cs +++ b/KitX Script/Kscript.CSharp.Parser/Core/MockPluginManager.cs @@ -16,6 +16,7 @@ public class MockPluginManager : IPluginManager "SampleCalculator", new Dictionary { { "Add", (int a, int b) => a + b }, + { "Multiply", (double x, double y) => x * y }, { "Divide", (double numerator, double denominator, int decimals = 2) => Math.Round(numerator / denominator, decimals) } } }, @@ -23,7 +24,14 @@ public class MockPluginManager : IPluginManager "StringToolkit", new Dictionary { { "Reverse", (string text) => new string(text.Reverse().ToArray()) }, - { "ToUpper", (string text) => text.ToUpperInvariant() } + { "ToUpper", (string text) => text.ToUpperInvariant() }, + { "Concat", (string str1, string str2) => str1 + str2 } + } + }, + { + "KitXWF", new Dictionary + { + { "Print", (string message) => Console.WriteLine(message) } } } }; @@ -108,24 +116,43 @@ public bool IsMethodExists(string pluginName, string methodName) private object? InvokeMockMethod(PluginCallInfo callInfo, object method) { // 根据插件和方法名进行简单的模拟计算 - return (callInfo.PluginName, callInfo.MethodName) switch + switch (callInfo.PluginName) { - ("SampleCalculator", "Add") when callInfo.Parameters.Length >= 2 => - Convert.ToInt32(callInfo.Parameters[0]) + Convert.ToInt32(callInfo.Parameters[1]), - - ("SampleCalculator", "Divide") when callInfo.Parameters.Length >= 2 => - callInfo.Parameters.Length >= 3 - ? Math.Round(Convert.ToDouble(callInfo.Parameters[0]) / Convert.ToDouble(callInfo.Parameters[1]), Convert.ToInt32(callInfo.Parameters[2])) - : Math.Round(Convert.ToDouble(callInfo.Parameters[0]) / Convert.ToDouble(callInfo.Parameters[1]), 2), - - ("StringToolkit", "Reverse") when callInfo.Parameters.Length >= 1 => - new string(callInfo.Parameters[0].ToString()?.Reverse().ToArray() ?? Array.Empty()), - - ("StringToolkit", "ToUpper") when callInfo.Parameters.Length >= 1 => - callInfo.Parameters[0].ToString()?.ToUpperInvariant() ?? string.Empty, + case "SampleCalculator": + switch (callInfo.MethodName) + { + case "Add" when callInfo.Parameters.Length >= 2: + return Convert.ToInt32(callInfo.Parameters[0]) + Convert.ToInt32(callInfo.Parameters[1]); + case "Multiply" when callInfo.Parameters.Length >= 2: + return Convert.ToDouble(callInfo.Parameters[0]) * Convert.ToDouble(callInfo.Parameters[1]); + case "Divide" when callInfo.Parameters.Length >= 2: + return callInfo.Parameters.Length >= 3 + ? Math.Round(Convert.ToDouble(callInfo.Parameters[0]) / Convert.ToDouble(callInfo.Parameters[1]), Convert.ToInt32(callInfo.Parameters[2])) + : Math.Round(Convert.ToDouble(callInfo.Parameters[0]) / Convert.ToDouble(callInfo.Parameters[1]), 2); + } + break; + case "KitXWF": + switch (callInfo.MethodName) + { + case "Print" when callInfo.Parameters.Length >= 1: + Console.WriteLine($"[KitXWF] {callInfo.Parameters[0]?.ToString() ?? ""}"); + return null; + } + break; + case "StringToolkit": + switch (callInfo.MethodName) + { + case "Reverse" when callInfo.Parameters.Length >= 1: + return new string(callInfo.Parameters[0].ToString()?.Reverse().ToArray() ?? Array.Empty()); + case "ToUpper" when callInfo.Parameters.Length >= 1: + return callInfo.Parameters[0].ToString()?.ToUpperInvariant() ?? string.Empty; + case "Concat" when callInfo.Parameters.Length >= 2: + return callInfo.Parameters[0].ToString() + callInfo.Parameters[1].ToString(); + } + break; + } - _ => $"MockResult_{callInfo.MethodName}" - }; + return $"MockResult_{callInfo.MethodName}"; } /// diff --git a/KitX Script/Kscript.CSharp.Parser/Kscript.CSharp.Parser.csproj b/KitX Script/Kscript.CSharp.Parser/Kscript.CSharp.Parser.csproj index 03feb01..fe53ec8 100644 --- a/KitX Script/Kscript.CSharp.Parser/Kscript.CSharp.Parser.csproj +++ b/KitX Script/Kscript.CSharp.Parser/Kscript.CSharp.Parser.csproj @@ -1,13 +1,11 @@  - net8.0 - 12 + net10.0 + preview enable enable Kscript.CSharp.Parser - Exe - Kscript.CSharp.Parser.Examples.Program From 92fa4d1f0ad670cc2036ee92cd9f06089ff2b71f Mon Sep 17 00:00:00 2001 From: StarInk Date: Sat, 8 Nov 2025 10:36:40 +0100 Subject: [PATCH 14/17] =?UTF-8?q?=F0=9F=92=BE=20Feat(Examples):=20?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=AB=AF=E5=88=B0=E7=AB=AF=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E5=92=8C=E8=84=9A=E6=9C=AC=E6=89=A7=E8=A1=8C=E7=A4=BA=E4=BE=8B?= =?UTF-8?q?=EF=BC=8C=E9=AA=8C=E8=AF=81=E6=8F=92=E4=BB=B6=E8=B0=83=E7=94=A8?= =?UTF-8?q?=E9=93=BE=E5=92=8C=E8=84=9A=E6=9C=AC=E6=89=A7=E8=A1=8C=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Examples/EndToEndTest.cs | 392 ++++++++++++++++++ .../Examples/ScriptExecutionExample.cs | 259 ++++++++++++ .../Kscript.CSharp.Compiler.csproj | 22 + .../Kscript.CSharp.Compiler/Program.cs | 44 ++ .../Kscript.CSharp.Compiler/ScriptExecutor.cs | 262 ++++++++++++ 5 files changed, 979 insertions(+) create mode 100644 KitX Script/Kscript.CSharp.Compiler/Examples/EndToEndTest.cs create mode 100644 KitX Script/Kscript.CSharp.Compiler/Examples/ScriptExecutionExample.cs create mode 100644 KitX Script/Kscript.CSharp.Compiler/Kscript.CSharp.Compiler.csproj create mode 100644 KitX Script/Kscript.CSharp.Compiler/Program.cs create mode 100644 KitX Script/Kscript.CSharp.Compiler/ScriptExecutor.cs diff --git a/KitX Script/Kscript.CSharp.Compiler/Examples/EndToEndTest.cs b/KitX Script/Kscript.CSharp.Compiler/Examples/EndToEndTest.cs new file mode 100644 index 0000000..1cc63e8 --- /dev/null +++ b/KitX Script/Kscript.CSharp.Compiler/Examples/EndToEndTest.cs @@ -0,0 +1,392 @@ +using Kscript.CSharp.Parser; +using Kscript.CSharp.Parser.Core; +using Kscript.CSharp.Compiler; +using Kscript.CSharp.Parser.Models; +using KitX.Shared.CSharp.Plugin; +using System.Reflection; + +namespace Kscript.CSharp.Compiler.Examples; + +/// +/// 端到端测试:用户脚本 → Compiler → Parser → PluginManager调用 +/// 验证完整的调用链是否正常工作 +/// +public static class EndToEndTest +{ + /// + /// 运行端到端测试 + /// + public static async Task RunTest() + { + Console.WriteLine("=== KitX.CSharp.Compiler 端到端测试 ===\n"); + + try + { + // 1. 初始化插件管理器 + Console.WriteLine("1. 初始化插件管理器..."); + Parser.Parser.SetPluginManager(new MockPluginManager()); + + // 2. 创建测试插件清单 + Console.WriteLine("2. 创建测试插件清单..."); + var plugins = CreateTestPlugins(); + + // 3. 生成动态程序集 + Console.WriteLine("3. 生成动态程序集..."); + var assembly = Parser.Parser.Generate(plugins, "EndToEndTestAssembly"); + Console.WriteLine($" ✓ 成功生成程序集: {assembly.FullName}"); + + // 4. 验证生成的程序集 + Console.WriteLine("\n4. 验证生成的程序集..."); + await ValidateGeneratedAssembly(assembly); + + // 5. 创建脚本执行器并测试 + Console.WriteLine("\n5. 创建脚本执行器并测试..."); + await TestScriptExecution(assembly); + + // 6. 测试错误处理 + Console.WriteLine("\n6. 测试错误处理..."); + await TestErrorHandling(assembly); + + // 7. 性能测试 + Console.WriteLine("\n7. 性能测试..."); + await PerformanceTest(assembly); + + Console.WriteLine("\n=== 端到端测试完成 ==="); + Console.WriteLine("✅ 所有测试通过,C#脚本可以成功调用生成的插件方法!"); + } + catch (Exception ex) + { + Console.WriteLine($"❌ 端到端测试失败: {ex.Message}"); + Console.WriteLine($"详细错误信息:\n{ex}"); + } + } + + /// + /// 创建测试插件数据 + /// + /// 插件信息列表 + private static List CreateTestPlugins() + { + return new List + { + new PluginInfo + { + Name = "SampleCalculator", + Version = "1.0.0", + Functions = new List + { + new Function + { + Name = "Add", + ReturnValueType = "int", + Parameters = new List + { + new Parameter { Name = "a", Type = "int", IsOptional = false }, + new Parameter { Name = "b", Type = "int", IsOptional = false } + } + }, + new Function + { + Name = "Multiply", + ReturnValueType = "double", + Parameters = new List + { + new Parameter { Name = "x", Type = "double", IsOptional = false }, + new Parameter { Name = "y", Type = "double", IsOptional = false } + } + }, + new Function + { + Name = "Divide", + ReturnValueType = "double", + Parameters = new List + { + new Parameter { Name = "numerator", Type = "double", IsOptional = false }, + new Parameter { Name = "denominator", Type = "double", IsOptional = false }, + new Parameter { Name = "decimals", Type = "int", IsOptional = true, Value = "2" } + } + } + } + }, + new PluginInfo + { + Name = "StringToolkit", + Version = "1.0.0", + Functions = new List + { + new Function + { + Name = "Reverse", + ReturnValueType = "string", + Parameters = new List + { + new Parameter { Name = "text", Type = "string", IsOptional = false } + } + }, + new Function + { + Name = "Concat", + ReturnValueType = "string", + Parameters = new List + { + new Parameter { Name = "str1", Type = "string", IsOptional = false }, + new Parameter { Name = "str2", Type = "string", IsOptional = false } + } + } + } + }, + new PluginInfo + { + Name = "KitXWF", + Version = "1.0.0", + Functions = new List + { + new Function + { + Name = "Print", + ReturnValueType = "void", + Parameters = new List + { + new Parameter { Name = "message", Type = "string", IsOptional = false } + } + } + } + } + }; + } + + /// + /// 验证生成的程序集 + /// + /// 生成的程序集 + private static async Task ValidateGeneratedAssembly(Assembly assembly) + { + Console.WriteLine(" 验证程序集结构..."); + + // 获取所有插件类型 + var types = Parser.Parser.GetPluginTypes(assembly); + Console.WriteLine($" 发现 {types.Count} 个插件类型:"); + + foreach (var type in types) + { + Console.WriteLine($" - {type.Name}"); + + // 获取所有方法 + var methods = Parser.Parser.GetPluginMethods(type); + Console.WriteLine($" 包含 {methods.Count} 个方法:"); + + foreach (var method in methods) + { + var parameters = method.GetParameters(); + var paramList = string.Join(", ", parameters.Select(p => $"{p.ParameterType.Name} {p.Name}")); + Console.WriteLine($" - {method.ReturnType.Name} {method.Name}({paramList})"); + } + } + + Console.WriteLine(" ✓ 程序集结构验证完成"); + } + + /// + /// 测试脚本执行 + /// + /// 生成的程序集 + private static async Task TestScriptExecution(Assembly assembly) + { + using var executor = new ScriptExecutor(); + + // 添加程序集引用 + executor.AddAssemblyReference(assembly); + Console.WriteLine(" ✓ 已添加程序集引用到脚本执行器"); + + // 测试基本计算脚本 + Console.WriteLine("\n 测试基本计算脚本..."); + string basicScript = @" + // 测试加法 + var sum = SampleCalculator.Add(10, 20); + KitXWF.Print(string.Format(""加法结果: {0}"", sum)); + + // 测试乘法 + var product = SampleCalculator.Multiply(3.5, 2.8); + KitXWF.Print(string.Format(""乘法结果: {0}"", product)); + + // 测试字符串反转 + var reversed = StringToolkit.Reverse(""Hello KitX""); + KitXWF.Print(string.Format(""字符串反转结果: {0}"", reversed)); + + return new { Sum = sum, Product = product, Reversed = reversed }; + "; + + var basicResult = await executor.ExecuteAsync(basicScript, "BasicTest"); + Console.WriteLine($" ✓ 基本脚本执行成功: Sum={basicResult.Sum}, Product={basicResult.Product}, Reversed={basicResult.Reversed}"); + + // 测试复杂业务逻辑脚本 + Console.WriteLine("\n 测试复杂业务逻辑脚本..."); + string complexScript = @" + // 模拟订单处理 + var orderItems = new[] + { + new { Name = ""商品A"", Price = 10.5, Quantity = 2 }, + new { Name = ""商品B"", Price = 25.0, Quantity = 1 }, + new { Name = ""商品C"", Price = 5.75, Quantity = 3 } + }; + + var totalAmount = 0.0; + var itemCount = orderItems.Length; + + foreach (var item in orderItems) + { + var itemTotal = SampleCalculator.Multiply(item.Price, (double)item.Quantity); + totalAmount = totalAmount + itemTotal; + KitXWF.Print(string.Format(""处理商品: {0}, 小计: {1}"", item.Name, itemTotal)); + } + + // 计算平均价格 + var averagePrice = SampleCalculator.Divide(totalAmount, (double)itemCount, 2); + + // 生成订单摘要 + var summary = StringToolkit.Concat(string.Format(""订单总额: {0}, 平均价格: {1}"", totalAmount, averagePrice), "" (已处理)""); + + return new { + TotalAmount = totalAmount, + ItemCount = itemCount, + AveragePrice = averagePrice, + Summary = summary + }; + "; + + var complexResult = await executor.ExecuteAsync(complexScript, "ComplexTest"); + Console.WriteLine($" ✓ 复杂脚本执行成功: Total={complexResult.TotalAmount}, Items={complexResult.ItemCount}, Avg={complexResult.AveragePrice}"); + + // 测试脚本验证功能 + Console.WriteLine("\n 测试脚本验证功能..."); + var validScript = "var x = 10; return x * 2;"; + var invalidScript = "var x = 10; return x *; // 语法错误"; + + var validValidation = executor.ValidateScript(validScript); + var invalidValidation = executor.ValidateScript(invalidScript); + + Console.WriteLine($" ✓ 有效脚本验证: {validValidation.IsValid} - {validValidation.Message}"); + Console.WriteLine($" ✓ 无效脚本验证: {!invalidValidation.IsValid} - {invalidValidation.Message}"); + + // 显示执行器状态 + var status = executor.GetStatus(); + Console.WriteLine($" 执行器状态: {status.ReferencedAssembliesCount} 个程序集, {status.GlobalVariablesCount} 个全局变量"); + } + + /// + /// 测试错误处理 + /// + /// 生成的程序集 + private static async Task TestErrorHandling(Assembly assembly) + { + using var executor = new ScriptExecutor(); + executor.AddAssemblyReference(assembly); + + // 测试编译错误 + Console.WriteLine(" 测试编译错误处理..."); + try + { + var syntaxErrorScript = "var x = 10; var y = ; return x + y;"; // 故意语法错误 + await executor.ExecuteAsync(syntaxErrorScript); + Console.WriteLine(" ❌ 应该捕获编译错误但没有"); + } + catch (ScriptExecutionException ex) + { + Console.WriteLine($" ✓ 成功捕获编译错误: {ex.Message}"); + } + + // 测试运行时错误 + Console.WriteLine("\n 测试运行时错误处理..."); + try + { + var runtimeErrorScript = @" + var result = SampleCalculator.Divide(10, 0); // 除零错误 + return result; + "; + + await executor.ExecuteAsync(runtimeErrorScript); + Console.WriteLine(" ❌ 应该捕获运行时错误但没有"); + } + catch (ScriptExecutionException ex) + { + Console.WriteLine($" ✓ 成功捕获运行时错误: {ex.Message}"); + } + + // 测试插件方法不存在错误 + Console.WriteLine("\n 测试插件方法不存在错误..."); + try + { + var methodNotFoundScript = "var result = SampleCalculator.NonExistentMethod(10, 20); return result;"; + await executor.ExecuteAsync(methodNotFoundScript); + Console.WriteLine(" ❌ 应该捕获方法不存在错误但没有"); + } + catch (ScriptExecutionException ex) + { + Console.WriteLine($" ✓ 成功捕获方法不存在错误: {ex.Message}"); + } + } + + /// + /// 性能测试 + /// + /// 生成的程序集 + private static async Task PerformanceTest(Assembly assembly) + { + using var executor = new ScriptExecutor(); + executor.AddAssemblyReference(assembly); + + Console.WriteLine(" 执行性能测试..."); + + var testScript = @" + using System.Collections.Generic; + var results = new List(); + for (int i = 0; i < 10; i++) + { + var sum = SampleCalculator.Add(i, i * 2); + results.Add(sum); + } + return results.Count; + "; + + var stopwatch = System.Diagnostics.Stopwatch.StartNew(); + + // 执行多次取平均值 + const int iterations = 10; + var times = new List(); + + for (int i = 0; i < iterations; i++) + { + stopwatch.Restart(); + await executor.ExecuteAsync(testScript, $"PerfTest_{i}"); + times.Add(stopwatch.ElapsedMilliseconds); + } + + var avgTime = times.Average(); + var minTime = times.Min(); + var maxTime = times.Max(); + + Console.WriteLine($" 性能测试结果 ({iterations} 次执行):"); + Console.WriteLine($" - 平均执行时间: {avgTime:F2} ms"); + Console.WriteLine($" - 最快执行时间: {minTime} ms"); + Console.WriteLine($" - 最慢执行时间: {maxTime} ms"); + Console.WriteLine($" - 性能评级: {GetPerformanceRating(avgTime)}"); + } + + /// + /// 获取性能评级 + /// + /// 平均执行时间 + /// 性能评级 + private static string GetPerformanceRating(double avgTime) + { + return avgTime switch + { + < 10 => "优秀", + < 50 => "良好", + < 100 => "一般", + < 200 => "较差", + _ => "很差" + }; + } +} + diff --git a/KitX Script/Kscript.CSharp.Compiler/Examples/ScriptExecutionExample.cs b/KitX Script/Kscript.CSharp.Compiler/Examples/ScriptExecutionExample.cs new file mode 100644 index 0000000..149319a --- /dev/null +++ b/KitX Script/Kscript.CSharp.Compiler/Examples/ScriptExecutionExample.cs @@ -0,0 +1,259 @@ +using Kscript.CSharp.Parser; +using Kscript.CSharp.Parser.Core; +using Kscript.CSharp.Compiler; +using Kscript.CSharp.Parser.Models; +using System.Text.Json; +using System.Reflection; +using KitX.Shared.CSharp.Plugin; + +namespace Kscript.CSharp.Compiler.Examples; + +/// +/// C#脚本执行示例 +/// +public static class ScriptExecutionExample +{ + /// + /// 运行脚本执行示例 + /// + public static async Task RunExample() + { + Console.WriteLine("=== Kscript.CSharp.Compiler 脚本执行示例 ===\n"); + + try + { + // 1. 初始化插件管理器 + Console.WriteLine("1. 初始化插件管理器..."); + Parser.Parser.SetPluginManager(new MockPluginManager()); + + // 2. 创建示例插件数据 + var plugins = new List + { + new PluginInfo + { + Name = "SampleCalculator", + Version = "1.0.0", + Functions = new List + { + new Function + { + Name = "Add", + ReturnValueType = "int", + Parameters = new List + { + new Parameter { Name = "a", Type = "int", IsOptional = false }, + new Parameter { Name = "b", Type = "int", IsOptional = false } + } + }, + new Function + { + Name = "Multiply", + ReturnValueType = "double", + Parameters = new List + { + new Parameter { Name = "x", Type = "double", IsOptional = false }, + new Parameter { Name = "y", Type = "double", IsOptional = false } + } + } + } + }, + new PluginInfo + { + Name = "StringToolkit", + Version = "1.0.0", + Functions = new List + { + new Function + { + Name = "Reverse", + ReturnValueType = "string", + Parameters = new List + { + new Parameter { Name = "text", Type = "string", IsOptional = false } + } + }, + new Function + { + Name = "ToUpper", + ReturnValueType = "string", + Parameters = new List + { + new Parameter { Name = "text", Type = "string", IsOptional = false } + } + } + } + }, + new PluginInfo + { + Name = "KitXWF", + Version = "1.0.0", + Functions = new List + { + new Function + { + Name = "Print", + ReturnValueType = "void", + Parameters = new List + { + new Parameter { Name = "message", Type = "string", IsOptional = false } + } + } + } + } + }; + + // 3. 生成动态程序集 + Console.WriteLine("2. 生成动态程序集..."); + var assembly = Parser.Parser.Generate(plugins, "ScriptExampleAssembly"); + Console.WriteLine($" ✓ 成功生成程序集: {assembly.FullName}"); + + // 4. 创建脚本执行器 + Console.WriteLine("\n3. 创建脚本执行器..."); + using var executor = new ScriptExecutor(); + + // 5. 添加程序集引用 + executor.AddAssemblyReference(assembly); + Console.WriteLine(" ✓ 已添加程序集引用"); + + // 6. 设置全局变量 + executor.SetGlobalVariable("PI", Math.PI); + executor.SetGlobalVariable("AppName", "KitX Script Engine"); + Console.WriteLine(" ✓ 已设置全局变量"); + + // 7. 执行简单计算脚本 + Console.WriteLine("\n4. 执行简单计算脚本..."); + string simpleScript = @" + var sum = SampleCalculator.Add(15, 25); + var product = SampleCalculator.Multiply(3.5, 2.8); + return new { Sum = sum, Product = product }; + "; + + var simpleResult = await executor.ExecuteAsync(simpleScript); + Console.WriteLine($" 计算结果: Sum = {simpleResult.Sum}, Product = {simpleResult.Product}"); + + // 8. 执行字符串处理脚本 + Console.WriteLine("\n5. 执行字符串处理脚本..."); + string stringScript = @" + var reversed = StringToolkit.Reverse(""Hello World""); + var upper = StringToolkit.ToUpper(""kitx script""); + return new { Original = ""Hello World"", Reversed = reversed, Upper = upper }; + "; + + var stringResult = await executor.ExecuteAsync(stringScript); + Console.WriteLine($" 字符串处理结果:"); + Console.WriteLine($" Original: {stringResult.Original}"); + Console.WriteLine($" Reversed: {stringResult.Reversed}"); + Console.WriteLine($" Upper: {stringResult.Upper}"); + + // 9. 执行复杂业务逻辑脚本 + Console.WriteLine("\n6. 执行复杂业务逻辑脚本..."); + string complexScript = @" + // 模拟一个简单的业务场景:计算订单总价 + var orderItems = new[] + { + new { Name = ""商品A"", Price = 10.5, Quantity = 2 }, + new { Name = ""商品B"", Price = 25.0, Quantity = 1 }, + new { Name = ""商品C"", Price = 5.75, Quantity = 3 } + }; + + var total = 0.0; + foreach (var item in orderItems) + { + var itemTotal = SampleCalculator.Multiply(item.Price, item.Quantity); + total = total + itemTotal; // 直接使用double加法 + KitXWF.Print(string.Format(""商品: {0}, 单价: {1}, 数量: {2}, 小计: {3}"", item.Name, item.Price, item.Quantity, itemTotal)); + } + + // 使用字符串处理生成订单摘要 + var summary = StringToolkit.ToUpper(string.Format(""订单总价: {0}"", total)); + return new { Total = total, Summary = summary, ItemCount = orderItems.Length }; + "; + + var complexResult = await executor.ExecuteAsync(complexScript); + Console.WriteLine($" 复杂业务逻辑结果:"); + Console.WriteLine($" 订单总价: {complexResult.Total}"); + Console.WriteLine($" 订单摘要: {complexResult.Summary}"); + Console.WriteLine($" 商品数量: {complexResult.ItemCount}"); + + // 10. 显示执行器状态 + Console.WriteLine("\n7. 脚本执行器状态:"); + var status = executor.GetStatus(); + Console.WriteLine($" 引用程序集数量: {status.ReferencedAssembliesCount}"); + Console.WriteLine($" 全局变量数量: {status.GlobalVariablesCount}"); + Console.WriteLine($" 命名空间数量: {status.UsingsCount}"); + Console.WriteLine($" 程序集列表: {string.Join("", "", status.AssemblyNames)}"); + + // 11. 脚本验证示例 + Console.WriteLine("\n8. 脚本验证示例:"); + var validScript = "var x = 10; var y = 20; return x + y;"; + var invalidScript = "var x = 10; var y = ; return x + y;"; // 故意的语法错误 + + var validResult = executor.ValidateScript(validScript); + Console.WriteLine($" 有效脚本验证: {validResult.IsValid} - {validResult.Message}"); + + var invalidResult = executor.ValidateScript(invalidScript); + Console.WriteLine($" 无效脚本验证: {invalidResult.IsValid} - {invalidResult.Message}"); + } + catch (Exception ex) + { + Console.WriteLine($"❌ 示例执行失败: {ex.Message}"); + Console.WriteLine($"详细错误信息:\n{ex}"); + } + + Console.WriteLine("\n=== 脚本执行示例完成 ==="); + } + + /// + /// 演示脚本安全性和错误处理 + /// + public static async Task RunSecurityExample() + { + Console.WriteLine("=== 脚本安全性示例 ===\n"); + + using var executor = new ScriptExecutor(); + + // 1. 测试无限循环检测 + Console.WriteLine("1. 测试无限循环检测..."); + string infiniteLoopScript = @" + while (true) + { + // 这会导致无限循环 + } + "; + + try + { + // 设置超时机制(CSharpScript本身不支持,但可以通过其他方式实现) + var task = executor.ExecuteAsync(infiniteLoopScript); + var timeoutTask = Task.Delay(5000); // 5秒超时 + + var completedTask = await Task.WhenAny(task, timeoutTask); + if (completedTask == timeoutTask) + { + Console.WriteLine(" ✓ 检测到潜在的超时/无限循环"); + } + } + catch (Exception ex) + { + Console.WriteLine($" ✓ 安全机制生效: {ex.Message}"); + } + + // 2. 测试恶意代码检测 + Console.WriteLine("\n2. 测试恶意代码检测..."); + string maliciousScript = @" + // 尝试访问文件系统 + System.IO.File.WriteAllText(""malicious.txt"", ""This is malicious""); + return ""File created""; + "; + + try + { + var result = await executor.ExecuteAsync(maliciousScript); + Console.WriteLine(" ⚠️ 警告: 脚本执行成功,可能存在安全风险"); + } + catch (Exception ex) + { + Console.WriteLine($" ✓ 安全机制阻止了恶意操作: {ex.Message}"); + } + } +} diff --git a/KitX Script/Kscript.CSharp.Compiler/Kscript.CSharp.Compiler.csproj b/KitX Script/Kscript.CSharp.Compiler/Kscript.CSharp.Compiler.csproj new file mode 100644 index 0000000..1cadee8 --- /dev/null +++ b/KitX Script/Kscript.CSharp.Compiler/Kscript.CSharp.Compiler.csproj @@ -0,0 +1,22 @@ + + + + net10.0 + preview + enable + enable + Kscript.CSharp.Compiler + Exe + + + + + + + + + + + + + diff --git a/KitX Script/Kscript.CSharp.Compiler/Program.cs b/KitX Script/Kscript.CSharp.Compiler/Program.cs new file mode 100644 index 0000000..75319a1 --- /dev/null +++ b/KitX Script/Kscript.CSharp.Compiler/Program.cs @@ -0,0 +1,44 @@ +using Kscript.CSharp.Compiler.Examples; + +namespace Kscript.CSharp.Compiler; + +/// +/// Kscript.CSharp.Compiler 主程序入口 +/// +public static class Program +{ + /// + /// 主入口方法 + /// + /// 命令行参数 + public static async Task Main(string[] args) + { + Console.OutputEncoding = System.Text.Encoding.UTF8; + Console.WriteLine("🚀 欢迎使用 KitX.CSharp.Compiler!"); + Console.WriteLine("==================================================\n"); + + try + { + // 运行脚本执行示例 + await ScriptExecutionExample.RunExample(); + + Console.WriteLine("\n" + new string('=', 60)); + + // 运行端到端测试 + await EndToEndTest.RunTest(); + + Console.WriteLine("\n=================================================="); + Console.WriteLine("✅ 所有示例运行完成!"); + Console.WriteLine("\n按任意键退出..."); + Console.ReadKey(); + } + catch (Exception ex) + { + Console.WriteLine($"\n❌ 程序运行失败: {ex.Message}"); + Console.WriteLine($"详细错误信息:\n{ex}"); + + Console.WriteLine("\n按任意键退出..."); + Console.ReadKey(); + } + } +} diff --git a/KitX Script/Kscript.CSharp.Compiler/ScriptExecutor.cs b/KitX Script/Kscript.CSharp.Compiler/ScriptExecutor.cs new file mode 100644 index 0000000..6ec1c98 --- /dev/null +++ b/KitX Script/Kscript.CSharp.Compiler/ScriptExecutor.cs @@ -0,0 +1,262 @@ +using Microsoft.CodeAnalysis.CSharp.Scripting; +using Microsoft.CodeAnalysis.Scripting; +using System.Reflection; +using System.IO; +using System.Reflection.Emit; +using Kscript.CSharp.Parser.Core; +using Kscript.CSharp.Parser.Models; + +namespace Kscript.CSharp.Compiler; + +/// +/// C#脚本执行器,用于执行用户编写的C#脚本并调用生成的插件方法 +/// +public class ScriptExecutor : IDisposable +{ + private readonly Dictionary _globalVariables = new(); + private readonly List _referencedAssemblies = new(); + private readonly List _usings = new(); + private ScriptOptions? _scriptOptions; + private bool _disposed = false; + + /// + /// 构造函数 + /// + public ScriptExecutor() + { + _scriptOptions = ScriptOptions.Default + .WithImports("System", "System.Math", "System.Collections.Generic", "System.Console", "System.Linq") + .WithEmitDebugInformation(true); + } + + /// + /// 添加程序集引用 + /// + /// 要引用的程序集 + public void AddAssemblyReference(Assembly assembly) + { + if (assembly == null) + throw new ArgumentNullException(nameof(assembly)); + + if (!_referencedAssemblies.Contains(assembly)) + { + _referencedAssemblies.Add(assembly); + Console.WriteLine($"[ScriptExecutor] 已添加程序集引用: {assembly.FullName}"); + } + } + + /// + /// 添加命名空间引用 + /// + /// 命名空间 + public void AddUsing(string @namespace) + { + if (string.IsNullOrEmpty(@namespace)) + throw new ArgumentException("命名空间不能为空", nameof(@namespace)); + + if (!_usings.Contains(@namespace)) + { + _usings.Add(@namespace); + Console.WriteLine($"[ScriptExecutor] 已添加命名空间引用: {@namespace}"); + } + } + + /// + /// 设置全局变量 + /// + /// 变量名 + /// 变量值 + public void SetGlobalVariable(string name, object value) + { + if (string.IsNullOrEmpty(name)) + throw new ArgumentException("变量名不能为空", nameof(name)); + + _globalVariables[name] = value; + Console.WriteLine($"[ScriptExecutor] 已设置全局变量: {name} = {value}"); + } + + /// + /// 执行C#脚本 + /// + /// 返回值类型 + /// 脚本代码 + /// 程序集名称(用于调试) + /// 执行结果 + public async Task ExecuteAsync(string script, string assemblyName = "ScriptAssembly") + { + if (string.IsNullOrEmpty(script)) + throw new ArgumentException("脚本代码不能为空", nameof(script)); + + try + { + Console.WriteLine($"[ScriptExecutor] 开始执行脚本: {assemblyName}"); + Console.WriteLine($"[ScriptExecutor] 脚本代码: {script.Substring(0, Math.Min(script.Length, 100))}..."); + + // 更新脚本选项,包含所有引用 + var options = ScriptOptions.Default + .WithReferences(_referencedAssemblies) + .WithImports(_usings.Concat(new[] { "System.Console" })) + .WithEmitDebugInformation(true); + + // 创建脚本对象 + var csharpScript = CSharpScript.Create(script, options); + + // 执行脚本(不使用全局变量) + var result = await csharpScript.RunAsync(null); + + Console.WriteLine($"[ScriptExecutor] 脚本执行成功,结果: {result.ReturnValue}"); + return result.ReturnValue; + } + catch (CompilationErrorException ex) + { + Console.WriteLine($"[ScriptExecutor] 脚本编译错误: {ex.Message}"); + throw new ScriptExecutionException($"脚本编译失败: {ex.Message}", ex); + } + catch (Exception ex) + { + Console.WriteLine($"[ScriptExecutor] 脚本执行异常: {ex.Message}"); + throw new ScriptExecutionException($"脚本执行失败: {ex.Message}", ex); + } + } + + /// + /// 执行脚本(无返回值) + /// + /// 脚本代码 + /// 程序集名称 + public async Task ExecuteAsync(string script, string assemblyName = "ScriptAssembly") + { + await ExecuteAsync(script, assemblyName); + } + + /// + /// 验证脚本语法 + /// + /// 脚本代码 + /// 验证结果 + public ScriptValidationResult ValidateScript(string script) + { + if (string.IsNullOrEmpty(script)) + return new ScriptValidationResult(false, "脚本代码不能为空"); + + try + { + // 更新脚本选项,包含所有引用 + var options = ScriptOptions.Default + .WithReferences(_referencedAssemblies) + .WithImports(_usings.Concat(new[] { "System.Console" })) + .WithEmitDebugInformation(true); + + var csharpScript = CSharpScript.Create(script, options); + + // 尝试编译以检查语法 + var diagnostics = csharpScript.Compile(); + + if (!diagnostics.Any()) + { + return new ScriptValidationResult(true, "脚本语法正确"); + } + else + { + var errors = string.Join("\n", diagnostics.Select(d => d.ToString())); + return new ScriptValidationResult(false, $"脚本语法错误:\n{errors}"); + } + } + catch (Exception ex) + { + return new ScriptValidationResult(false, $"脚本验证异常: {ex.Message}"); + } + } + + /// + /// 获取当前状态信息 + /// + /// 状态信息 + public ScriptExecutorStatus GetStatus() + { + return new ScriptExecutorStatus + { + ReferencedAssembliesCount = _referencedAssemblies.Count, + GlobalVariablesCount = _globalVariables.Count, + UsingsCount = _usings.Count, + AssemblyNames = _referencedAssemblies.Select(a => a.GetName().Name).ToList() + }; + } + + /// + /// 清除所有引用和变量 + /// + public void Clear() + { + _referencedAssemblies.Clear(); + _globalVariables.Clear(); + _usings.Clear(); + Console.WriteLine("[ScriptExecutor] 已清除所有引用和变量"); + } + + /// + /// 释放资源 + /// + public void Dispose() + { + if (!_disposed) + { + Clear(); + _disposed = true; + Console.WriteLine("[ScriptExecutor] 资源已释放"); + } + } +} + +/// +/// 脚本全局变量容器 +/// +public class ScriptGlobals +{ + private readonly Dictionary _variables; + + public ScriptGlobals(Dictionary variables) + { + _variables = variables ?? new Dictionary(); + } + + /// + /// 动态属性访问器 + /// + public object this[string name] + { + get + { + if (_variables.TryGetValue(name, out var value)) + return value; + throw new KeyNotFoundException($"未找到全局变量: {name}"); + } + } +} + +/// +/// 脚本验证结果 +/// +public record ScriptValidationResult(bool IsValid, string Message); + +/// +/// 脚本执行器状态 +/// +public record ScriptExecutorStatus +{ + public int ReferencedAssembliesCount { get; init; } + public int GlobalVariablesCount { get; init; } + public int UsingsCount { get; init; } + public List AssemblyNames { get; init; } = new(); +} + +/// +/// 脚本执行异常 +/// +public class ScriptExecutionException : Exception +{ + public ScriptExecutionException(string message, Exception? innerException = null) + : base(message, innerException) + { + } +} From e80de9d7837165b2b00dd23bba9307fba979da75 Mon Sep 17 00:00:00 2001 From: StarInk Date: Sat, 8 Nov 2025 10:37:40 +0100 Subject: [PATCH 15/17] =?UTF-8?q?=F0=9F=94=A7=20Fix(Examples):=20=E7=A7=BB?= =?UTF-8?q?=E9=99=A4=E5=9F=BA=E7=A1=80=E7=94=A8=E6=B3=95=E7=A4=BA=E4=BE=8B?= =?UTF-8?q?=E5=92=8C=E5=AE=9E=E9=99=85=E6=8F=92=E4=BB=B6=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E5=99=A8=E7=A4=BA=E4=BE=8B=EF=BC=8C=E5=A4=8D=E5=88=B6=E7=A4=BA?= =?UTF-8?q?=E4=BE=8B=E6=95=B0=E6=8D=AE=E8=87=B3=E6=96=B0=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../example.json | 154 ++++++++++ .../Examples/BasicUsageExample.cs | 263 ------------------ .../Kscript.CSharp.Parser/Examples/Program.cs | 164 ----------- .../Examples/RealPluginManagerExample.cs | 129 --------- 4 files changed, 154 insertions(+), 556 deletions(-) create mode 100644 KitX Script/Kscript.CSharp.Parser.Examples/example.json delete mode 100644 KitX Script/Kscript.CSharp.Parser/Examples/BasicUsageExample.cs delete mode 100644 KitX Script/Kscript.CSharp.Parser/Examples/Program.cs delete mode 100644 KitX Script/Kscript.CSharp.Parser/Examples/RealPluginManagerExample.cs diff --git a/KitX Script/Kscript.CSharp.Parser.Examples/example.json b/KitX Script/Kscript.CSharp.Parser.Examples/example.json new file mode 100644 index 0000000..60ed38c --- /dev/null +++ b/KitX Script/Kscript.CSharp.Parser.Examples/example.json @@ -0,0 +1,154 @@ +[ + { + "Name": "SampleCalculator", + "Version": "1.0.0", + "DisplayName": { + "en": "Calculator", + "zh": "计算器" + }, + "AuthorName": "KitX Devs", + "AuthorLink": "https://github.com/Crequency/KitX", + "PublisherName": "Crequency", + "PublisherLink": "https://kitx.cc", + "SimpleDescription": { + "en": "A simple calculator plugin.", + "zh": "简易计算器插件" + }, + "ComplexDescription": {}, + "TotalDescriptionInMarkdown": {}, + "IconInBase64": "", + "PublishDate": "2025-07-29T00:00:00Z", + "LastUpdateDate": "2025-07-29T00:00:00Z", + "IsMarketVersion": true, + "Tags": { + "en": "math,utility", + "zh": "数学,工具" + }, + "Functions": [ + { + "Name": "Add", + "DisplayNames": { + "en": "Add two integers", + "zh": "整数相加" + }, + "Parameters": [ + { + "Name": "a", + "DisplayNames": { + "en": "First number", + "zh": "第一个数" + }, + "Type": "int", + "Value": "0", + "IsOptional": false + }, + { + "Name": "b", + "DisplayNames": { + "en": "Second number", + "zh": "第二个数" + }, + "Type": "int", + "Value": "0", + "IsOptional": false + } + ], + "ReturnValue": "Sum of a and b", + "ReturnValueType": "int" + }, + { + "Name": "Divide", + "DisplayNames": { + "en": "Divide with optional precision", + "zh": "带可选精度的除法" + }, + "Parameters": [ + { + "Name": "numerator", + "Type": "double", + "Value": "0", + "IsOptional": false + }, + { + "Name": "denominator", + "Type": "double", + "Value": "1", + "IsOptional": false + }, + { + "Name": "decimals", + "Type": "int", + "Value": "2", + "IsOptional": true + } + ], + "ReturnValue": "Rounded quotient", + "ReturnValueType": "double" + } + ], + "RootStartupFileName": "SampleCalculator.dll" + }, + { + "Name": "StringToolkit", + "Version": "1.2.0", + "DisplayName": { + "en": "String Toolkit", + "zh": "字符串工具箱" + }, + "AuthorName": "Community", + "AuthorLink": "https://example.com", + "PublisherName": "KitX", + "PublisherLink": "https://kitx.cc", + "SimpleDescription": { + "en": "Utility functions for string manipulation.", + "zh": "字符串处理工具集" + }, + "ComplexDescription": {}, + "TotalDescriptionInMarkdown": {}, + "IconInBase64": "", + "PublishDate": "2025-07-29T00:00:00Z", + "LastUpdateDate": "2025-07-29T00:00:00Z", + "IsMarketVersion": false, + "Tags": { + "en": "string,utility,text", + "zh": "字符串,工具,文本" + }, + "Functions": [ + { + "Name": "Reverse", + "DisplayNames": { + "en": "Reverse string", + "zh": "反转字符串" + }, + "Parameters": [ + { + "Name": "text", + "Type": "string", + "Value": "", + "IsOptional": false + } + ], + "ReturnValue": "Reversed text", + "ReturnValueType": "string" + }, + { + "Name": "ToUpper", + "DisplayNames": { + "en": "Convert to upper case", + "zh": "转大写" + }, + "Parameters": [ + { + "Name": "text", + "Type": "string", + "Value": "", + "IsOptional": false + } + ], + "ReturnValue": "Uppercase text", + "ReturnValueType": "string" + } + ], + "RootStartupFileName": "StringToolkit.dll" + } + ] diff --git a/KitX Script/Kscript.CSharp.Parser/Examples/BasicUsageExample.cs b/KitX Script/Kscript.CSharp.Parser/Examples/BasicUsageExample.cs deleted file mode 100644 index 3a35e74..0000000 --- a/KitX Script/Kscript.CSharp.Parser/Examples/BasicUsageExample.cs +++ /dev/null @@ -1,263 +0,0 @@ -using System.Text.Json; -using Kscript.CSharp.Parser; -using Kscript.CSharp.Parser.Core; - -namespace Kscript.CSharp.Parser.Examples; - -/// -/// 基础用法示例 -/// -public static class BasicUsageExample -{ - /// - /// 演示基本功能 - /// - public static async Task RunExample() - { - Console.WriteLine("=== KitX.CSharp.Parser 基础用法示例 ===\n"); - - try - { - // 0. 初始化插件管理器 - Console.WriteLine("0. 初始化插件管理器..."); - if (!Parser.IsInitialized) - { - Parser.SetPluginManager(new MockPluginManager()); - Console.WriteLine(" ✓ 已设置 MockPluginManager"); - } - - // 1. 从现有的 example.json 文件加载插件清单 - Console.WriteLine("1. 加载插件清单..."); - var exampleJsonPath = Path.Combine(".", "example.json"); - if (!File.Exists(exampleJsonPath)) - { - Console.WriteLine($" 警告: example.json 文件不存在于 {exampleJsonPath}"); - Console.WriteLine(" 将使用内置示例数据"); - RunWithBuiltInData(); - return; - } - - var assembly = await Parser.GenerateFromFileAsync(exampleJsonPath, "ExamplePluginAssembly"); - Console.WriteLine($" ✓ 成功生成程序集: {assembly.FullName}"); - - // 2. 获取生成的插件类型 - Console.WriteLine("\n2. 分析生成的插件类型..."); - var pluginTypes = Parser.GetPluginTypes(assembly); - Console.WriteLine($" 发现 {pluginTypes.Count} 个插件类:"); - - foreach (var type in pluginTypes) - { - Console.WriteLine($" - {type.Name}"); - var methods = Parser.GetPluginMethods(type); - foreach (var method in methods) - { - var parameters = string.Join(", ", method.GetParameters().Select(p => $"{p.ParameterType.Name} {p.Name}")); - Console.WriteLine($" └── {method.ReturnType.Name} {method.Name}({parameters})"); - } - } - - // 3. 动态调用插件方法 - Console.WriteLine("\n3. 动态调用插件方法..."); - DynamicInvokeExample(assembly); - - // 4. 缓存统计 - Console.WriteLine("\n4. 缓存统计信息..."); - var stats = Parser.GetCacheStatistics(); - Console.WriteLine($" {stats}"); - - // 5. 演示缓存效果 - Console.WriteLine("\n5. 测试缓存效果..."); - - // 5.1 先检查是否存在缓存 - var plugins = JsonSerializer.Deserialize>(await File.ReadAllTextAsync(exampleJsonPath), new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true, - AllowTrailingCommas = true - }); - var hasCacheBefore = Parser.HasCache(plugins!, "ExamplePluginAssembly"); - Console.WriteLine($" 生成前检查缓存存在: {hasCacheBefore}"); - - // 5.2 第二次生成(应该使用缓存) - var assembly2 = await Parser.GenerateFromFileAsync(exampleJsonPath, "ExamplePluginAssembly"); - Console.WriteLine($" 第二次生成是否使用缓存: {assembly == assembly2}"); - - // 5.3 强制重新生成(应该绕过缓存) - var assembly3 = Parser.Generate(plugins!, "ExamplePluginAssembly", useCache: false); - Console.WriteLine($" 强制重新生成是否使用新程序集: {assembly3 != assembly2}"); - - // 5.4 检查强制重新生成后的缓存状态 - var hasCacheAfterRegenerate = Parser.HasCache(plugins!, "ExamplePluginAssembly"); - Console.WriteLine($" 重新生成后缓存存在: {hasCacheAfterRegenerate}"); - - } - catch (Exception ex) - { - Console.WriteLine($"❌ 示例执行失败: {ex.Message}"); - Console.WriteLine($"详细信息: {ex}"); - } - } - - /// - /// 使用内置数据运行示例 - /// - private static void RunWithBuiltInData() - { - // 确保插件管理器已初始化 - if (!Parser.IsInitialized) - { - Parser.SetPluginManager(new MockPluginManager()); - } - - // 创建示例插件数据 - var plugins = new List - { - new PluginInfo - { - Name = "TestCalculator", - Version = "1.0.0", - Functions = new List - { - new Function - { - Name = "Add", - ReturnValueType = "int", - Parameters = new List - { - new Parameter { Name = "a", Type = "int", IsOptional = false }, - new Parameter { Name = "b", Type = "int", IsOptional = false } - } - }, - new Function - { - Name = "Multiply", - ReturnValueType = "double", - Parameters = new List - { - new Parameter { Name = "x", Type = "double", IsOptional = false }, - new Parameter { Name = "y", Type = "double", IsOptional = false } - } - } - } - } - }; - - Console.WriteLine(" 使用内置示例数据..."); - var assembly = Parser.Generate(plugins, "BuiltInExampleAssembly"); - Console.WriteLine($" ✓ 成功生成程序集: {assembly.FullName}"); - - DynamicInvokeExample(assembly); - } - - /// - /// 动态调用示例 - /// - private static void DynamicInvokeExample(Assembly assembly) - { - var pluginTypes = Parser.GetPluginTypes(assembly); - - foreach (var type in pluginTypes) - { - Console.WriteLine($"\n 调用插件: {type.Name}"); - var methods = Parser.GetPluginMethods(type); - - foreach (var method in methods) - { - try - { - object? result = null; - var parameters = method.GetParameters(); - - // 根据方法名和参数生成测试数据 - object[] args = GenerateTestArguments(method.Name, parameters); - - Console.WriteLine($" 调用 {method.Name}({string.Join(", ", args)})"); - - // 动态调用方法 - result = method.Invoke(null, args); - - Console.WriteLine($" → 结果: {result}"); - } - catch (Exception ex) - { - Console.WriteLine($" → 调用失败: {ex.Message}"); - } - } - } - } - - /// - /// 生成测试参数 - /// - private static object[] GenerateTestArguments(string methodName, ParameterInfo[] parameters) - { - var args = new object[parameters.Length]; - - for (int i = 0; i < parameters.Length; i++) - { - var param = parameters[i]; - - // 根据参数类型和方法名生成合适的测试值 - args[i] = param.ParameterType.Name switch - { - "Int32" => methodName switch - { - "Add" => i == 0 ? 10 : 20, - "Divide" when param.Name == "decimals" => 2, - _ => i + 1 - }, - "Double" => methodName switch - { - "Divide" => i == 0 ? 10.0 : 3.0, - "Multiply" => i == 0 ? 2.5 : 4.0, - _ => (i + 1) * 1.5 - }, - "String" => methodName switch - { - "Reverse" => "Hello", - "ToUpper" => "world", - _ => $"test{i}" - }, - _ => GetDefaultValue(param.ParameterType) - }; - } - - return args; - } - - /// - /// 获取类型默认值 - /// - private static object GetDefaultValue(Type type) - { - if (type.IsValueType) - return Activator.CreateInstance(type)!; - - return type == typeof(string) ? "default" : null!; - } - - /// - /// 演示高级功能 - /// - public static void RunAdvancedExample() - { - Console.WriteLine("\n=== 高级功能演示 ===\n"); - - try - { - // 1. 清除缓存 - Console.WriteLine("1. 清除所有缓存..."); - Parser.ClearCache(); - Console.WriteLine(" ✓ 缓存已清除"); - - // 2. 重新设置插件管理器 - Console.WriteLine("\n2. 重新设置插件管理器..."); - Parser.SetPluginManager(new MockPluginManager()); - Console.WriteLine(" ✓ 已重新设置插件管理器"); - - } - catch (Exception ex) - { - Console.WriteLine($"❌ 高级功能演示失败: {ex.Message}"); - } - } -} diff --git a/KitX Script/Kscript.CSharp.Parser/Examples/Program.cs b/KitX Script/Kscript.CSharp.Parser/Examples/Program.cs deleted file mode 100644 index 142351e..0000000 --- a/KitX Script/Kscript.CSharp.Parser/Examples/Program.cs +++ /dev/null @@ -1,164 +0,0 @@ -using Kscript.CSharp.Parser.Examples; -using Kscript.CSharp.Parser.Core; - -namespace Kscript.CSharp.Parser.Examples; - -/// -/// 演示程序入口 -/// -public static class Program -{ - /// - /// 主入口方法 - /// - public static async Task Main(string[] args) - { - Console.OutputEncoding = System.Text.Encoding.UTF8; - Console.WriteLine("🚀 欢迎使用 KitX.CSharp.Parser 演示程序!"); - Console.WriteLine("==================================================\n"); - - try - { - // 首先运行简化后的功能测试 - Console.WriteLine("=== 测试简化后的 Parser 功能 ==="); - TestSimplifiedParser(); - - Console.WriteLine("\n" + new string('=', 60)); - - // 运行基础用法示例 - await BasicUsageExample.RunExample(); - - // 运行高级功能示例 - BasicUsageExample.RunAdvancedExample(); - - // 运行实际插件管理器示例 - Console.WriteLine("\n" + new string('=', 60)); - RealPluginManagerExample.RunExample(); - - Console.WriteLine("\n" + new string('=', 60)); - RealPluginManagerExample.DashboardIntegrationExample(); - - Console.WriteLine("\n=================================================="); - Console.WriteLine("✅ 所有示例运行完成!"); - } - catch (Exception ex) - { - Console.WriteLine($"\n❌ 程序运行失败: {ex.Message}"); - Console.WriteLine($"详细错误信息:\n{ex}"); - } - - Console.WriteLine("\n按任意键退出..."); - Console.ReadKey(); - } - - /// - /// 测试简化后的 Parser 功能 - /// - private static void TestSimplifiedParser() - { - Console.WriteLine("1. 测试默认插件管理器设置..."); - - // 测试使用 MockPluginManager 中已有的插件 - var testPlugins = new List - { - new PluginInfo - { - Name = "SampleCalculator", - Version = "1.0.0", - Functions = new List - { - new Function - { - Name = "Add", - ReturnValueType = "int", - Parameters = new List - { - new Parameter { Name = "a", Type = "int", IsOptional = false }, - new Parameter { Name = "b", Type = "int", IsOptional = false } - } - } - } - } - }; - - try - { - // 先设置插件管理器 - Parser.SetPluginManager(new MockPluginManager()); - - // 使用 MockPluginManager 生成程序集 - var assembly = Parser.Generate(testPlugins, "TestAssembly"); - Console.WriteLine(" ✓ 默认 MockPluginManager 工作正常"); - - // 获取生成的类型 - var types = Parser.GetPluginTypes(assembly); - Console.WriteLine($" ✓ 生成了 {types.Count} 个插件类型"); - - foreach (var type in types) - { - var methods = Parser.GetPluginMethods(type); - Console.WriteLine($" - {type.Name}: {methods.Count} 个方法"); - } - } - catch (Exception ex) - { - Console.WriteLine($" ❌ 测试失败: {ex.Message}"); - } - - Console.WriteLine("2. 测试自定义插件管理器设置..."); - try - { - // 设置新的插件管理器 - Parser.SetPluginManager(new MockPluginManager()); - var assembly2 = Parser.Generate(testPlugins, "TestAssembly2"); - Console.WriteLine(" ✓ 自定义插件管理器设置工作正常"); - } - catch (Exception ex) - { - Console.WriteLine($" ❌ 自定义设置测试失败: {ex.Message}"); - } - - Console.WriteLine("3. 测试空参数处理..."); - try - { - // 测试 null 参数应该抛出异常 - Parser.SetPluginManager(null); - Console.WriteLine(" ❌ 空参数测试失败: 应该抛出异常但没有"); - } - catch (ArgumentNullException) - { - Console.WriteLine(" ✓ 空参数正确抛出 ArgumentNullException"); - } - catch (Exception ex) - { - Console.WriteLine($" ❌ 空参数测试失败: {ex.Message}"); - } - - Console.WriteLine("4. 测试插件存在性检测..."); - try - { - var mockManager = new MockPluginManager(); - Parser.SetPluginManager(mockManager); - - // 测试存在的插件 - var existsSampleCalculator = mockManager.IsPluginExists("SampleCalculator"); - Console.WriteLine($" ✓ SampleCalculator 存在: {existsSampleCalculator}"); - - var existsAddMethod = mockManager.IsMethodExists("SampleCalculator", "Add"); - Console.WriteLine($" ✓ SampleCalculator.Add 方法存在: {existsAddMethod}"); - - // 测试不存在的插件 - var existsNonExistent = mockManager.IsPluginExists("NonExistentPlugin"); - Console.WriteLine($" ✓ NonExistentPlugin 存在: {existsNonExistent}"); - - var existsNonExistentMethod = mockManager.IsMethodExists("SampleCalculator", "NonExistentMethod"); - Console.WriteLine($" ✓ SampleCalculator.NonExistentMethod 方法存在: {existsNonExistentMethod}"); - - Console.WriteLine(" ✓ 插件存在性检测功能工作正常"); - } - catch (Exception ex) - { - Console.WriteLine($" ❌ 插件存在性检测测试失败: {ex.Message}"); - } - } -} diff --git a/KitX Script/Kscript.CSharp.Parser/Examples/RealPluginManagerExample.cs b/KitX Script/Kscript.CSharp.Parser/Examples/RealPluginManagerExample.cs deleted file mode 100644 index 937a3ad..0000000 --- a/KitX Script/Kscript.CSharp.Parser/Examples/RealPluginManagerExample.cs +++ /dev/null @@ -1,129 +0,0 @@ -using Kscript.CSharp.Parser; -using Kscript.CSharp.Parser.Core; -using System.Text.Json; - -namespace Kscript.CSharp.Parser.Examples; - -/// -/// 实际插件管理器使用示例 -/// -public static class RealPluginManagerExample -{ - /// - /// 演示如何使用 RealPluginManager - /// - public static void RunExample() - { - Console.WriteLine("=== KitX.CSharp.Parser 实际插件管理器使用示例 ===\n"); - - try - { - // 1. 设置插件管理器 - Console.WriteLine("1. 设置插件管理器..."); - - // 这里应该传入实际的 PluginsServer 实例和日志记录器 - // 由于这是一个示例,我们使用 null 作为占位符 - // 在实际使用中,这里应该传入真实的 PluginsServer 实例和日志记录器 - // 例如: - // var pluginsServer = KitX.Dashboard.Network.PluginsNetwork.PluginsServer.Instance; - // var logger = Serilog.Log.Logger; - // Parser.SetPluginManager(new RealPluginManager(pluginsServer, message => logger.Information(message))); - - Console.WriteLine(" 使用 MockPluginManager 进行演示..."); - Parser.SetPluginManager(new MockPluginManager()); - - Console.WriteLine(" ✓ 插件管理器已设置"); - - // 2. 创建示例插件数据 - Console.WriteLine("\n2. 创建示例插件数据..."); - var examplePlugins = new List - { - new PluginInfo - { - Name = "SampleCalculator", - Version = "1.0.0", - Functions = new List - { - new Function - { - Name = "Add", - ReturnValueType = "int", - Parameters = new List - { - new Parameter { Name = "a", Type = "int", IsOptional = false }, - new Parameter { Name = "b", Type = "int", IsOptional = false } - } - } - } - } - }; - - Console.WriteLine(" ✓ 示例插件数据已创建"); - - // 3. 生成动态程序集 - Console.WriteLine("\n3. 生成动态程序集..."); - var assembly = Parser.Generate(examplePlugins, "RealPluginManagerExampleAssembly"); - Console.WriteLine($" ✓ 成功生成程序集: {assembly.FullName}"); - - // 4. 获取生成的插件类型 - Console.WriteLine("\n4. 分析生成的插件类型..."); - var pluginTypes = Parser.GetPluginTypes(assembly); - Console.WriteLine($" 发现 {pluginTypes.Count} 个插件类:"); - - foreach (var type in pluginTypes) - { - Console.WriteLine($" - {type.Name}"); - var methods = Parser.GetPluginMethods(type); - foreach (var method in methods) - { - var parameters = string.Join(", ", method.GetParameters().Select(p => $"{p.ParameterType.Name} {p.Name}")); - Console.WriteLine($" └── {method.ReturnType.Name} {method.Name}({parameters})"); - } - } - - Console.WriteLine("\n=== 示例完成 ==="); - Console.WriteLine("\n注意:在实际使用中,您需要:"); - Console.WriteLine("1. 确保 KitX Dashboard 正在运行"); - Console.WriteLine("2. 传入真实的 PluginsServer.Instance"); - Console.WriteLine("3. 传入真实的日志记录器实例"); - Console.WriteLine("4. 确保插件已正确连接到 Dashboard"); - } - catch (Exception ex) - { - Console.WriteLine($"❌ 示例执行失败: {ex.Message}"); - Console.WriteLine($"详细信息: {ex}"); - } - } - - /// - /// 演示如何在 KitX Dashboard 中集成 - /// - public static void DashboardIntegrationExample() - { - Console.WriteLine("=== KitX Dashboard 集成示例 ===\n"); - - Console.WriteLine("在 KitX Dashboard 中使用 RealPluginManager 的步骤:\n"); - - Console.WriteLine("1. 在 Dashboard 启动时设置插件管理器工厂:"); - Console.WriteLine("```csharp"); - Console.WriteLine("// 在 Dashboard 的初始化代码中"); - Console.WriteLine("Parser.SetPluginManagerFactory(() => new RealPluginManager("); - Console.WriteLine(" KitX.Dashboard.Network.PluginsNetwork.PluginsServer.Instance,"); - Console.WriteLine(" message => Log.Information(message)"); - Console.WriteLine("));"); - Console.WriteLine("```"); - - Console.WriteLine("\n2. 生成插件程序集:"); - Console.WriteLine("```csharp"); - Console.WriteLine("var assembly = Parser.Generate(plugins, \"DashboardPluginAssembly\");"); - Console.WriteLine("```"); - - Console.WriteLine("\n3. 在脚本中使用生成的 API:"); - Console.WriteLine("```csharp"); - Console.WriteLine("// 现在可以在脚本中直接调用"); - Console.WriteLine("var result = SampleCalculator.Add(10, 20);"); - Console.WriteLine("```"); - - Console.WriteLine("\n这样,脚本中的插件调用将通过真实的 KitX Dashboard 插件系统执行!"); - } -} From ea85a5a7fd432e62d6c08b99b8948d4ede95ba69 Mon Sep 17 00:00:00 2001 From: StarInk Date: Sat, 8 Nov 2025 11:34:45 +0100 Subject: [PATCH 16/17] =?UTF-8?q?=F0=9F=94=A7=20Fix(ScriptExecutor):=20?= =?UTF-8?q?=E7=A7=BB=E9=99=A4=E4=B8=8D=E5=BF=85=E8=A6=81=E7=9A=84=E5=91=BD?= =?UTF-8?q?=E5=90=8D=E7=A9=BA=E9=97=B4=E5=BC=95=E7=94=A8=E2=80=9CSystem.Co?= =?UTF-8?q?nsole=E2=80=9D=E4=BB=A5=E7=AE=80=E5=8C=96=E8=84=9A=E6=9C=AC?= =?UTF-8?q?=E9=80=89=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- KitX Script/Kscript.CSharp.Compiler/ScriptExecutor.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/KitX Script/Kscript.CSharp.Compiler/ScriptExecutor.cs b/KitX Script/Kscript.CSharp.Compiler/ScriptExecutor.cs index 6ec1c98..e648cf9 100644 --- a/KitX Script/Kscript.CSharp.Compiler/ScriptExecutor.cs +++ b/KitX Script/Kscript.CSharp.Compiler/ScriptExecutor.cs @@ -25,7 +25,7 @@ public class ScriptExecutor : IDisposable public ScriptExecutor() { _scriptOptions = ScriptOptions.Default - .WithImports("System", "System.Math", "System.Collections.Generic", "System.Console", "System.Linq") + .WithImports("System", "System.Math", "System.Collections.Generic", "System.Linq") .WithEmitDebugInformation(true); } @@ -144,7 +144,6 @@ public ScriptValidationResult ValidateScript(string script) // 更新脚本选项,包含所有引用 var options = ScriptOptions.Default .WithReferences(_referencedAssemblies) - .WithImports(_usings.Concat(new[] { "System.Console" })) .WithEmitDebugInformation(true); var csharpScript = CSharpScript.Create(script, options); From 3512cc53aef7af2f90d95aa7f1b3936b300c82ba Mon Sep 17 00:00:00 2001 From: StarInk Date: Sat, 14 Feb 2026 00:30:07 +0100 Subject: [PATCH 17/17] =?UTF-8?q?=F0=9F=92=BE=20Feat(RealPluginManager,=20?= =?UTF-8?q?IPluginServiceProvider):=20=E9=87=8D=E6=9E=84=E6=8F=92=E4=BB=B6?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=99=A8=E4=BB=A5=E4=BD=BF=E7=94=A8=E6=8F=92?= =?UTF-8?q?=E4=BB=B6=E6=9C=8D=E5=8A=A1=E6=8F=90=E4=BE=9B=E8=80=85=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=EF=BC=8C=E6=B7=BB=E5=8A=A0=E6=8F=92=E4=BB=B6=E5=93=8D?= =?UTF-8?q?=E5=BA=94=E8=AE=A2=E9=98=85=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Core/IPluginServiceProvider.cs | 44 +++++++ .../Core/RealPluginManager.cs | 119 +++--------------- 2 files changed, 63 insertions(+), 100 deletions(-) create mode 100644 KitX Script/Kscript.CSharp.Parser/Core/IPluginServiceProvider.cs diff --git a/KitX Script/Kscript.CSharp.Parser/Core/IPluginServiceProvider.cs b/KitX Script/Kscript.CSharp.Parser/Core/IPluginServiceProvider.cs new file mode 100644 index 0000000..badc3e9 --- /dev/null +++ b/KitX Script/Kscript.CSharp.Parser/Core/IPluginServiceProvider.cs @@ -0,0 +1,44 @@ +using KitX.Shared.CSharp.Plugin; + +namespace Kscript.CSharp.Parser.Core; + +/// +/// 插件服务提供者接口 - 由Dashboard实现 +/// 用于解耦Parser和Dashboard,避免直接依赖和反射 +/// +public interface IPluginServiceProvider +{ + /// + /// 获取所有运行中的插件信息 + /// + /// 运行中的插件信息集合 + IEnumerable GetRunningPlugins(); + + /// + /// 根据插件名称查找插件信息 + /// + /// 插件名称 + /// 插件信息,如果未找到则返回null + PluginInfo? FindPlugin(string pluginName); + + /// + /// 查找插件连接器 + /// + /// 插件信息 + /// 插件连接器对象,如果未找到则返回null + object? FindConnector(PluginInfo pluginInfo); + + /// + /// 向插件连接器发送请求 + /// + /// 插件连接器 + /// 请求对象 + /// 异步任务 + Task SendRequestAsync(object connector, object request); + + /// + /// 订阅插件响应事件 + /// + /// 响应处理器,接收(requestId, responseJson) + void SubscribeToResponses(Action responseHandler); +} diff --git a/KitX Script/Kscript.CSharp.Parser/Core/RealPluginManager.cs b/KitX Script/Kscript.CSharp.Parser/Core/RealPluginManager.cs index 3626efa..1f7d94c 100644 --- a/KitX Script/Kscript.CSharp.Parser/Core/RealPluginManager.cs +++ b/KitX Script/Kscript.CSharp.Parser/Core/RealPluginManager.cs @@ -13,7 +13,7 @@ namespace Kscript.CSharp.Parser.Core; /// public class RealPluginManager : IPluginManager { - private readonly object _pluginsServer; + private readonly IPluginServiceProvider _serviceProvider; private readonly Action _infoLogger; private readonly Action _errorLogger; @@ -30,16 +30,24 @@ public class RealPluginManager : IPluginManager /// /// 构造函数 /// - /// PluginsServer 实例 + /// 插件服务提供者实例 /// 信息日志记录器 /// 错误日志记录器 - public RealPluginManager(object pluginsServer, Action? infoLogger = null, Action? errorLogger = null) + public RealPluginManager( + IPluginServiceProvider serviceProvider, + Action? infoLogger = null, + Action? errorLogger = null) { - _pluginsServer = pluginsServer ?? throw new ArgumentNullException(nameof(pluginsServer)); + _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); _infoLogger = infoLogger ?? (message => Console.WriteLine(message)); _errorLogger = errorLogger ?? (message => Console.WriteLine(message)); - _infoLogger("[WorkflowScriptService] 正在初始化 RealPluginManager..."); + _infoLogger("[RealPluginManager] 正在初始化 RealPluginManager..."); + + // 订阅插件响应事件 + _serviceProvider.SubscribeToResponses(HandlePluginResponse); + + _infoLogger("[RealPluginManager] RealPluginManager 初始化完成"); } /// @@ -52,7 +60,7 @@ public T Call(PluginCallInfo callInfo) _infoLogger($"[RealPluginManager] 开始调用插件方法: {callInfo}"); // 查找插件信息 - var pluginInfo = FindPluginInfo(callInfo.PluginName); + var pluginInfo = _serviceProvider.FindPlugin(callInfo.PluginName); if (pluginInfo == null) { _infoLogger($"[RealPluginManager] 未找到插件: {callInfo.PluginName}"); @@ -60,7 +68,7 @@ public T Call(PluginCallInfo callInfo) } // 查找插件连接器 - var connector = FindPluginConnector(pluginInfo); + var connector = _serviceProvider.FindConnector(pluginInfo); if (connector == null) { _infoLogger($"[RealPluginManager] 插件 {callInfo.PluginName} 未连接"); @@ -95,7 +103,7 @@ public bool IsPluginExists(string pluginName) { try { - var pluginInfo = FindPluginInfo(pluginName); + var pluginInfo = _serviceProvider.FindPlugin(pluginName); var exists = pluginInfo != null; _infoLogger($"[RealPluginManager] 检查插件 '{pluginName}' 是否存在: {exists}"); @@ -115,7 +123,7 @@ public bool IsMethodExists(string pluginName, string methodName) { try { - var pluginInfo = FindPluginInfo(pluginName); + var pluginInfo = _serviceProvider.FindPlugin(pluginName); var exists = pluginInfo?.Functions.Any(f => f.Name == methodName) ?? false; _infoLogger($"[RealPluginManager] 检查方法 '{pluginName}.{methodName}' 是否存在: {exists}"); @@ -128,72 +136,6 @@ public bool IsMethodExists(string pluginName, string methodName) } } - /// - /// 查找插件信息 - /// - private PluginInfo? FindPluginInfo(string pluginName) - { - try - { - // 通过传入的 PluginsServer 实例查找插件信息 - var pluginsServerType = _pluginsServer.GetType(); - var pluginConnectorsProperty = pluginsServerType.GetProperty("PluginConnectors"); - - if (pluginConnectorsProperty != null) - { - var pluginConnectors = pluginConnectorsProperty.GetValue(_pluginsServer) as System.Collections.IList; - if (pluginConnectors != null) - { - foreach (var connector in pluginConnectors) - { - if (connector != null) - { - var connectorType = connector.GetType(); - var pluginInfoProperty = connectorType.GetProperty("PluginInfo"); - if (pluginInfoProperty != null) - { - var pluginInfo = pluginInfoProperty.GetValue(connector) as PluginInfo; - if (pluginInfo != null && pluginInfo.Name == pluginName) - { - return pluginInfo; - } - } - } - } - } - } - } - catch (Exception ex) - { - _errorLogger($"[RealPluginManager] 查找插件信息失败: {pluginName} - 异常: {ex.Message}"); - } - - return null; - } - - /// - /// 查找插件连接器 - /// - private object? FindPluginConnector(PluginInfo pluginInfo) - { - try - { - var pluginsServerType = _pluginsServer.GetType(); - var findConnectorMethod = pluginsServerType.GetMethod("FindConnector", new[] { typeof(PluginInfo) }); - - if (findConnectorMethod != null) - { - return findConnectorMethod.Invoke(_pluginsServer, new object[] { pluginInfo }); - } - } - catch (Exception ex) - { - _errorLogger($"[RealPluginManager] 查找插件连接器失败: {pluginInfo.Name} - 异常: {ex.Message}"); - } - - return null; - } - /// /// 发送插件请求 /// @@ -205,7 +147,7 @@ private async Task SendPluginRequest(object connector, PluginCallInfo call var connectorInstance = new Connector() .SetSerializer(x => JsonSerializer.Serialize(x, _serializerOptions)) .SetSender(request => { - SendRequestToConnector(connector, request).ConfigureAwait(false); + _serviceProvider.SendRequestAsync(connector, request).ConfigureAwait(false); }); // 构建参数列表 @@ -219,6 +161,7 @@ private async Task SendPluginRequest(object connector, PluginCallInfo call { Name = $"param{i}", Type = paramType.Name, + // TODO: 等待KitX数据传输标准制定完成后,替换为标准序列化方法 Value = paramValue?.ToString() ?? string.Empty, IsOptional = false }); @@ -295,30 +238,6 @@ private async Task SendPluginRequest(object connector, PluginCallInfo call } } - /// - /// 向连接器发送请求 - /// - private async Task SendRequestToConnector(object connector, Request request) - { - try - { - var connectorType = connector.GetType(); - var requestMethod = connectorType.GetMethod("Request", new[] { typeof(Request) }); - if (requestMethod != null) - { - var result = requestMethod.Invoke(connector, new object[] { request }); - if (result is Task task) - { - await task; - } - } - } - catch (Exception ex) - { - _errorLogger($"[RealPluginManager] 向连接器发送请求失败 - 异常: {ex.Message}"); - } - } - /// /// 处理插件响应 ///