using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.IO; using System.IO.Pipes; using System.Text; using System.Threading.Tasks; using System.Windows; using PCL.Core.App.IoC; using PCL.Core.IO; namespace PCL.Core.App.Essentials; /// /// 用于终止 Pipe RPC 执行过程并返回错误信息的异常
/// 当抛出该异常时 RPC 服务端将会返回内容为 ReasonERR 响应 ///
public class RpcException(string reason) : Exception { public string Reason => reason; } /// /// 标记一个方法或属性,使其成为 RPC 函数/属性。
/// 方法签名需兼容 委托,属性仅支持 类型。 ///
/// 该 RPC 函数/属性的名称 [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)] [DependencyCollector("rpc-function", AttributeTargets.Method)] [DependencyCollector("rpc-property", AttributeTargets.Property)] public sealed class RegisterRpc(string name) : Attribute; /// /// RPC 服务项 /// [LifecycleService(LifecycleState.Loaded)] [LifecycleScope("rpc", "远程执行服务")] public sealed partial class RpcService { private NamedPipeServerStream? _pipe; [LifecycleStart] private void _Start() { _pipe = PipeComm.StartPipeServer("Echo", _EchoPipeName, _EchoPipeCallback); } [LifecycleStop] private async Task _StopAsync() { if (_pipe is not null) await _pipe.DisposeAsync(); } [LifecycleDependencyInjection("rpc-function", AttributeTargets.Method)] private static void _CollectRpcFunctionRegistry(ImmutableList<(RpcFunction func, string name)> items) { foreach (var (func, name) in items) { AddFunction(name, func); } } [LifecycleDependencyInjection("rpc-property", AttributeTargets.Property)] private static void _CollectRpcPropertyRegistry(ImmutableList<(PropertyAccessor prop, string name)> items) { foreach (var (prop, name) in items) { AddProperty(new RpcProperty( name, () => prop.Value, value => prop.Value = value ?? "", prop.CanSet )); } } public const string PipePrefix = "PCLCE_RPC"; private static readonly string _EchoPipeName = $"{PipePrefix}@{Basics.CurrentProcess.Id}"; private static readonly string[] _RequestTypeArray = ["GET", "SET", "REQ"]; private static readonly HashSet _RequestType = [.._RequestTypeArray]; #region Property private static readonly Dictionary _PropertyMap = new(); /// /// 添加一个新的 RPC 属性,若有多个使用 foreach 即可 /// /// 要添加的属性 /// 是否成功添加(若已存在相同名称的属性则无法添加) public static bool AddProperty(RpcProperty prop) => _PropertyMap.TryAdd(prop.Name, prop); /// /// 通过指定的名称删除已存在的 RPC 属性 /// /// 属性名称 /// 是否成功删除(若不存在该名称则无法删除) public static bool RemoveProperty(string name) { return _PropertyMap.Remove(name); } /// /// 删除已存在的 RPC 属性,实质上仍然是通过属性的名称删除,但会检查是否是同一个对象 /// /// 要删除的属性 /// public static bool RemoveProperty(RpcProperty prop) { var key = prop.Name; var result = _PropertyMap.TryGetValue(key, out var value); if (!result || value != prop) return false; _PropertyMap.Remove(key); return true; } #endregion #region Function private static readonly Dictionary _FunctionMap = new() { ["ping"] = ((_, _, _) => RpcResponse.EmptySuccess), ["activate"] = ((_, _, _) => { if (Lifecycle.CurrentState >= LifecycleState.WindowCreated) ActivateMainWindow(); else Lifecycle.When(LifecycleState.WindowCreated, ActivateMainWindow); return RpcResponse.EmptySuccess; void ActivateMainWindow() { var app = Lifecycle.CurrentApplication; app.Dispatcher.BeginInvoke(() => { var window = app.MainWindow!; if (window.WindowState == WindowState.Minimized) window.WindowState = WindowState.Normal; if (!window.Topmost) { window.Topmost = true; window.Topmost = false; } window.Activate(); }); } }) }; /// /// 添加一个新的 RPC 函数,若有多个使用 foreach 即可 /// /// 函数名称 /// 函数过程 /// 是否成功添加(若已存在相同名称的函数则无法添加) public static bool AddFunction(string name, RpcFunction func) => _FunctionMap.TryAdd(name, func); /// /// 通过指定的名称删除已存在的 RPC 函数 /// /// 函数名称 /// 是否成功删除(若不存在该名称则无法删除) public static bool RemoveFunction(string name) { return _FunctionMap.Remove(name); } #endregion private static bool _EchoPipeCallback(StreamReader reader, StreamWriter writer, Process? client) { try { // GET/SET/REQ [target] // [content] var header = reader.ReadLine(); // 读入请求头 Context.Info($"客户端请求: {header}"); var args = header?.Split([' '], 2) ?? []; // 分离请求类型和参数 if (args.Length < 2 || args[1].Length == 0) throw new RpcException("请求参数过少"); var type = args[0].ToUpperInvariant(); if (!_RequestType.Contains(type)) throw new RpcException($"请求类型必须为 {string.Join("/", _RequestTypeArray)} 其中之一"); var target = args[1]; // 读入请求内容(可能没有) var buffer = new StringBuilder(); var tmp = reader.Read(); while (tmp != PipeComm.PipeEndingChar) { buffer.Append((char)tmp); tmp = reader.Read(); } var content = buffer.Length == 0 ? null : buffer.ToString(); switch (type) { case "GET": case "SET": { target = target.ToLowerInvariant(); var result = _PropertyMap.TryGetValue(target, out var prop); if (!result) throw new RpcException($"不存在属性 {target}"); RpcResponse response; if (type == "GET") { try { var value = prop!.Value; response = new RpcResponse(RpcResponseStatus.Success, RpcResponseType.Text, value, target); Context.Trace($"返回值: {value}"); } catch (RpcPropertyOperationFailedException) { response = RpcResponse.EmptyFailure; Context.Debug("设置失败: 只写属性或请求被拒绝"); } } else if (prop!.Settable) { try { prop.Value = content; response = RpcResponse.EmptySuccess; Context.Trace($"设置成功: {content}"); } catch (RpcPropertyOperationFailedException) { response = RpcResponse.EmptyFailure; Context.Debug("设置失败: 请求被拒绝"); } } else { response = RpcResponse.EmptyFailure; Context.Debug("设置失败: 只读属性"); } response.Response(writer); break; } case "REQ": { var targetArgs = target.Split([' '], 2); // 分离函数名和参数 var name = targetArgs[0].ToLowerInvariant(); var indent = false; // 检测缩进指示 if (name.EndsWith('$')) { indent = true; name = name[..^1]; } var result = _FunctionMap.TryGetValue(name, out var func); if (!result) throw new RpcException($"不存在函数 {name}"); string? argument = null; if (targetArgs.Length > 1) argument = targetArgs[1]; Context.Trace($"正在调用函数 {name} {argument}"); var response = func!(argument, content, indent); response.Response(writer); Context.Trace($"函数已退出,返回状态 {response.Status}"); break; } } } catch (Exception ex) { if (ex is RpcException rpcEx) { var reason = rpcEx.Reason; RpcResponse.Err(reason).Response(writer); Context.Info($"出错: {reason}"); } else { RpcResponse.Err(ex.ToString(), "stacktrace").Response(writer); Context.Error("处理请求时发生异常", ex); } } return true; } }