using dnlib.DotNet; using HybridCLR.Editor.ABI; using HybridCLR.Editor.Meta; using HybridCLR.Editor.Template; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; using UnityEditor; using UnityEngine; using TypeInfo = HybridCLR.Editor.ABI.TypeInfo; namespace HybridCLR.Editor.MethodBridge { public class Generator { public class Options { public PlatformABI PlatformABI { get; set; } public string TemplateCode { get; set; } public string OutputFile { get; set; } public IReadOnlyList NotGenericMethods { get; set; } public IReadOnlyCollection GenericMethods { get; set; } public HashSet SpeicalPreserveMethods { get; set; } } private PlatformABI _platformABI; private readonly List _notGenericMethods; private readonly List _genericMethods; private readonly HashSet _preservedMethods; private readonly string _templateCode; private readonly string _outputFile; private readonly PlatformGeneratorBase _platformAdaptor; private readonly TypeCreatorBase _typeCreator; private readonly HashSet _managed2nativeMethodSet = new HashSet(); private readonly HashSet _native2managedMethodSet = new HashSet(); private readonly HashSet _adjustThunkMethodSet = new HashSet(); public Generator(Options options) { _platformABI = options.PlatformABI; List<(MethodDef, string)> notGenericMethodInfo = options.NotGenericMethods.Select(m => (m, m.FullName)).ToList(); notGenericMethodInfo.Sort((a, b) => string.Compare(a.Item2, b.Item2, StringComparison.Ordinal)); _notGenericMethods = notGenericMethodInfo.Select(m => m.Item1).ToList(); List<(GenericMethod, string)> genericMethodInfo = options.GenericMethods.Select(m => (m, m.ToString())).ToList(); genericMethodInfo.Sort((a, b) => string.CompareOrdinal(a.Item2, b.Item2)); _genericMethods = genericMethodInfo.Select(m => m.Item1).ToList(); _preservedMethods = options.SpeicalPreserveMethods; _templateCode = options.TemplateCode; _outputFile = options.OutputFile; _platformAdaptor = CreatePlatformAdaptor(options.PlatformABI); _typeCreator = TypeCreatorFactory.CreateTypeCreator(options.PlatformABI); } private static PlatformGeneratorBase CreatePlatformAdaptor(PlatformABI type) { switch (type) { case PlatformABI.Universal32: return new PlatformGeneratorUniversal32(); case PlatformABI.Universal64: return new PlatformGeneratorUniversal64(); case PlatformABI.Arm64: return new PlatformGeneratorArm64(); case PlatformABI.WebGL32: return new PlatformGeneratorWebGL32(); default: throw new NotSupportedException(); } } private MethodDesc CreateMethodDesc(MethodDef methodDef, bool forceRemoveThis, TypeSig returnType, List parameters) { var paramInfos = new List(); if (forceRemoveThis && !methodDef.IsStatic) { parameters.RemoveAt(0); } foreach (var paramInfo in parameters) { paramInfos.Add(new ParamInfo() { Type = _typeCreator.CreateTypeInfo(paramInfo) }); } var mbs = new MethodDesc() { MethodDef = methodDef, ReturnInfo = new ReturnInfo() { Type = returnType != null ? _typeCreator.CreateTypeInfo(returnType) : TypeInfo.s_void }, ParamInfos = paramInfos, }; _typeCreator.OptimizeMethod(mbs); return mbs; } private void AddManaged2NativeMethod(MethodDesc method) { method.Init(); _managed2nativeMethodSet.Add(method); } private void AddNative2ManagedMethod(MethodDesc method) { method.Init(); _native2managedMethodSet.Add(method); } private void AddAdjustThunkMethod(MethodDesc method) { method.Init(); _adjustThunkMethodSet.Add(method); } private void ProcessMethod(MethodDef method, List klassInst, List methodInst) { if (method.IsPrivate || (method.IsAssembly && !method.IsPublic && !method.IsFamily)) { if (!_preservedMethods.Contains(new GenericMethod(method, klassInst, methodInst))) { return; } else { //Debug.Log($"[PreservedMethod] method:{method}"); } } TypeSig returnType; List parameters; if (klassInst == null && methodInst == null) { returnType = method.ReturnType; parameters = method.Parameters.Select(p => p.Type).ToList(); } else { var gc = new GenericArgumentContext(klassInst, methodInst); returnType = MetaUtil.Inflate(method.ReturnType, gc); parameters = method.Parameters.Select(p => MetaUtil.Inflate(p.Type, gc)).ToList(); } var m2nMethod = CreateMethodDesc(method, false, returnType, parameters); AddManaged2NativeMethod(m2nMethod); if (method.IsVirtual) { if (method.DeclaringType.IsInterface) { AddAdjustThunkMethod(m2nMethod); } //var adjustThunkMethod = CreateMethodDesc(method, true, returnType, parameters); AddNative2ManagedMethod(m2nMethod); } if (method.Name == "Invoke" && method.DeclaringType.IsDelegate) { var openMethod = CreateMethodDesc(method, true, returnType, parameters); AddNative2ManagedMethod(openMethod); } } public void PrepareMethods() { foreach(var method in _notGenericMethods) { ProcessMethod(method, null, null); } foreach(var method in _genericMethods) { ProcessMethod(method.Method, method.KlassInst, method.MethodInst); } } public void Generate() { var frr = new FileRegionReplace(_templateCode.Replace("{PLATFORM_ABI}", ABIUtil.GetHybridCLRPlatformMacro(_platformABI))); List lines = new List(20_0000); List managed2NativeMethodList = _managed2nativeMethodSet.ToList(); managed2NativeMethodList.Sort((a, b) => string.CompareOrdinal(a.Sig, b.Sig)); List native2ManagedMethodList = _native2managedMethodSet.ToList(); native2ManagedMethodList.Sort((a, b) => string.CompareOrdinal(a.Sig, b.Sig)); List adjustThunkMethodList = _adjustThunkMethodSet.ToList(); adjustThunkMethodList.Sort((a, b) => string.CompareOrdinal(a.Sig, b.Sig)); Debug.LogFormat("== managed2native:{0} native2managed:{1} adjustThunk:{2}", managed2NativeMethodList.Count, native2ManagedMethodList.Count, adjustThunkMethodList.Count); foreach(var method in managed2NativeMethodList) { _platformAdaptor.GenerateManaged2NativeMethod(method, lines); } _platformAdaptor.GenerateManaged2NativeStub(managed2NativeMethodList, lines); foreach (var method in native2ManagedMethodList) { _platformAdaptor.GenerateNative2ManagedMethod(method, lines); } _platformAdaptor.GenerateNative2ManagedStub(native2ManagedMethodList, lines); foreach (var method in adjustThunkMethodList) { _platformAdaptor.GenerateAdjustThunkMethod(method, lines); } _platformAdaptor.GenerateAdjustThunkStub(adjustThunkMethodList, lines); frr.Replace("CODE", string.Join("\n", lines)); Directory.CreateDirectory(Path.GetDirectoryName(_outputFile)); frr.Commit(_outputFile); } } }