diff --git a/.gitignore b/.gitignore index 35063fc..a8c110a 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,10 @@ bld/ [Ll]og/ [Ll]ogs/ +.vs +.vscode +.idea + # .NET Core project.lock.json project.fragment.lock.json diff --git a/CodeGenerator/AttributeChecker.cs b/CodeGenerator/AttributeChecker.cs new file mode 100644 index 0000000..095ce82 --- /dev/null +++ b/CodeGenerator/AttributeChecker.cs @@ -0,0 +1,36 @@ +using Mono.Cecil; + +namespace CodeGenerator +{ + public static class AttributeChecker + { + public static CustomAttribute? GetTargetttribute(this ICustomAttributeProvider definition) + { + var customAttributes = definition.CustomAttributes; + + return customAttributes.FirstOrDefault(_ => _.AttributeType.Name == nameof(JsonRPCAttribute)); + } + + public static bool ContainsTargetAttribute(this ICustomAttributeProvider definition) => + GetTargetttribute(definition) != null; + + public static bool IsCompilerGenerated(this ICustomAttributeProvider definition) + { + var customAttributes = definition.CustomAttributes; + + return customAttributes.Any(_ => _.AttributeType.Name == "CompilerGeneratedAttribute"); + } + + public static void RemoveTargetAttribute(this ICustomAttributeProvider definition) + { + var customAttributes = definition.CustomAttributes; + + var targetAttribute = customAttributes.FirstOrDefault(_ => _.AttributeType.Name == nameof(JsonRPCAttribute)); + + if (targetAttribute != null) + { + customAttributes.Remove(targetAttribute); + } + } + } +} diff --git a/CodeGenerator/CecilExtensions.cs b/CodeGenerator/CecilExtensions.cs new file mode 100644 index 0000000..cf69e18 --- /dev/null +++ b/CodeGenerator/CecilExtensions.cs @@ -0,0 +1,145 @@ +using Mono.Cecil; +using Mono.Cecil.Cil; +using Mono.Collections.Generic; + +namespace CodeGenerator +{ + public static class CecilExtensions + { + public static bool IsBoxingRequired(this TypeReference typeReference, TypeReference expectedType) + { + if (expectedType.IsValueType && string.Equals(typeReference.FullName, expectedType.FullName)) + { + // Boxing is never required if type is expected + return false; + } + + if (typeReference.IsValueType || + typeReference.IsGenericParameter) + { + return true; + } + + return false; + } + + public static IEnumerable AbstractMethods(this TypeDefinition type) => + type.Methods.Where(_ => _.IsAbstract).ToList(); + + public static IEnumerable ConcreteMethods(this TypeDefinition type) => + type.Methods.Where(x => !x.IsAbstract && + x.HasBody && + !IsEmptyConstructor(x)).ToList(); + + static bool IsEmptyConstructor(this MethodDefinition method) => + method.Name == ".ctor" && + method.Body.Instructions.Count(_ => _.OpCode != OpCodes.Nop) == 3; + + public static bool IsInstanceConstructor(this MethodDefinition methodDefinition) => + methodDefinition.IsConstructor && !methodDefinition.IsStatic; + + public static void InsertBefore(this MethodBody body, Instruction target, Instruction instruction) => + body.Instructions.InsertBefore(target, instruction); + + public static void InsertBefore(this Collection instructions, Instruction target, Instruction instruction) + { + var index = instructions.IndexOf(target); + instructions.Insert(index, instruction); + } + + public static string MethodName(this MethodDefinition method) + { + if (method.IsConstructor) + { + return $"{method.DeclaringType.Name}{method.Name} "; + } + + return $"{method.DeclaringType.Name}.{method.Name} "; + } + + public static void Insert(this MethodBody body, int index, IEnumerable instructions) + { + instructions = instructions.Reverse(); + foreach (var instruction in instructions) + { + body.Instructions.Insert(index, instruction); + } + } + + public static void Add(this MethodBody body, params Instruction[] instructions) + { + foreach (var instruction in instructions) + { + body.Instructions.Add(instruction); + } + } + + public static bool IsYield(this MethodDefinition method) + { + if (method.ReturnType is null) + { + return false; + } + + if (!method.ReturnType.Name.StartsWith("IEnumerable")) + { + return false; + } + + var stateMachinePrefix = $"<{method.Name}>"; + var nestedTypes = method.DeclaringType.NestedTypes; + return nestedTypes.Any(_ => _.Name.StartsWith(stateMachinePrefix)); + } + + public static CustomAttribute GetAsyncStateMachineAttribute(this MethodDefinition method) => + method.CustomAttributes.FirstOrDefault(_ => _.AttributeType.Name == "AsyncStateMachineAttribute"); + + public static bool IsAsync(this MethodDefinition method) => + GetAsyncStateMachineAttribute(method) != null; + + public static bool IsLeaveInstruction(this Instruction instruction) => + instruction.OpCode == OpCodes.Leave || + instruction.OpCode == OpCodes.Leave_S; + + public static MethodDefinition Method(this TypeDefinition type, string name) + { + var method = type.Methods.FirstOrDefault(_ => _.Name == name); + + if (method is null) + { + throw new($"Could not find method '{name}' on type {type.FullName}."); + } + + return method; + } + + public static MethodDefinition Method(this TypeDefinition type, string name, params string[] parameters) + { + var method = type.Methods + .FirstOrDefault(x => + { + return x.Name == name && + parameters.Length == x.Parameters.Count && + x.Parameters.Select(y => y.ParameterType.Name).SequenceEqual(parameters); + }); + + if (method is null) + { + throw new($"Could not find method '{name}' on type {type.FullName}."); + } + + return method; + } + + public static TypeDefinition Type(this List types, string name) + { + var type = types.FirstOrDefault(_ => _.Name == name); + if (type is null) + { + throw new($"Could not find type '{name}'."); + } + + return type; + } + } +} diff --git a/CodeGenerator/CodeGenerator.csproj b/CodeGenerator/CodeGenerator.csproj new file mode 100644 index 0000000..8c3b2d6 --- /dev/null +++ b/CodeGenerator/CodeGenerator.csproj @@ -0,0 +1,18 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + diff --git a/CodeGenerator/JsonRPCAttribute.cs b/CodeGenerator/JsonRPCAttribute.cs new file mode 100644 index 0000000..f6ab669 --- /dev/null +++ b/CodeGenerator/JsonRPCAttribute.cs @@ -0,0 +1,8 @@ +namespace CodeGenerator +{ + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] + public class JsonRPCAttribute : Attribute + { + + } +} diff --git a/CodeGenerator/JsonRPCCodeGenerator.cs b/CodeGenerator/JsonRPCCodeGenerator.cs new file mode 100644 index 0000000..5aaf5a2 --- /dev/null +++ b/CodeGenerator/JsonRPCCodeGenerator.cs @@ -0,0 +1,506 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Emit; +using Mono.Cecil; +using Mono.Cecil.Cil; +using Mono.Cecil.Rocks; +using System.Text; + +namespace CodeGenerator +{ + public class JsonRPCCodeGenerator + { + private readonly Action logger; + private ModuleDefinition moduleDefinition = null!; + private List types = null!; + private string filePath = null!; + private MethodDefinition moduleeStaticConstructor = null; + public JsonRPCCodeGenerator(Action logFn) + { + this.logger = logFn; + } + + public void LoadAssembly(string filePath) + { + this.filePath = filePath; + var readerParameters = new ReaderParameters + { + InMemory = true + }; + + moduleDefinition = ModuleDefinition.ReadModule(filePath, readerParameters); + } + + public void WriteModule() + { + ArgumentNullException.ThrowIfNull(filePath, $"WriteModule call but {filePath} is null."); + + moduleDefinition.Write(filePath); + } + + private void AddModuleStaticConstructor(TypeDefinition type) + { + var sctor = type.GetStaticConstructor(); + if (sctor == null) + { + sctor = new MethodDefinition(".cctor", MethodAttributes.Static | MethodAttributes.Private | MethodAttributes.HideBySig, moduleDefinition.ImportReference(typeof(void))); + sctor.IsRuntimeSpecialName = true; + sctor.IsSpecialName = true; + + //var cw = moduleDefinition.ImportReference(typeof(Console).GetMethods().Where(m => m.GetParameters().Length == 1 && m.Name == "WriteLine" && m.GetParameters()[0].ParameterType == typeof(string)).First()); + + // sctor.Body.Instructions.Add(Instruction.Create(OpCodes.Nop)); + // sctor.Body.Instructions.Add(Instruction.Create(OpCodes.Ldstr, "AddModuleStaticConstructor")); + // sctor.Body.Instructions.Add(Instruction.Create(OpCodes.Call, cw)); + + sctor.Body.Instructions.Add(Instruction.Create(OpCodes.Nop)); + sctor.Body.Instructions.Add(Instruction.Create(OpCodes.Ret)); + type.Methods.Add(sctor); + } + + moduleeStaticConstructor = sctor; + } + + private void AddModuleInitializerMethod(MethodReference methodReference) + { + ArgumentNullException.ThrowIfNull(moduleeStaticConstructor, $"moduleeStaticConstructor is null."); + if (moduleeStaticConstructor.Body.Instructions[0].OpCode.Code == Code.Nop) + { + moduleeStaticConstructor.Body.Instructions.Insert(1, Instruction.Create(OpCodes.Call, methodReference)); + } + else + { + moduleeStaticConstructor.Body.Instructions.Insert(0, Instruction.Create(OpCodes.Call, methodReference)); + } + } + + public void ProcessAssembly() + { + ArgumentNullException.ThrowIfNull(moduleDefinition, $"moduleDefinition is null."); + + types = moduleDefinition.GetTypes().ToList(); + foreach (var type in types) + { + if (type.Name.Equals("")) + { + AddModuleStaticConstructor(type); + break; + } + } + + if (moduleDefinition.Assembly.ContainsTargetAttribute()) + { + foreach (var type in types) + { + if (type.IsCompilerGenerated()) + { + continue; + } + + foreach (var method in type.ConcreteMethods()) + { + ProcessMethod(type, method); + } + } + + return; + } + + foreach (var type in types) + { + if (type.IsCompilerGenerated()) + { + continue; + } + + if (type.ContainsTargetAttribute()) + { + foreach (var method in type.ConcreteMethods()) + { + ProcessMethod(type, method); + } + + continue; + } + + foreach (var method in type.ConcreteMethods().Where(_ => _.ContainsTargetAttribute())) + { + ProcessMethod(type, method); + } + } + + RemoveAttributes(); + } + + private MethodDefinition CompileFunc(MemoryStream ms, string funcBody, string returnType) + { + returnType = returnType.EndsWith("Void") ? "void" : returnType; + string code = @$" + using System; + public class DynamicClass + {{ + public static {returnType} HelloWorld {funcBody} + }}"; + + // 创建语法树 + SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(code); + + // 引用必要的程序集 + var references = AppDomain.CurrentDomain.GetAssemblies() + .Where(a => !a.IsDynamic) + .Select(a => MetadataReference.CreateFromFile(a.Location)) + .Cast() + .ToList(); + + // 创建编译选项 + CSharpCompilationOptions options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: true); + + // 创建编译对象 + CSharpCompilation compilation = CSharpCompilation.Create( + "DynamicAssembly", + new[] { syntaxTree }, + references, + options + ); + + // 编译代码 + EmitResult result = compilation.Emit(ms); + + // 检查编译错误 + if (!result.Success) + { + foreach (var diagnostic in result.Diagnostics) + { + Console.WriteLine(diagnostic.ToString()); + } + + return null; + } + + // 加载编译后的程序集 + ms.Seek(0, SeekOrigin.Begin); + + var md = ModuleDefinition.ReadModule(ms); + var m = md.GetType("DynamicClass").Method("HelloWorld"); + + return m; + } + + private string GetMethodSignature(MethodDefinition method) + { + StringBuilder sb = new StringBuilder(); + + sb.Append(method.DeclaringType.FullName.Replace('.', '_')); + sb.Append("__"); + sb.Append(method.Name); + sb.Append('_'); + for (int i = 0; i < method.Parameters.Count; i++) + { + var param = method.Parameters[i]; + sb.Append('_'); + sb.Append(param.ParameterType.Name); + } + return sb.ToString(); + } + + private MethodDefinition AddRPCRealMethod(TypeDefinition type, MethodDefinition method) + { + var methodSignature = GetMethodSignature(method); + MethodBody body = method.Body; + + MethodDefinition realMethod = new MethodDefinition(methodSignature, MethodAttributes.Public | MethodAttributes.Static, method.ReturnType); + realMethod.Body.InitLocals = body.InitLocals; + realMethod.Body.Instructions.Clear(); + realMethod.Body.Variables.Clear(); + + for (int i = 0; i < body.Instructions.Count; i++) + { + var ins = body.Instructions[i]; + realMethod.Body.Instructions.Add(ins); + } + + + for (int i = 0; i < body.Variables.Count; i++) + { + realMethod.Body.Variables.Add(body.Variables[i]); + } + + for (int i = 0; i < method.Parameters.Count; i++) + { + var param = method.Parameters[i]; + realMethod.Parameters.Add(param); + } + type.Methods.Add(realMethod); + + + return realMethod; + } + + private void AddRPCHandleMethod(TypeDefinition type, MethodDefinition method) + { + var name = method.Name +"_Handle"; + var handleMethod = new MethodDefinition(name, MethodAttributes.Static | MethodAttributes.Public, moduleDefinition.ImportReference(typeof(JsonRPC.Protocol.JsonRPCResponse))); + handleMethod.Parameters.Add(new("req", ParameterAttributes.In, moduleDefinition.ImportReference(typeof(JsonRPC.Protocol.JsonRPCRequest)))); + using var ms = new MemoryStream(); + StringBuilder bodyBuilder = new(); + StringBuilder parameterBuilder = new(); + type.Methods.Add(handleMethod); + bodyBuilder.AppendLine(@$" +(JsonRPC.Protocol.JsonRPCRequest req) +{{ + + if(req.Params != null && req.Params.Count != {method.Parameters.Count}) + {{ + return new JsonRPC.Protocol.JsonRPCResponse() + {{ + Id = req.Id, + Error = new JsonRPC.Protocol.JsonRPCError() + {{ + Code = (int)JsonRPC.Protocol.EErrorCode.InvalidParam, + Message = ""req Parameters {method.Parameters.Count}"", + }} + }}; + }} + else + {{ + + "); + + bool returnVoid = method.ReturnType.Name.EndsWith("Void"); + if (!returnVoid) + { + bodyBuilder.Append("var ret ="); + } + bodyBuilder.Append($"{method.Name}("); + for (int i = 0; i < method.Parameters.Count; i++) + { + var param = method.Parameters[i]; + bodyBuilder.Append($"({param.ParameterType.FullName})req.Params[{i}]"); + parameterBuilder.Append(param.ParameterType.FullName); + parameterBuilder.Append(' '); + parameterBuilder.Append(param.Name); + if (param != method.Parameters[^1]) + { + parameterBuilder.Append(','); + bodyBuilder.Append(','); + } + } + bodyBuilder.Append(");"); + if (returnVoid) + { + bodyBuilder.AppendLine(@" + return new JsonRPC.Protocol.JsonRPCResponse() + { + Id = req.Id, + }; + "); + } + else + { + bodyBuilder.AppendLine(@$" + return new JsonRPC.Protocol.JsonRPCResponse<{method.ReturnType.FullName}>() + {{ + Id = req.Id, + Result = ret, + }}; + "); + } + bodyBuilder.AppendLine($@" + }} + }} + + public static {(returnVoid ? "void" : method.ReturnType.FullName)} {method.Name}({parameterBuilder.ToString()}){{ return {(returnVoid ? "" : "default")};}} + "); + + + var tempFunc = CompileFunc(ms, bodyBuilder.ToString(), "JsonRPC.Protocol.JsonRPCResponse"); + ReplaceMethodBody(tempFunc, handleMethod); + for (int i = 0; i < handleMethod.Body.Instructions.Count; i++) + { + var ins = handleMethod.Body.Instructions[i]; + if (ins.OpCode.Code == Code.Call && ins.Operand is MethodReference mr) + { + if (mr.Name.Equals(method.Name)) + { + ins.Operand = moduleDefinition.ImportReference(method); + } + } + } + + AddRPCRegMethod(type, handleMethod); + } + + private void AddRPCRegMethod(TypeDefinition type, MethodDefinition method) + { + var name = method.Name +"_Reg"; + + var regMethod = new MethodDefinition(name, MethodAttributes.Static | MethodAttributes.Public, moduleDefinition.ImportReference(typeof(void))); + type.Methods.Add(regMethod); + + var body = regMethod.Body; + + using var ms = new MemoryStream(); + StringBuilder bodyBuilder = new(); + + bodyBuilder.AppendLine(@$" + () + {{ + var fn = new Func({method.Name}); + JsonRPC.RPC.Receiver.Instance.RegHandler(""{method.Name.Replace("_Handle", "")}"", fn); + }} + public static JsonRPC.Protocol.JsonRPCResponse {method.Name}(JsonRPC.Protocol.JsonRPCRequest req){{ return null;}} + "); + + var tempFunc = CompileFunc(ms, bodyBuilder.ToString(), "Void"); + ReplaceMethodBody(tempFunc, regMethod); + for (int i = 0; i < regMethod.Body.Instructions.Count; i++) + { + var ins = regMethod.Body.Instructions[i]; + if (ins.OpCode.Code == Code.Ldftn && ins.Operand is MethodReference mr) + { + if (mr.Name.Equals(method.Name)) + { + ins.Operand = moduleDefinition.ImportReference(method); + } + } + } + AddModuleInitializerMethod(regMethod); + } + + private void ReplaceMethodBody(MethodDefinition srcMethod, MethodDefinition dstMethod) + { + var body = dstMethod.Body; + body.Instructions.Clear(); + body.Variables.Clear(); + + srcMethod.Body.SimplifyMacros(); + + body.MaxStackSize = srcMethod.Body.MaxStackSize; + body.InitLocals = srcMethod.Body.InitLocals; + + body.LocalVarToken = srcMethod.Body.LocalVarToken; + + foreach (var item in srcMethod.Body.Instructions) + { + if (item.OpCode.Code == Code.Castclass && item.Operand is TypeReference tr) + { + var instruction = Instruction.Create(item.OpCode, moduleDefinition.ImportReference(tr)); + instruction.Offset = item.Offset; + body.Instructions.Add(instruction); + } + else if (item.Operand is MethodReference mr) + { + var newMr = moduleDefinition.ImportReference(mr); + var instruction = Instruction.Create(item.OpCode, newMr); + instruction.Offset = item.Offset; + body.Instructions.Add(instruction); + } + else + { + body.Instructions.Add(item); + } + } + + for (int i = 0; i < srcMethod.Body.Variables.Count; i++) + { + var localVar = srcMethod.Body.Variables[i]; + localVar.VariableType = moduleDefinition.ImportReference(localVar.VariableType); + body.Variables.Add(localVar); + } + body.Optimize(); + } + + private void ProcessMethod(TypeDefinition type, MethodDefinition method) + { + bool hasTargetAttribute = method.ContainsTargetAttribute(); + + if (method.IsYield()) + { + if (hasTargetAttribute) + { + logger.Invoke("Could not process '" + method.FullName + "' since methods that yield are currently not supported. Please remove the [Time] attribute from that method."); + return; + } + + logger.Invoke("Skipping '" + method.FullName + "' since methods that yield are not supported."); + return; + } + + if (!method.IsStatic) + { + logger.Invoke("Skipping '" + method.FullName + "' non static methods are not supported."); + return; + } + + if (method.IsAsync() && hasTargetAttribute) + { + logger.Invoke("Could not process '" + method.FullName + "' async methods are not supported."); + return; + } + + var realMethod = AddRPCRealMethod(type, method); + AddRPCHandleMethod(type, realMethod); + StringBuilder parameterBuilder = new(); + StringBuilder bodyBuilder = new(); + bool returnVoid = method.ReturnType.Name.EndsWith("Void"); + + parameterBuilder.Append('('); + bodyBuilder.AppendLine("{"); + bodyBuilder.AppendLine($"JsonRPC.Protocol.JsonRPCRequest req = new(\"{realMethod.Name}\", JsonRPC.RPC.Sender.Instance.GetId());"); + foreach (var item in method.Parameters) + { + parameterBuilder.Append(item.ParameterType.FullName); + parameterBuilder.Append(' '); + parameterBuilder.Append(item.Name); + if (item != method.Parameters[^1]) + { + parameterBuilder.Append(","); + } + bodyBuilder.AppendLine($"req.AddParam({item.Name});"); + } + parameterBuilder.Append(')'); + if(returnVoid) + { + bodyBuilder.AppendLine(@$" + var json = JsonRPC.RPC.Sender.Instance.Send(req); + var res = JsonRPC.RPC.Sender.Instance.JsonDeserialize(json); + if(!JsonRPC.RPC.Sender.Instance.HnadleResponseError(res)) + {{ + }} + return; + }}"); + } + else + { + bodyBuilder.AppendLine(@$" + var json = JsonRPC.RPC.Sender.Instance.Send(req); + var res = JsonRPC.RPC.Sender.Instance.JsonDeserialize>(json); + if(!JsonRPC.RPC.Sender.Instance.HnadleResponseError(res)) + {{ + return default; + }} + + return res.Result; + }}"); + } + + using var ms = new MemoryStream(); + var sigFunc = CompileFunc(ms, parameterBuilder.AppendLine(bodyBuilder.ToString()).ToString(), method.ReturnType.FullName); + ReplaceMethodBody(sigFunc, method); + } + + void RemoveAttributes() + { + moduleDefinition.Assembly.RemoveTargetAttribute(); + foreach (var typeDefinition in types) + { + typeDefinition.RemoveTargetAttribute(); + foreach (var method in typeDefinition.Methods) + { + method.RemoveTargetAttribute(); + } + } + } + + } +} diff --git a/JsonRPC.sln b/JsonRPC.sln new file mode 100644 index 0000000..f895a0a --- /dev/null +++ b/JsonRPC.sln @@ -0,0 +1,43 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.36518.9 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeGenerator", "CodeGenerator\CodeGenerator.csproj", "{35FC52B4-AB3D-4879-ADAA-F90A63A78F8D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test", "Test\Test.csproj", "{FA0F37D3-770B-42F4-A53D-36CB8161A6F4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestInterceptor", "TestInterceptor\TestInterceptor.csproj", "{0B65E76A-4435-4D6B-864A-097475739B42}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JsonRPC", "JsonRPC\JsonRPC.csproj", "{55CEBB38-910A-413E-80F3-35774E12A396}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {35FC52B4-AB3D-4879-ADAA-F90A63A78F8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {35FC52B4-AB3D-4879-ADAA-F90A63A78F8D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {35FC52B4-AB3D-4879-ADAA-F90A63A78F8D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {35FC52B4-AB3D-4879-ADAA-F90A63A78F8D}.Release|Any CPU.Build.0 = Release|Any CPU + {FA0F37D3-770B-42F4-A53D-36CB8161A6F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FA0F37D3-770B-42F4-A53D-36CB8161A6F4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FA0F37D3-770B-42F4-A53D-36CB8161A6F4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FA0F37D3-770B-42F4-A53D-36CB8161A6F4}.Release|Any CPU.Build.0 = Release|Any CPU + {0B65E76A-4435-4D6B-864A-097475739B42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0B65E76A-4435-4D6B-864A-097475739B42}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0B65E76A-4435-4D6B-864A-097475739B42}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0B65E76A-4435-4D6B-864A-097475739B42}.Release|Any CPU.Build.0 = Release|Any CPU + {55CEBB38-910A-413E-80F3-35774E12A396}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {55CEBB38-910A-413E-80F3-35774E12A396}.Debug|Any CPU.Build.0 = Debug|Any CPU + {55CEBB38-910A-413E-80F3-35774E12A396}.Release|Any CPU.ActiveCfg = Release|Any CPU + {55CEBB38-910A-413E-80F3-35774E12A396}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {D497A5D8-3095-4DF8-A63B-C381551EF746} + EndGlobalSection +EndGlobal diff --git a/JsonRPC/JsonRPC.csproj b/JsonRPC/JsonRPC.csproj new file mode 100644 index 0000000..80ca13d --- /dev/null +++ b/JsonRPC/JsonRPC.csproj @@ -0,0 +1,18 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + diff --git a/JsonRPC/Protocol/EJsonRPCVersion.cs b/JsonRPC/Protocol/EJsonRPCVersion.cs new file mode 100644 index 0000000..fba5136 --- /dev/null +++ b/JsonRPC/Protocol/EJsonRPCVersion.cs @@ -0,0 +1,15 @@ + +using System.Runtime.Serialization; + + +namespace JsonRPC.Protocol +{ + public enum EJsonRPCVersion + { + [EnumMember(Value = "1.0")] + Version1 = 1, + + [EnumMember(Value = "2.0")] + Version2 = 2 + } +} diff --git a/JsonRPC/Protocol/JsonRPCError.cs b/JsonRPC/Protocol/JsonRPCError.cs new file mode 100644 index 0000000..1e22d10 --- /dev/null +++ b/JsonRPC/Protocol/JsonRPCError.cs @@ -0,0 +1,26 @@ +using System.Text.Json.Serialization; + +namespace JsonRPC.Protocol +{ + public enum EErrorCode + { + ParseError = -32700,// 解析错误,服务器收到无效的 JSON。 + InvalidRequest = -32600, // 无效请求,发送的 JSON 不是有效的请求对象。 + MethodNotFound = -32601, // 方法未找到,方法不存在或无效。 + InvalidParam = -32602, // 无效参数,提供的参数无效。 + InternalError = -32603, // 内部错误,JSON-RPC 内部错误。 + } + + [Serializable] + public class JsonRPCError + { + [JsonPropertyName("data")] + public string? Data { get; set; } + + [JsonPropertyName("code")] + public int Code { get; set; } + + [JsonPropertyName("message")] + public string Message { get; set; } = null!; + } +} diff --git a/JsonRPC/Protocol/JsonRPCRequest.cs b/JsonRPC/Protocol/JsonRPCRequest.cs new file mode 100644 index 0000000..d4102ee --- /dev/null +++ b/JsonRPC/Protocol/JsonRPCRequest.cs @@ -0,0 +1,38 @@ +using System.Text.Json.Serialization; + +namespace JsonRPC.Protocol +{ + [Serializable] + public class JsonRPCRequest + { + [JsonPropertyName("method")] + public string Method { get; private set; } + + [JsonPropertyName("params")] + public List Params { get; private set; } = new(); + + [JsonPropertyName("id")] + public int Id { get; private set; } + + [JsonPropertyName("jsonrpc")] + public EJsonRPCVersion Version => EJsonRPCVersion.Version2; + + public JsonRPCRequest(string method, int id) + { + if (string.IsNullOrEmpty(method)) + { + throw new ArgumentNullException(method); + } + + this.Method = method; + this.Id = id; + } + + public JsonRPCRequest AddParam(object param) + { + this.Params.Add(param); + return this; + } + + } +} diff --git a/JsonRPC/Protocol/JsonRPCResponse.cs b/JsonRPC/Protocol/JsonRPCResponse.cs new file mode 100644 index 0000000..a5d090d --- /dev/null +++ b/JsonRPC/Protocol/JsonRPCResponse.cs @@ -0,0 +1,24 @@ +using System.Text.Json.Serialization; + +namespace JsonRPC.Protocol +{ + [Serializable] + public class JsonRPCResponse + { + [JsonPropertyName("jsonrpc")] + public EJsonRPCVersion Version { get; set; } = EJsonRPCVersion.Version2; + + [JsonPropertyName("error")] + public JsonRPCError? Error { get; set; } + + [JsonPropertyName("id")] + public int Id { get; set; } + } + + [Serializable] + public class JsonRPCResponse : JsonRPCResponse + { + [JsonPropertyName("result")] + public T? Result { get; set; } + } +} diff --git a/JsonRPC/RPC/AbsSingle.cs b/JsonRPC/RPC/AbsSingle.cs new file mode 100644 index 0000000..b0ca509 --- /dev/null +++ b/JsonRPC/RPC/AbsSingle.cs @@ -0,0 +1,19 @@ +public abstract class AbsSingle where T : class, new() +{ + static public T Instance + { + get + { + return SingleHolder.instance; + } + } + + private static class SingleHolder + { + public static T instance; + static SingleHolder() + { + instance = new T(); + } + } +} diff --git a/JsonRPC/RPC/Receiver.cs b/JsonRPC/RPC/Receiver.cs new file mode 100644 index 0000000..43ebfd3 --- /dev/null +++ b/JsonRPC/RPC/Receiver.cs @@ -0,0 +1,71 @@ +using JsonRPC.Protocol; +using Newtonsoft.Json; + +namespace JsonRPC.RPC +{ + public class Receiver : AbsSingle + { + Dictionary> handlers = new(); + + public void RegHandler(string methodSig, Func func) + { + handlers.Add(methodSig, func); + } + + public string OnReceive(string msg) + { + JsonRPCResponse response; + var req = JsonConvert.DeserializeObject(msg); + if(req == null) + { + response = new JsonRPCResponse() + { + Error = new JsonRPCError() + { + Code = (int)EErrorCode.ParseError, + Message = msg, + Data = "invalid json." + } + }; + + return JsonConvert.SerializeObject(response); + } + + if (handlers.TryGetValue(req.Method, out var handler)) + { + try + { + response = handler!(req); + } + catch (Exception e) + { + response = new JsonRPCResponse() + { + Id = req.Id, + Error = new JsonRPCError() + { + Code = (int)EErrorCode.InternalError, + Message = msg, + Data = e.ToString() + } + }; + } + } + else + { + response = new JsonRPCResponse() + { + Id = req.Id, + Error = new JsonRPCError() + { + Code = (int)EErrorCode.MethodNotFound, + Message = msg, + } + }; + } + + return JsonConvert.SerializeObject(response); + } + + } +} diff --git a/JsonRPC/RPC/Sender.cs b/JsonRPC/RPC/Sender.cs new file mode 100644 index 0000000..fe9afbc --- /dev/null +++ b/JsonRPC/RPC/Sender.cs @@ -0,0 +1,45 @@ +using JsonRPC.Protocol; +using Newtonsoft.Json; + +namespace JsonRPC.RPC +{ + public class Sender : AbsSingle + { + Func sendFunc = null!; + Action errorFunc = null!; + int id; + + public int GetId() + { + return Interlocked.Increment(ref id); + } + + public void SetSendFunction(Func send) + { + sendFunc = send; + } + + public bool HnadleResponseError(JsonRPCResponse response) + { + if(response.Error != null) + { + errorFunc?.Invoke(response.Error); + return false; + } + + return true; + } + + public string Send(JsonRPCRequest request) + { + var reqJs = JsonConvert.SerializeObject(request); + + return sendFunc.Invoke(reqJs); + } + + public T JsonDeserialize(string json) where T : JsonRPCResponse + { + return JsonConvert.DeserializeObject(json); + } + } +} diff --git a/Test/Program.cs b/Test/Program.cs new file mode 100644 index 0000000..e55e607 --- /dev/null +++ b/Test/Program.cs @@ -0,0 +1,17 @@ +// See https://aka.ms/new-console-template for more information +using CodeGenerator; +using JsonRPC.Protocol; +Console.WriteLine("Hello, World!"); + +JsonRPCCodeGenerator jsonRPCCodeGenerator = new JsonRPCCodeGenerator((s) => Console.WriteLine(s)); +jsonRPCCodeGenerator.LoadAssembly("G:\\Misc\\JsonRPC\\TestInterceptor\\bin\\Debug\\net8.0\\TestInterceptor.dll"); +jsonRPCCodeGenerator.ProcessAssembly(); +jsonRPCCodeGenerator.WriteModule(); +JsonRPC.RPC.Sender.Instance.SetSendFunction((s) => +{ + Console.WriteLine(s); + return JsonRPC.RPC.Receiver.Instance.OnReceive(s); +}); +TestInterceptor.Class1.Echo("ss"); +Console.WriteLine(TestInterceptor.Class1.Test1()); +//typeof(TestInterceptor.Class1).GetMethod("TestInterceptor_Class1__Test_", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public)?.Invoke(null, null); diff --git a/Test/Test.csproj b/Test/Test.csproj new file mode 100644 index 0000000..877d850 --- /dev/null +++ b/Test/Test.csproj @@ -0,0 +1,16 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + diff --git a/TestInterceptor/Class1.cs b/TestInterceptor/Class1.cs new file mode 100644 index 0000000..7425228 --- /dev/null +++ b/TestInterceptor/Class1.cs @@ -0,0 +1,36 @@ +using CodeGenerator; +using JsonRPC.Protocol; +using JsonRPC.RPC; +using System.Runtime.CompilerServices; + +namespace TestInterceptor +{ + public class Class1 + { + static Class1() { Console.WriteLine("Class1 Class1"); } + + [JsonRPC] + public static void Test() + { + Console.WriteLine("static TestFunc"); + } + + [JsonRPC] + public static void Echo(string msg) + { + Console.WriteLine(msg); + } + + [JsonRPC] + public static string Test1() + { + return "aaaaaaa"; + } + + + public static string Test11() + { + return "aaaaaaa"; + } + } +} diff --git a/TestInterceptor/TestInterceptor.csproj b/TestInterceptor/TestInterceptor.csproj new file mode 100644 index 0000000..8f83971 --- /dev/null +++ b/TestInterceptor/TestInterceptor.csproj @@ -0,0 +1,14 @@ + + + + net8.0 + enable + enable + + + + + + + +