init repo

This commit is contained in:
StarBeats 2025-10-14 21:05:08 +08:00
parent 2bfce66fb5
commit 5c8c6a82bb
19 changed files with 1099 additions and 0 deletions

4
.gitignore vendored
View File

@ -23,6 +23,10 @@ bld/
[Ll]og/ [Ll]og/
[Ll]ogs/ [Ll]ogs/
.vs
.vscode
.idea
# .NET Core # .NET Core
project.lock.json project.lock.json
project.fragment.lock.json project.fragment.lock.json

View File

@ -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);
}
}
}
}

View File

@ -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<MethodDefinition> AbstractMethods(this TypeDefinition type) =>
type.Methods.Where(_ => _.IsAbstract).ToList();
public static IEnumerable<MethodDefinition> 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<Instruction> 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<Instruction> 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<TypeDefinition> types, string name)
{
var type = types.FirstOrDefault(_ => _.Name == name);
if (type is null)
{
throw new($"Could not find type '{name}'.");
}
return type;
}
}
}

View File

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
<PackageReference Include="Mono.Cecil" Version="0.11.6" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\JsonRPC\JsonRPC.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,8 @@
namespace CodeGenerator
{
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class JsonRPCAttribute : Attribute
{
}
}

View File

@ -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<string> logger;
private ModuleDefinition moduleDefinition = null!;
private List<TypeDefinition> types = null!;
private string filePath = null!;
private MethodDefinition moduleeStaticConstructor = null;
public JsonRPCCodeGenerator(Action<string> 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("<Module>"))
{
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<MetadataReference>()
.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<JsonRPC.Protocol.JsonRPCRequest, JsonRPC.Protocol.JsonRPCResponse>({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<JsonRPC.Protocol.JsonRPCResponse>(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<JsonRPC.Protocol.JsonRPCResponse<{method.ReturnType.FullName}>>(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();
}
}
}
}
}

43
JsonRPC.sln Normal file
View File

@ -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

18
JsonRPC/JsonRPC.csproj Normal file
View File

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<BaseOutputPath></BaseOutputPath>
</PropertyGroup>
<ItemGroup>
<Folder Include="Http\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
</ItemGroup>
</Project>

View File

@ -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
}
}

View File

@ -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!;
}
}

View File

@ -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<object> 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;
}
}
}

View File

@ -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<T> : JsonRPCResponse
{
[JsonPropertyName("result")]
public T? Result { get; set; }
}
}

19
JsonRPC/RPC/AbsSingle.cs Normal file
View File

@ -0,0 +1,19 @@
public abstract class AbsSingle<T> 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();
}
}
}

71
JsonRPC/RPC/Receiver.cs Normal file
View File

@ -0,0 +1,71 @@
using JsonRPC.Protocol;
using Newtonsoft.Json;
namespace JsonRPC.RPC
{
public class Receiver : AbsSingle<Receiver>
{
Dictionary<string, Func<JsonRPCRequest, JsonRPCResponse>> handlers = new();
public void RegHandler(string methodSig, Func<JsonRPCRequest, JsonRPCResponse> func)
{
handlers.Add(methodSig, func);
}
public string OnReceive(string msg)
{
JsonRPCResponse response;
var req = JsonConvert.DeserializeObject<JsonRPCRequest>(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);
}
}
}

45
JsonRPC/RPC/Sender.cs Normal file
View File

@ -0,0 +1,45 @@
using JsonRPC.Protocol;
using Newtonsoft.Json;
namespace JsonRPC.RPC
{
public class Sender : AbsSingle<Sender>
{
Func<string, string> sendFunc = null!;
Action<JsonRPCError> errorFunc = null!;
int id;
public int GetId()
{
return Interlocked.Increment(ref id);
}
public void SetSendFunction(Func<string, string> 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<T>(string json) where T : JsonRPCResponse
{
return JsonConvert.DeserializeObject<T>(json);
}
}
}

17
Test/Program.cs Normal file
View File

@ -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);

16
Test/Test.csproj Normal file
View File

@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\CodeGenerator\CodeGenerator.csproj" />
<ProjectReference Include="..\JsonRPC\JsonRPC.csproj" />
<ProjectReference Include="..\TestInterceptor\TestInterceptor.csproj" />
</ItemGroup>
</Project>

36
TestInterceptor/Class1.cs Normal file
View File

@ -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";
}
}
}

View File

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\CodeGenerator\CodeGenerator.csproj" />
<ProjectReference Include="..\JsonRPC\JsonRPC.csproj" />
</ItemGroup>
</Project>