Compare commits
146 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
434160afa8 | |
|
|
01754c8fb4 | |
|
|
1934d90f27 | |
|
|
814572e1a1 | |
|
|
d505624c78 | |
|
|
e36bbb9022 | |
|
|
0642726469 | |
|
|
d86572db37 | |
|
|
b5944ea834 | |
|
|
73654e76d2 | |
|
|
1537690bb5 | |
|
|
7f158f814a | |
|
|
ee7af5c592 | |
|
|
ab41cd0144 | |
|
|
59a3c3974a | |
|
|
c9ff293dfe | |
|
|
56486b8a76 | |
|
|
8e27e8d41b | |
|
|
fca130bbfa | |
|
|
c21dbcc60a | |
|
|
7f2fa52f37 | |
|
|
da46e71534 | |
|
|
804418fddb | |
|
|
7a0032934b | |
|
|
b301b44cd3 | |
|
|
ddc3332958 | |
|
|
52f25bdb2a | |
|
|
31be818bb6 | |
|
|
213a11ff25 | |
|
|
585be97be9 | |
|
|
361873803f | |
|
|
46f308ed5e | |
|
|
34406b1c13 | |
|
|
e5b52922f7 | |
|
|
e44fa663db | |
|
|
903cf9faa2 | |
|
|
3fe41a8cba | |
|
|
603b0ccb24 | |
|
|
9429a4d24d | |
|
|
ea592ba20c | |
|
|
8d1280a1bd | |
|
|
528240b52b | |
|
|
4768c039d2 | |
|
|
10dc749d7a | |
|
|
0c5ef00c12 | |
|
|
1553582ec1 | |
|
|
756b906a21 | |
|
|
355e24fbe8 | |
|
|
9e4946aa45 | |
|
|
8e571d594d | |
|
|
b8dd2be1e4 | |
|
|
de7b60695a | |
|
|
a11ebecf93 | |
|
|
019412fa81 | |
|
|
84bf6d59db | |
|
|
82163d2e50 | |
|
|
208372e6af | |
|
|
dd8d812641 | |
|
|
c19b34542e | |
|
|
e30306d7e1 | |
|
|
20f34c9b14 | |
|
|
45749ffb5c | |
|
|
b176d63d93 | |
|
|
0c99afd58e | |
|
|
2458c9a9ba | |
|
|
4d0fef095b | |
|
|
642d2cd644 | |
|
|
e54ce36b0e | |
|
|
ac8b649d50 | |
|
|
9ca9cd5aa7 | |
|
|
37dafd7b3c | |
|
|
f58d12c2cb | |
|
|
d5eaa350c5 | |
|
|
1f0053a3c9 | |
|
|
15dccc7427 | |
|
|
7d287a2293 | |
|
|
f19ba3eb30 | |
|
|
4e172129d8 | |
|
|
2414e77fef | |
|
|
77517a9814 | |
|
|
162f21f6a5 | |
|
|
bd7c3293bc | |
|
|
e25be945c7 | |
|
|
925921c27c | |
|
|
64a6d26fa3 | |
|
|
c24617d864 | |
|
|
59bc259cc8 | |
|
|
c413cd6143 | |
|
|
c614cdcf4e | |
|
|
08f493ab3e | |
|
|
3705ca6e1f | |
|
|
34df279943 | |
|
|
b85ed1aa92 | |
|
|
faf4a328af | |
|
|
53f4f33bf0 | |
|
|
48c91f497b | |
|
|
9afbe3bd84 | |
|
|
2d5f71f6b7 | |
|
|
5747458bf2 | |
|
|
0b455eb882 | |
|
|
e086228d90 | |
|
|
909e96ca7c | |
|
|
75b24345f6 | |
|
|
88e20505f8 | |
|
|
b4b486944c | |
|
|
360ba38805 | |
|
|
e5a6952777 | |
|
|
9330cf682c | |
|
|
16a7d5d529 | |
|
|
303c6c7b35 | |
|
|
e4cf9e9086 | |
|
|
b977a85b23 | |
|
|
0fcb51a369 | |
|
|
d61511924d | |
|
|
aca7dd0a67 | |
|
|
fc685a99ca | |
|
|
d431aeb019 | |
|
|
63d0aaa6e6 | |
|
|
d45974c6b4 | |
|
|
3de931a3af | |
|
|
b96d6fc10d | |
|
|
ef1b78caae | |
|
|
47622bf49c | |
|
|
846d7103a3 | |
|
|
170f13082e | |
|
|
60016d7481 | |
|
|
97dae7fed2 | |
|
|
f935127cd3 | |
|
|
ec5f2ef05e | |
|
|
6d0ceaa4f5 | |
|
|
95922aec1e | |
|
|
3259c11828 | |
|
|
74bab1d935 | |
|
|
fd81edb495 | |
|
|
12bafb897f | |
|
|
a6befb1312 | |
|
|
a36e3fe72d | |
|
|
d9776d1793 | |
|
|
2697eb5a90 | |
|
|
4879bb7f44 | |
|
|
24f85780a2 | |
|
|
6c679cc3ab | |
|
|
20a06f1ec5 | |
|
|
5446bd937c | |
|
|
ae88dd0eec | |
|
|
a5c006d67c |
|
|
@ -0,0 +1,12 @@
|
||||||
|
#include "../Il2CppCompatibleDef.h"
|
||||||
|
|
||||||
|
namespace hybridclr
|
||||||
|
{
|
||||||
|
const char* g_placeHolderAssemblies[] =
|
||||||
|
{
|
||||||
|
//!!!{{PLACE_HOLDER
|
||||||
|
|
||||||
|
//!!!}}PLACE_HOLDER
|
||||||
|
nullptr,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
#include <codegen/il2cpp-codegen-metadata.h>
|
||||||
|
#if HYBRIDCLR_UNITY_2023_OR_NEW
|
||||||
|
#include <codegen/il2cpp-codegen.h>
|
||||||
|
#elif HYBRIDCLR_UNITY_2022
|
||||||
|
#include <codegen/il2cpp-codegen-il2cpp.h>
|
||||||
|
#elif HYBRIDCLR_UNITY_2020 || HYBRIDCLR_UNITY_2021
|
||||||
|
#include <codegen/il2cpp-codegen-common-big.h>
|
||||||
|
#else
|
||||||
|
#include <codegen/il2cpp-codegen-common.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "vm/ClassInlines.h"
|
||||||
|
#include "vm/Object.h"
|
||||||
|
#include "vm/Class.h"
|
||||||
|
#include "vm/ScopedThreadAttacher.h"
|
||||||
|
|
||||||
|
#include "../metadata/MetadataUtil.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include "../interpreter/InterpreterModule.h"
|
||||||
|
#include "../interpreter/MethodBridge.h"
|
||||||
|
#include "../interpreter/Interpreter.h"
|
||||||
|
#include "../interpreter/MemoryUtil.h"
|
||||||
|
#include "../interpreter/InstrinctDef.h"
|
||||||
|
|
||||||
|
using namespace hybridclr::interpreter;
|
||||||
|
using namespace hybridclr::metadata;
|
||||||
|
|
||||||
|
//!!!{{CODE
|
||||||
|
|
||||||
|
|
||||||
|
//!!!}}CODE
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
//!!!{{UNITY_VERSION
|
||||||
|
|
||||||
|
|
||||||
|
//!!!}}UNITY_VERSION
|
||||||
|
|
@ -2,23 +2,38 @@
|
||||||
"versions": [
|
"versions": [
|
||||||
{
|
{
|
||||||
"unity_version":"2019",
|
"unity_version":"2019",
|
||||||
"hybridclr" : { "branch":"v5.1.0"},
|
"hybridclr" : { "branch":"v8.6.0"},
|
||||||
"il2cpp_plus": { "branch":"v2019-5.0.0"}
|
"il2cpp_plus": { "branch":"v2019-8.1.0"}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"unity_version":"2020",
|
"unity_version":"2020",
|
||||||
"hybridclr" : { "branch":"v5.1.0"},
|
"hybridclr" : { "branch":"v8.6.0"},
|
||||||
"il2cpp_plus": { "branch":"v2020-5.0.0"}
|
"il2cpp_plus": { "branch":"v2020-8.1.0"}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"unity_version":"2021",
|
"unity_version":"2021",
|
||||||
"hybridclr" : { "branch":"v5.1.0"},
|
"hybridclr" : { "branch":"v8.6.0"},
|
||||||
"il2cpp_plus": { "branch":"v2021-5.0.0"}
|
"il2cpp_plus": { "branch":"v2021-8.1.0"}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"unity_version":"2022",
|
"unity_version":"2022",
|
||||||
"hybridclr" : { "branch":"v5.1.0"},
|
"hybridclr" : { "branch":"v8.6.0"},
|
||||||
"il2cpp_plus": { "branch":"v2022-5.1.0"}
|
"il2cpp_plus": { "branch":"v2022-8.2.0"}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"unity_version":"2022-tuanjie",
|
||||||
|
"hybridclr" : { "branch":"v8.6.0"},
|
||||||
|
"il2cpp_plus": { "branch":"v2022-tuanjie-8.6.0"}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"unity_version":"2023",
|
||||||
|
"hybridclr" : { "branch":"v8.6.0"},
|
||||||
|
"il2cpp_plus": { "branch":"v2023-8.1.0"}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"unity_version":"6000",
|
||||||
|
"hybridclr" : { "branch":"v8.6.0"},
|
||||||
|
"il2cpp_plus": { "branch":"v6000-8.1.0"}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -116,8 +116,8 @@ namespace MonoHook
|
||||||
|
|
||||||
static void SetupFlushICacheFunc()
|
static void SetupFlushICacheFunc()
|
||||||
{
|
{
|
||||||
string processorType = SystemInfo.processorType;
|
string processorType = SystemInfo.processorType.ToLowerInvariant();
|
||||||
if (processorType.Contains("Intel") || processorType.Contains("AMD"))
|
if (processorType.Contains("intel") || processorType.Contains("amd"))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (IntPtr.Size == 4)
|
if (IntPtr.Size == 4)
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,11 @@ namespace MonoHook
|
||||||
|
|
||||||
static HookUtils()
|
static HookUtils()
|
||||||
{
|
{
|
||||||
jit_write_protect_supported = pthread_jit_write_protect_supported_np() != 0;
|
try
|
||||||
|
{
|
||||||
|
jit_write_protect_supported = pthread_jit_write_protect_supported_np() != 0;
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
PropertyInfo p_SystemPageSize = typeof(Environment).GetProperty("SystemPageSize");
|
PropertyInfo p_SystemPageSize = typeof(Environment).GetProperty("SystemPageSize");
|
||||||
if (p_SystemPageSize == null)
|
if (p_SystemPageSize == null)
|
||||||
|
|
@ -35,6 +39,16 @@ namespace MonoHook
|
||||||
|
|
||||||
public static void MemCpy_Jit(void* pDst, byte[] src)
|
public static void MemCpy_Jit(void* pDst, byte[] src)
|
||||||
{
|
{
|
||||||
|
if (!jit_write_protect_supported)
|
||||||
|
{
|
||||||
|
fixed(void * pSrc = &src[0])
|
||||||
|
{
|
||||||
|
MemCpy(pDst, pSrc, src.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
fixed(void * p = &src[0])
|
fixed(void * p = &src[0])
|
||||||
{
|
{
|
||||||
memcpy_jit(new IntPtr(pDst), new IntPtr(p), src.Length);
|
memcpy_jit(new IntPtr(pDst), new IntPtr(p), src.Length);
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ using System.IO;
|
||||||
|
|
||||||
namespace HybridCLR.MonoHook
|
namespace HybridCLR.MonoHook
|
||||||
{
|
{
|
||||||
#if UNITY_2021_1_OR_NEWER
|
#if UNITY_2021_1_OR_NEWER && !UNITY_2023_1_OR_NEWER
|
||||||
[InitializeOnLoad]
|
[InitializeOnLoad]
|
||||||
public class CopyStrippedAOTAssembliesHook
|
public class CopyStrippedAOTAssembliesHook
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ using System.IO;
|
||||||
|
|
||||||
namespace HybridCLR.MonoHook
|
namespace HybridCLR.MonoHook
|
||||||
{
|
{
|
||||||
#if UNITY_2022
|
#if UNITY_2022 || UNITY_2023_1_OR_NEWER
|
||||||
[InitializeOnLoad]
|
[InitializeOnLoad]
|
||||||
public class GetIl2CppFolderHook
|
public class GetIl2CppFolderHook
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ using System.IO;
|
||||||
|
|
||||||
namespace HybridCLR.MonoHook
|
namespace HybridCLR.MonoHook
|
||||||
{
|
{
|
||||||
#if (UNITY_2021_1_OR_NEWER && UNITY_WEBGL) || TUANJIE_2022
|
#if UNITY_2021_1_OR_NEWER && (UNITY_WEBGL || UNITY_WEIXINMINIGAME)
|
||||||
[InitializeOnLoad]
|
[InitializeOnLoad]
|
||||||
public class PatchScriptingAssembliesJsonHook
|
public class PatchScriptingAssembliesJsonHook
|
||||||
{
|
{
|
||||||
|
|
@ -35,18 +35,29 @@ namespace HybridCLR.MonoHook
|
||||||
|
|
||||||
private static string BuildMainWindowTitle()
|
private static string BuildMainWindowTitle()
|
||||||
{
|
{
|
||||||
string tempJsonPath = $"{Application.dataPath}/../Library/PlayerDataCache/WebGL/Data/ScriptingAssemblies.json";
|
var cacheDir = $"{Application.dataPath}/../Library/PlayerDataCache";
|
||||||
#if TUANJIE_2022
|
if (Directory.Exists(cacheDir))
|
||||||
if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.WeixinMiniGame)
|
|
||||||
{
|
{
|
||||||
tempJsonPath = $"{Application.dataPath}/../Library/PlayerDataCache/WeixinMiniGame/Data/ScriptingAssemblies.json";
|
foreach (var tempJsonPath in Directory.GetDirectories(cacheDir, "*", SearchOption.TopDirectoryOnly))
|
||||||
}
|
{
|
||||||
|
string dirName = Path.GetFileName(tempJsonPath);
|
||||||
|
#if UNITY_WEIXINMINIGAME
|
||||||
|
if (!dirName.Contains("WeixinMiniGame"))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if (!dirName.Contains("WebGL"))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
if (File.Exists(tempJsonPath))
|
|
||||||
{
|
var patcher = new PatchScriptingAssemblyList();
|
||||||
var patcher = new PatchScriptingAssemblyList();
|
patcher.PathScriptingAssembilesFile(tempJsonPath);
|
||||||
patcher.PathScriptingAssembilesFile(Path.GetDirectoryName(tempJsonPath));
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string newTitle = BuildMainWindowTitleProxy();
|
string newTitle = BuildMainWindowTitleProxy();
|
||||||
return newTitle;
|
return newTitle;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,14 @@ namespace HybridCLR.Editor.AOT
|
||||||
{
|
{
|
||||||
public static byte[] Strip(byte[] assemblyBytes)
|
public static byte[] Strip(byte[] assemblyBytes)
|
||||||
{
|
{
|
||||||
var mod = ModuleDefMD.Load(assemblyBytes);
|
var context = ModuleDef.CreateModuleContext();
|
||||||
|
var readerOption = new ModuleCreationOptions(context)
|
||||||
|
{
|
||||||
|
Runtime = CLRRuntimeReaderKind.Mono
|
||||||
|
};
|
||||||
|
var mod = ModuleDefMD.Load(assemblyBytes, readerOption);
|
||||||
|
// remove all resources
|
||||||
|
mod.Resources.Clear();
|
||||||
foreach (var type in mod.GetTypes())
|
foreach (var type in mod.GetTypes())
|
||||||
{
|
{
|
||||||
if (type.HasGenericParameters)
|
if (type.HasGenericParameters)
|
||||||
|
|
@ -31,8 +38,8 @@ namespace HybridCLR.Editor.AOT
|
||||||
}
|
}
|
||||||
var writer = new System.IO.MemoryStream();
|
var writer = new System.IO.MemoryStream();
|
||||||
var options = new ModuleWriterOptions(mod);
|
var options = new ModuleWriterOptions(mod);
|
||||||
options.MetadataOptions.Flags |= MetadataFlags.PreserveAll;
|
options.MetadataOptions.Flags |= MetadataFlags.PreserveRids;
|
||||||
mod.Write(writer);
|
mod.Write(writer, options);
|
||||||
writer.Flush();
|
writer.Flush();
|
||||||
return writer.ToArray();
|
return writer.ToArray();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,12 +17,16 @@ namespace HybridCLR.Editor.AOT
|
||||||
public AssemblyReferenceDeepCollector Collector { get; set; }
|
public AssemblyReferenceDeepCollector Collector { get; set; }
|
||||||
|
|
||||||
public int MaxIterationCount { get; set; }
|
public int MaxIterationCount { get; set; }
|
||||||
|
|
||||||
|
public bool ComputeAotAssembly { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly int _maxInterationCount;
|
private readonly int _maxInterationCount;
|
||||||
|
|
||||||
private readonly AssemblyReferenceDeepCollector _assemblyCollector;
|
private readonly AssemblyReferenceDeepCollector _assemblyCollector;
|
||||||
|
|
||||||
|
private readonly bool _computeAotAssembly;
|
||||||
|
|
||||||
private readonly HashSet<GenericClass> _genericTypes = new HashSet<GenericClass>();
|
private readonly HashSet<GenericClass> _genericTypes = new HashSet<GenericClass>();
|
||||||
private readonly HashSet<GenericMethod> _genericMethods = new HashSet<GenericMethod>();
|
private readonly HashSet<GenericMethod> _genericMethods = new HashSet<GenericMethod>();
|
||||||
|
|
||||||
|
|
@ -47,6 +51,7 @@ namespace HybridCLR.Editor.AOT
|
||||||
{
|
{
|
||||||
_assemblyCollector = options.Collector;
|
_assemblyCollector = options.Collector;
|
||||||
_maxInterationCount = options.MaxIterationCount;
|
_maxInterationCount = options.MaxIterationCount;
|
||||||
|
_computeAotAssembly = options.ComputeAotAssembly;
|
||||||
_methodReferenceAnalyzer = new MethodReferenceAnalyzer(this.OnNewMethod);
|
_methodReferenceAnalyzer = new MethodReferenceAnalyzer(this.OnNewMethod);
|
||||||
_hotUpdateAssemblyFiles = new HashSet<string>(options.Collector.GetRootAssemblyNames().Select(assName => assName + ".dll"));
|
_hotUpdateAssemblyFiles = new HashSet<string>(options.Collector.GetRootAssemblyNames().Select(assName => assName + ".dll"));
|
||||||
}
|
}
|
||||||
|
|
@ -71,7 +76,7 @@ namespace HybridCLR.Editor.AOT
|
||||||
|
|
||||||
private bool IsAotType(TypeDef type)
|
private bool IsAotType(TypeDef type)
|
||||||
{
|
{
|
||||||
return !_hotUpdateAssemblyFiles.Contains(type.Module.Name);
|
return _computeAotAssembly || !_hotUpdateAssemblyFiles.Contains(type.Module.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsAotGenericMethod(MethodDef method)
|
private bool IsAotGenericMethod(MethodDef method)
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ namespace HybridCLR.Editor.AOT
|
||||||
{
|
{
|
||||||
_typeSimpleNameMapping.Add(e.Key.FullName, e.Value);
|
_typeSimpleNameMapping.Add(e.Key.FullName, e.Value);
|
||||||
}
|
}
|
||||||
_systemTypePattern = new Regex(string.Join("|", _typeSimpleNameMapping.Keys.Select (k => $@"\b{k}\b")));
|
_systemTypePattern = new Regex(string.Join("|", _typeSimpleNameMapping.Keys.Select (k => $@"\b{Regex.Escape(k)}\b")));
|
||||||
}
|
}
|
||||||
|
|
||||||
public string PrettifyTypeSig(string typeSig)
|
public string PrettifyTypeSig(string typeSig)
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ using System.Text;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using HybridCLR.Editor.Settings;
|
using HybridCLR.Editor.Settings;
|
||||||
#if UNITY_2019 && UNITY_IOS
|
#if UNITY_2019 && (UNITY_IOS || UNITY_TVOS)
|
||||||
using UnityEditor.Build;
|
using UnityEditor.Build;
|
||||||
using UnityEditor.Callbacks;
|
using UnityEditor.Callbacks;
|
||||||
using UnityEditor.iOS.Xcode;
|
using UnityEditor.iOS.Xcode;
|
||||||
|
|
@ -17,22 +17,10 @@ namespace HybridCLR.Editor.BuildProcessors
|
||||||
{
|
{
|
||||||
public static class AddLil2cppSourceCodeToXcodeproj2019
|
public static class AddLil2cppSourceCodeToXcodeproj2019
|
||||||
{
|
{
|
||||||
//[MenuItem("Test/GenProj")]
|
|
||||||
//public static void Modify()
|
|
||||||
//{
|
|
||||||
// OnPostProcessBuild(BuildTarget.iOS, $"{SettingsUtil.ProjectDir}/Build-iOS");
|
|
||||||
//}
|
|
||||||
|
|
||||||
//[MenuItem("Test/CreateLumps")]
|
|
||||||
//public static void CreateLumpsCmd()
|
|
||||||
//{
|
|
||||||
// CreateLumps($"{SettingsUtil.LocalIl2CppDir}/libil2cpp", $"{SettingsUtil.HybridCLRDataDir}/lumps");
|
|
||||||
//}
|
|
||||||
|
|
||||||
[PostProcessBuild]
|
[PostProcessBuild]
|
||||||
public static void OnPostProcessBuild(BuildTarget target, string pathToBuiltProject)
|
public static void OnPostProcessBuild(BuildTarget target, string pathToBuiltProject)
|
||||||
{
|
{
|
||||||
if (target != BuildTarget.iOS || !HybridCLRSettings.Instance.enable)
|
if (!HybridCLRSettings.Instance.enable)
|
||||||
return;
|
return;
|
||||||
/*
|
/*
|
||||||
* 1. 生成lump,并且添加到工程
|
* 1. 生成lump,并且添加到工程
|
||||||
|
|
@ -47,7 +35,7 @@ namespace HybridCLR.Editor.BuildProcessors
|
||||||
10. add "#include <stdio.h>" to Classes/Prefix.pch
|
10. add "#include <stdio.h>" to Classes/Prefix.pch
|
||||||
*/
|
*/
|
||||||
|
|
||||||
string pbxprojFile = $"{pathToBuiltProject}/Unity-iPhone.xcodeproj/project.pbxproj";
|
string pbxprojFile = BuildProcessorUtil.GetXcodeProjectFile(pathToBuiltProject);
|
||||||
string srcLibil2cppDir = $"{SettingsUtil.LocalIl2CppDir}/libil2cpp";
|
string srcLibil2cppDir = $"{SettingsUtil.LocalIl2CppDir}/libil2cpp";
|
||||||
string dstLibil2cppDir = $"{pathToBuiltProject}/Libraries/libil2cpp";
|
string dstLibil2cppDir = $"{pathToBuiltProject}/Libraries/libil2cpp";
|
||||||
string lumpDir = $"{pathToBuiltProject}/Libraries/lumps";
|
string lumpDir = $"{pathToBuiltProject}/Libraries/lumps";
|
||||||
|
|
@ -147,6 +135,7 @@ namespace HybridCLR.Editor.BuildProcessors
|
||||||
{
|
{
|
||||||
newPro += " $(SRCROOT)/Libraries/external/xxHash";
|
newPro += " $(SRCROOT)/Libraries/external/xxHash";
|
||||||
}
|
}
|
||||||
|
newPro += " $(SRCR00T)/Libraries/external/mono";
|
||||||
//Debug.Log($"config:{bcn} new prop:{newPro}");
|
//Debug.Log($"config:{bcn} new prop:{newPro}");
|
||||||
proj.SetBuildPropertyForConfig(configGuid, headerSearchPaths, newPro);
|
proj.SetBuildPropertyForConfig(configGuid, headerSearchPaths, newPro);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ using System.Text;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using HybridCLR.Editor.Settings;
|
using HybridCLR.Editor.Settings;
|
||||||
#if (UNITY_2020 || UNITY_2021) && UNITY_IOS
|
#if (UNITY_2020 || UNITY_2021) && (UNITY_IOS || UNITY_TVOS)
|
||||||
using UnityEditor.Build;
|
using UnityEditor.Build;
|
||||||
using UnityEditor.Callbacks;
|
using UnityEditor.Callbacks;
|
||||||
using UnityEditor.iOS.Xcode;
|
using UnityEditor.iOS.Xcode;
|
||||||
|
|
@ -17,22 +17,11 @@ namespace HybridCLR.Editor.BuildProcessors
|
||||||
{
|
{
|
||||||
public static class AddLil2cppSourceCodeToXcodeproj2020Or2021
|
public static class AddLil2cppSourceCodeToXcodeproj2020Or2021
|
||||||
{
|
{
|
||||||
//[MenuItem("Test/GenProj")]
|
|
||||||
//public static void Modify()
|
|
||||||
//{
|
|
||||||
// OnPostProcessBuild(BuildTarget.iOS, $"{SettingsUtil.ProjectDir}/Build-iOS");
|
|
||||||
//}
|
|
||||||
|
|
||||||
//[MenuItem("Test/CreateLumps")]
|
|
||||||
//public static void CreateLumpsCmd()
|
|
||||||
//{
|
|
||||||
// CreateLumps($"{SettingsUtil.LocalIl2CppDir}/libil2cpp", $"{SettingsUtil.HybridCLRDataDir}/lumps");
|
|
||||||
//}
|
|
||||||
|
|
||||||
[PostProcessBuild]
|
[PostProcessBuild]
|
||||||
public static void OnPostProcessBuild(BuildTarget target, string pathToBuiltProject)
|
public static void OnPostProcessBuild(BuildTarget target, string pathToBuiltProject)
|
||||||
{
|
{
|
||||||
if (target != BuildTarget.iOS || !HybridCLRSettings.Instance.enable)
|
if (!HybridCLRSettings.Instance.enable)
|
||||||
return;
|
return;
|
||||||
/*
|
/*
|
||||||
* 1. 生成lump,并且添加到工程
|
* 1. 生成lump,并且添加到工程
|
||||||
|
|
@ -46,7 +35,7 @@ namespace HybridCLR.Editor.BuildProcessors
|
||||||
9. add external/xxHash
|
9. add external/xxHash
|
||||||
*/
|
*/
|
||||||
|
|
||||||
string pbxprojFile = $"{pathToBuiltProject}/Unity-iPhone.xcodeproj/project.pbxproj";
|
string pbxprojFile = BuildProcessorUtil.GetXcodeProjectFile(pathToBuiltProject);
|
||||||
string srcLibil2cppDir = $"{SettingsUtil.LocalIl2CppDir}/libil2cpp";
|
string srcLibil2cppDir = $"{SettingsUtil.LocalIl2CppDir}/libil2cpp";
|
||||||
string dstLibil2cppDir = $"{pathToBuiltProject}/Libraries/libil2cpp";
|
string dstLibil2cppDir = $"{pathToBuiltProject}/Libraries/libil2cpp";
|
||||||
string lumpDir = $"{pathToBuiltProject}/Libraries/lumps";
|
string lumpDir = $"{pathToBuiltProject}/Libraries/lumps";
|
||||||
|
|
@ -132,6 +121,7 @@ namespace HybridCLR.Editor.BuildProcessors
|
||||||
{
|
{
|
||||||
newPro += " $(SRCROOT)/Libraries/external/xxHash";
|
newPro += " $(SRCROOT)/Libraries/external/xxHash";
|
||||||
}
|
}
|
||||||
|
newPro += " $(SRCR00T)/Libraries/external/mono";
|
||||||
//Debug.Log($"config:{bcn} new prop:{newPro}");
|
//Debug.Log($"config:{bcn} new prop:{newPro}");
|
||||||
proj.SetBuildPropertyForConfig(configGuid, headerSearchPaths, newPro);
|
proj.SetBuildPropertyForConfig(configGuid, headerSearchPaths, newPro);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,47 +2,72 @@ using HybridCLR.Editor.Installer;
|
||||||
using HybridCLR.Editor.Settings;
|
using HybridCLR.Editor.Settings;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEditor.Build;
|
using UnityEditor.Build;
|
||||||
using UnityEditor.Callbacks;
|
using UnityEditor.Callbacks;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
#if UNITY_2022_2_OR_NEWER && UNITY_IOS
|
#if UNITY_2022 && (UNITY_IOS || UNITY_TVOS || UNITY_VISIONOS)
|
||||||
|
|
||||||
namespace HybridCLR.Editor.BuildProcessors
|
namespace HybridCLR.Editor.BuildProcessors
|
||||||
{
|
{
|
||||||
public static class AddLil2cppSourceCodeToXcodeproj2022OrNewer
|
public static class AddLil2cppSourceCodeToXcodeproj2022OrNewer
|
||||||
{
|
{
|
||||||
//[MenuItem("HybridCLR/Modfiyxcode")]
|
|
||||||
//public static void Modify()
|
|
||||||
//{
|
|
||||||
// OnPostProcessBuild(BuildTarget.iOS, $"{SettingsUtil.ProjectDir}/Build-iOS");
|
|
||||||
//}
|
|
||||||
|
|
||||||
[PostProcessBuild]
|
[PostProcessBuild]
|
||||||
public static void OnPostProcessBuild(BuildTarget target, string pathToBuiltProject)
|
public static void OnPostProcessBuild(BuildTarget target, string pathToBuiltProject)
|
||||||
{
|
{
|
||||||
if (target != BuildTarget.iOS || !HybridCLRSettings.Instance.enable)
|
if (!HybridCLRSettings.Instance.enable)
|
||||||
return;
|
return;
|
||||||
|
string pbxprojFile = BuildProcessorUtil.GetXcodeProjectFile(pathToBuiltProject);
|
||||||
string pbxprojFile = $"{pathToBuiltProject}/Unity-iPhone.xcodeproj/project.pbxproj";
|
|
||||||
RemoveExternalLibil2cppOption(pbxprojFile);
|
RemoveExternalLibil2cppOption(pbxprojFile);
|
||||||
CopyLibil2cppToXcodeProj(pathToBuiltProject);
|
CopyLibil2cppToXcodeProj(pathToBuiltProject);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string TryRemoveDunplicateShellScriptSegment(string pbxprojFile, string pbxprojContent)
|
||||||
|
{
|
||||||
|
// will appear duplicated Shell Script segment when append to existed xcode project.
|
||||||
|
// This is unity bug.
|
||||||
|
// we remove duplicated Shell Script to avoid build error.
|
||||||
|
string copyFileComment = @"/\* CopyFiles \*/,\s+([A-Z0-9]{24}) /\* ShellScript \*/,\s+([A-Z0-9]{24}) /\* ShellScript \*/,";
|
||||||
|
var m = Regex.Match(pbxprojContent, copyFileComment, RegexOptions.Multiline);
|
||||||
|
if (!m.Success)
|
||||||
|
{
|
||||||
|
return pbxprojContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m.Groups[1].Value != m.Groups[2].Value)
|
||||||
|
{
|
||||||
|
throw new BuildFailedException($"find invalid /* ShellScript */ segment");
|
||||||
|
}
|
||||||
|
|
||||||
|
int startIndexOfDupShellScript = m.Groups[2].Index;
|
||||||
|
int endIndexOfDupShellScript = pbxprojContent.IndexOf(",", startIndexOfDupShellScript);
|
||||||
|
|
||||||
|
pbxprojContent = pbxprojContent.Remove(startIndexOfDupShellScript, endIndexOfDupShellScript + 1 - startIndexOfDupShellScript);
|
||||||
|
Debug.LogWarning($"[AddLil2cppSourceCodeToXcodeproj] remove duplicated '/* ShellScript */' from file '{pbxprojFile}'");
|
||||||
|
return pbxprojContent;
|
||||||
|
}
|
||||||
|
|
||||||
private static void RemoveExternalLibil2cppOption(string pbxprojFile)
|
private static void RemoveExternalLibil2cppOption(string pbxprojFile)
|
||||||
{
|
{
|
||||||
string pbxprojContent = File.ReadAllText(pbxprojFile, Encoding.UTF8);
|
string pbxprojContent = File.ReadAllText(pbxprojFile, Encoding.UTF8);
|
||||||
string removeBuildOption = @"--external-lib-il2-cpp=\""$PROJECT_DIR/Libraries/libil2cpp.a\""";
|
string removeBuildOption = @"--external-lib-il2-cpp=\""$PROJECT_DIR/Libraries/libil2cpp.a\""";
|
||||||
if (!pbxprojContent.Contains(removeBuildOption))
|
if (pbxprojContent.Contains(removeBuildOption))
|
||||||
{
|
{
|
||||||
//throw new BuildFailedException("modified project.pbxproj fail");
|
pbxprojContent = pbxprojContent.Replace(removeBuildOption, "");
|
||||||
Debug.LogError("[AddLil2cppSourceCodeToXcodeproj] modified project.pbxproj fail");
|
Debug.Log($"[AddLil2cppSourceCodeToXcodeproj] remove il2cpp build option '{removeBuildOption}' from file '{pbxprojFile}'");
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
pbxprojContent = pbxprojContent.Replace(removeBuildOption, "");
|
else
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[AddLil2cppSourceCodeToXcodeproj] project.pbxproj remove building option:'{removeBuildOption}' fail. This may occur when 'Append' to existing xcode project in building");
|
||||||
|
}
|
||||||
|
|
||||||
|
pbxprojContent = TryRemoveDunplicateShellScriptSegment(pbxprojFile, pbxprojContent);
|
||||||
|
|
||||||
|
|
||||||
File.WriteAllText(pbxprojFile, pbxprojContent, Encoding.UTF8);
|
File.WriteAllText(pbxprojFile, pbxprojContent, Encoding.UTF8);
|
||||||
Debug.Log($"[AddLil2cppSourceCodeToXcodeproj] remove il2cpp build option '{removeBuildOption}' from file '{pbxprojFile}'");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void CopyLibil2cppToXcodeProj(string pathToBuiltProject)
|
private static void CopyLibil2cppToXcodeProj(string pathToBuiltProject)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
using HybridCLR.Editor.Installer;
|
||||||
|
using HybridCLR.Editor.Settings;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEditor.Build;
|
||||||
|
using UnityEditor.Callbacks;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
#if UNITY_2023_1_OR_NEWER && (UNITY_IOS || UNITY_TVOS || UNITY_VISIONOS)
|
||||||
|
|
||||||
|
namespace HybridCLR.Editor.BuildProcessors
|
||||||
|
{
|
||||||
|
public static class AddLil2cppSourceCodeToXcodeproj2022OrNewer
|
||||||
|
{
|
||||||
|
|
||||||
|
[PostProcessBuild]
|
||||||
|
public static void OnPostProcessBuild(BuildTarget target, string pathToBuiltProject)
|
||||||
|
{
|
||||||
|
if (!HybridCLRSettings.Instance.enable)
|
||||||
|
return;
|
||||||
|
CopyLibil2cppToXcodeProj(pathToBuiltProject);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CopyLibil2cppToXcodeProj(string pathToBuiltProject)
|
||||||
|
{
|
||||||
|
string srcLibil2cppDir = $"{SettingsUtil.LocalIl2CppDir}/libil2cpp";
|
||||||
|
string destLibil2cppDir = $"{pathToBuiltProject}/Il2CppOutputProject/IL2CPP/libil2cpp";
|
||||||
|
BashUtil.RemoveDir(destLibil2cppDir);
|
||||||
|
BashUtil.CopyDir(srcLibil2cppDir, destLibil2cppDir, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 2fa46135129b046a28014d58fdfd18ca
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using UnityEditor.Build;
|
||||||
|
|
||||||
|
namespace HybridCLR.Editor.BuildProcessors
|
||||||
|
{
|
||||||
|
|
||||||
|
public static class BuildProcessorUtil
|
||||||
|
{
|
||||||
|
|
||||||
|
public static string GetXcodeProjectFile(string pathToBuiltProject)
|
||||||
|
{
|
||||||
|
foreach (string dir in Directory.GetDirectories(pathToBuiltProject, "*.xcodeproj", SearchOption.TopDirectoryOnly))
|
||||||
|
{
|
||||||
|
string pbxprojFile = $"{dir}/project.pbxproj";
|
||||||
|
if (File.Exists(pbxprojFile))
|
||||||
|
{
|
||||||
|
return pbxprojFile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new BuildFailedException($"can't find xxxx.xcodeproj/project.pbxproj in {pathToBuiltProject}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 7d883f182f206fa4db31f4085ce0ecdc
|
guid: c680e56f90f2745298a90803c04f6efc
|
||||||
MonoImporter:
|
MonoImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
using HybridCLR.Editor.Settings;
|
using HybridCLR.Editor.Settings;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEditor.Build;
|
using UnityEditor.Build;
|
||||||
|
|
@ -16,6 +18,8 @@ namespace HybridCLR.Editor.BuildProcessors
|
||||||
{
|
{
|
||||||
public int callbackOrder => 0;
|
public int callbackOrder => 0;
|
||||||
|
|
||||||
|
public static bool DisableMethodBridgeDevelopmentFlagChecking { get; set; }
|
||||||
|
|
||||||
public void OnPreprocessBuild(BuildReport report)
|
public void OnPreprocessBuild(BuildReport report)
|
||||||
{
|
{
|
||||||
HybridCLRSettings globalSettings = SettingsUtil.HybridCLRSettings;
|
HybridCLRSettings globalSettings = SettingsUtil.HybridCLRSettings;
|
||||||
|
|
@ -67,6 +71,24 @@ namespace HybridCLR.Editor.BuildProcessors
|
||||||
Debug.LogWarning("[CheckSettings] No hot update modules configured in HybridCLRSettings");
|
Debug.LogWarning("[CheckSettings] No hot update modules configured in HybridCLRSettings");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!DisableMethodBridgeDevelopmentFlagChecking)
|
||||||
|
{
|
||||||
|
string methodBridgeFile = $"{SettingsUtil.GeneratedCppDir}/MethodBridge.cpp";
|
||||||
|
var match = Regex.Match(File.ReadAllText(methodBridgeFile), @"// DEVELOPMENT=(\d)");
|
||||||
|
if (match.Success)
|
||||||
|
{
|
||||||
|
int developmentFlagInMethodBridge = int.Parse(match.Groups[1].Value);
|
||||||
|
int developmentFlagInEditorSettings = EditorUserBuildSettings.development ? 1 : 0;
|
||||||
|
if (developmentFlagInMethodBridge != developmentFlagInEditorSettings)
|
||||||
|
{
|
||||||
|
Debug.LogError($"[CheckSettings] MethodBridge.cpp DEVELOPMENT flag:{developmentFlagInMethodBridge} is inconsistent with EditorUserBuildSettings.development:{developmentFlagInEditorSettings}. Please run 'HybridCLR/Generate/All' before building.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.LogError("[CheckSettings] MethodBridge.cpp DEVELOPMENT flag not found. Please run 'HybridCLR/Generate/All' before building.");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,11 @@ using System.Threading.Tasks;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEditor.Build;
|
using UnityEditor.Build;
|
||||||
using UnityEditor.Build.Reporting;
|
using UnityEditor.Build.Reporting;
|
||||||
using UnityEditor.Il2Cpp;
|
|
||||||
using UnityEditor.UnityLinker;
|
using UnityEditor.UnityLinker;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
#if !UNITY_2021_1_OR_NEWER
|
||||||
|
using UnityEditor.Il2Cpp;
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace HybridCLR.Editor.BuildProcessors
|
namespace HybridCLR.Editor.BuildProcessors
|
||||||
{
|
{
|
||||||
|
|
@ -33,11 +35,28 @@ namespace HybridCLR.Editor.BuildProcessors
|
||||||
return $"{projectDir}/Library/Bee/artifacts/WinPlayerBuildProgram/ManagedStripped";
|
return $"{projectDir}/Library/Bee/artifacts/WinPlayerBuildProgram/ManagedStripped";
|
||||||
case BuildTarget.StandaloneLinux64:
|
case BuildTarget.StandaloneLinux64:
|
||||||
return $"{projectDir}/Library/Bee/artifacts/LinuxPlayerBuildProgram/ManagedStripped";
|
return $"{projectDir}/Library/Bee/artifacts/LinuxPlayerBuildProgram/ManagedStripped";
|
||||||
|
case BuildTarget.WSAPlayer:
|
||||||
|
return $"{projectDir}/Library/Bee/artifacts/UWPPlayerBuildProgram/ManagedStripped";
|
||||||
case BuildTarget.Android:
|
case BuildTarget.Android:
|
||||||
return $"{projectDir}/Library/Bee/artifacts/Android/ManagedStripped";
|
return $"{projectDir}/Library/Bee/artifacts/Android/ManagedStripped";
|
||||||
|
#if TUANJIE_2022_3_OR_NEWER
|
||||||
|
case BuildTarget.HMIAndroid:
|
||||||
|
return $"{projectDir}/Library/Bee/artifacts/HMIAndroid/ManagedStripped";
|
||||||
|
#endif
|
||||||
case BuildTarget.iOS:
|
case BuildTarget.iOS:
|
||||||
return $"{projectDir}/Library/Bee/artifacts/iOS/ManagedStripped";
|
#if UNITY_TVOS
|
||||||
case BuildTarget.WebGL:
|
case BuildTarget.tvOS:
|
||||||
|
#endif
|
||||||
|
return $"{projectDir}/Library/Bee/artifacts/iOS/ManagedStripped";
|
||||||
|
#if UNITY_VISIONOS
|
||||||
|
case BuildTarget.VisionOS:
|
||||||
|
#if UNITY_6000_0_OR_NEWER
|
||||||
|
return $"{projectDir}/Library/Bee/artifacts/VisionOS/ManagedStripped";
|
||||||
|
#else
|
||||||
|
return $"{projectDir}/Library/Bee/artifacts/iOS/ManagedStripped";
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
case BuildTarget.WebGL:
|
||||||
return $"{projectDir}/Library/Bee/artifacts/WebGL/ManagedStripped";
|
return $"{projectDir}/Library/Bee/artifacts/WebGL/ManagedStripped";
|
||||||
case BuildTarget.StandaloneOSX:
|
case BuildTarget.StandaloneOSX:
|
||||||
return $"{projectDir}/Library/Bee/artifacts/MacStandalonePlayerBuildProgram/ManagedStripped";
|
return $"{projectDir}/Library/Bee/artifacts/MacStandalonePlayerBuildProgram/ManagedStripped";
|
||||||
|
|
@ -45,9 +64,11 @@ namespace HybridCLR.Editor.BuildProcessors
|
||||||
return $"{projectDir}/Library/Bee/artifacts/PS4PlayerBuildProgram/ManagedStripped";
|
return $"{projectDir}/Library/Bee/artifacts/PS4PlayerBuildProgram/ManagedStripped";
|
||||||
case BuildTarget.PS5:
|
case BuildTarget.PS5:
|
||||||
return $"{projectDir}/Library/Bee/artifacts/PS5PlayerBuildProgram/ManagedStripped";
|
return $"{projectDir}/Library/Bee/artifacts/PS5PlayerBuildProgram/ManagedStripped";
|
||||||
#if TUANJIE_2022
|
#if UNITY_WEIXINMINIGAME
|
||||||
case BuildTarget.WeixinMiniGame:
|
case BuildTarget.WeixinMiniGame:
|
||||||
return $"{projectDir}/Library/Bee/artifacts/WeixinMiniGame/ManagedStripped";
|
return $"{projectDir}/Library/Bee/artifacts/WeixinMiniGame/ManagedStripped";
|
||||||
|
#endif
|
||||||
|
#if UNITY_OPENHARMONY
|
||||||
case BuildTarget.OpenHarmony:
|
case BuildTarget.OpenHarmony:
|
||||||
return $"{projectDir}/Library/Bee/artifacts/OpenHarmonyPlayerBuildProgram/ManagedStripped";
|
return $"{projectDir}/Library/Bee/artifacts/OpenHarmonyPlayerBuildProgram/ManagedStripped";
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -65,8 +86,8 @@ namespace HybridCLR.Editor.BuildProcessors
|
||||||
|
|
||||||
public void OnBeforeConvertRun(BuildReport report, Il2CppBuildPipelineData data)
|
public void OnBeforeConvertRun(BuildReport report, Il2CppBuildPipelineData data)
|
||||||
{
|
{
|
||||||
// 此回调只在 2020中调用
|
BuildTarget target = report.summary.platform;
|
||||||
CopyStripDlls(GetStripAssembliesDir2020(data.target), data.target);
|
CopyStripDlls(GetStripAssembliesDir2020(target), target);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
@ -94,7 +115,7 @@ namespace HybridCLR.Editor.BuildProcessors
|
||||||
public void OnPostprocessBuild(BuildReport report)
|
public void OnPostprocessBuild(BuildReport report)
|
||||||
{
|
{
|
||||||
#if UNITY_2021_1_OR_NEWER
|
#if UNITY_2021_1_OR_NEWER
|
||||||
BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
|
BuildTarget target = report.summary.platform;
|
||||||
string srcStripDllPath = GetStripAssembliesDir2021(target);
|
string srcStripDllPath = GetStripAssembliesDir2021(target);
|
||||||
if (!string.IsNullOrEmpty(srcStripDllPath) && Directory.Exists(srcStripDllPath))
|
if (!string.IsNullOrEmpty(srcStripDllPath) && Directory.Exists(srcStripDllPath))
|
||||||
{
|
{
|
||||||
|
|
@ -105,7 +126,7 @@ namespace HybridCLR.Editor.BuildProcessors
|
||||||
|
|
||||||
public void OnPreprocessBuild(BuildReport report)
|
public void OnPreprocessBuild(BuildReport report)
|
||||||
{
|
{
|
||||||
BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
|
BuildTarget target = report.summary.platform;
|
||||||
var dstPath = SettingsUtil.GetAssembliesPostIl2CppStripDir(target);
|
var dstPath = SettingsUtil.GetAssembliesPostIl2CppStripDir(target);
|
||||||
BashUtil.RecreateDir(dstPath);
|
BashUtil.RecreateDir(dstPath);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,12 @@ using UnityEditor;
|
||||||
using UnityEditor.Android;
|
using UnityEditor.Android;
|
||||||
using UnityEditor.Build;
|
using UnityEditor.Build;
|
||||||
using UnityEditor.Build.Reporting;
|
using UnityEditor.Build.Reporting;
|
||||||
using UnityEditor.Il2Cpp;
|
|
||||||
using UnityEditor.UnityLinker;
|
using UnityEditor.UnityLinker;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityFS;
|
using UnityFS;
|
||||||
|
#if !UNITY_2023_1_OR_NEWER
|
||||||
|
using UnityEditor.Il2Cpp;
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace HybridCLR.Editor.BuildProcessors
|
namespace HybridCLR.Editor.BuildProcessors
|
||||||
{
|
{
|
||||||
|
|
@ -177,7 +179,7 @@ namespace HybridCLR.Editor.BuildProcessors
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if UNITY_WEBGL
|
#if UNITY_WEBGL && !UNITY_2022_3_OR_NEWER
|
||||||
public void OnBeforeConvertRun(BuildReport report, Il2CppBuildPipelineData data)
|
public void OnBeforeConvertRun(BuildReport report, Il2CppBuildPipelineData data)
|
||||||
{
|
{
|
||||||
PathScriptingAssembilesFile($"{SettingsUtil.ProjectDir}/Temp/StagingArea/Data");
|
PathScriptingAssembilesFile($"{SettingsUtil.ProjectDir}/Temp/StagingArea/Data");
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
using HybridCLR.Editor.Meta;
|
using HybridCLR.Editor.Meta;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
@ -22,25 +23,110 @@ namespace HybridCLR.Editor.Commands
|
||||||
GenerateAOTGenericReference(target);
|
GenerateAOTGenericReference(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 计算热更代码中的泛型引用
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="target"></param>
|
||||||
public static void GenerateAOTGenericReference(BuildTarget target)
|
public static void GenerateAOTGenericReference(BuildTarget target)
|
||||||
{
|
{
|
||||||
var gs = SettingsUtil.HybridCLRSettings;
|
var gs = SettingsUtil.HybridCLRSettings;
|
||||||
List<string> hotUpdateDllNames = SettingsUtil.HotUpdateAssemblyNamesExcludePreserved;
|
List<string> hotUpdateDllNames = SettingsUtil.HotUpdateAssemblyNamesExcludePreserved;
|
||||||
|
|
||||||
using (AssemblyReferenceDeepCollector collector = new AssemblyReferenceDeepCollector(MetaUtil.CreateHotUpdateAndAOTAssemblyResolver(target, hotUpdateDllNames), hotUpdateDllNames))
|
AssemblyReferenceDeepCollector collector = new AssemblyReferenceDeepCollector(MetaUtil.CreateHotUpdateAndAOTAssemblyResolver(target, hotUpdateDllNames), hotUpdateDllNames);
|
||||||
|
var analyzer = new Analyzer(new Analyzer.Options
|
||||||
{
|
{
|
||||||
var analyzer = new Analyzer(new Analyzer.Options
|
MaxIterationCount = Math.Min(20, gs.maxGenericReferenceIteration),
|
||||||
{
|
Collector = collector,
|
||||||
MaxIterationCount = Math.Min(20, gs.maxGenericReferenceIteration),
|
});
|
||||||
Collector = collector,
|
|
||||||
});
|
|
||||||
|
|
||||||
analyzer.Run();
|
analyzer.Run();
|
||||||
|
|
||||||
var writer = new GenericReferenceWriter();
|
var writer = new GenericReferenceWriter();
|
||||||
writer.Write(analyzer.AotGenericTypes.ToList(), analyzer.AotGenericMethods.ToList(), $"{Application.dataPath}/{gs.outputAOTGenericReferenceFile}");
|
writer.Write(analyzer.AotGenericTypes.ToList(), analyzer.AotGenericMethods.ToList(), $"{Application.dataPath}/{gs.outputAOTGenericReferenceFile}");
|
||||||
AssetDatabase.Refresh();
|
AssetDatabase.Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//[MenuItem("HybridCLR/Generate/AOTGenericReference2", priority = 103)]
|
||||||
|
//public static void GeneratedAOTGenericReferenceExcludeExists()
|
||||||
|
//{
|
||||||
|
// GeneratedAOTGenericReferenceExcludeExists(EditorUserBuildSettings.activeBuildTarget);
|
||||||
|
//}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 计算热更新代码中的泛型引用,但排除AOT已经存在的泛型引用
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="target"></param>
|
||||||
|
///
|
||||||
|
public static void GeneratedAOTGenericReferenceExcludeExistsAOTClassAndMethods(BuildTarget target)
|
||||||
|
{
|
||||||
|
|
||||||
|
var gs = SettingsUtil.HybridCLRSettings;
|
||||||
|
List<string> hotUpdateDllNames = SettingsUtil.HotUpdateAssemblyNamesExcludePreserved;
|
||||||
|
|
||||||
|
AssemblyReferenceDeepCollector hotUpdateCollector = new AssemblyReferenceDeepCollector(MetaUtil.CreateHotUpdateAndAOTAssemblyResolver(target, hotUpdateDllNames), hotUpdateDllNames);
|
||||||
|
var hotUpdateAnalyzer = new Analyzer(new Analyzer.Options
|
||||||
|
{
|
||||||
|
MaxIterationCount = Math.Min(10, gs.maxGenericReferenceIteration),
|
||||||
|
Collector = hotUpdateCollector,
|
||||||
|
});
|
||||||
|
|
||||||
|
hotUpdateAnalyzer.Run();
|
||||||
|
|
||||||
|
|
||||||
|
string aotDllDir = SettingsUtil.GetAssembliesPostIl2CppStripDir(target);
|
||||||
|
List<string> aotAssemblyNames = Directory.Exists(aotDllDir) ?
|
||||||
|
Directory.GetFiles(aotDllDir, "*.dll", SearchOption.TopDirectoryOnly).Select(Path.GetFileNameWithoutExtension).ToList()
|
||||||
|
: new List<string>();
|
||||||
|
if (aotAssemblyNames.Count == 0)
|
||||||
|
{
|
||||||
|
throw new Exception($"no aot assembly found. please run `HybridCLR/Generate/All` or `HybridCLR/Generate/AotDlls` to generate aot dlls before runing `HybridCLR/Generate/AOTGenericReference`");
|
||||||
}
|
}
|
||||||
|
AssemblyReferenceDeepCollector aotCollector = new AssemblyReferenceDeepCollector(MetaUtil.CreateAOTAssemblyResolver(target), aotAssemblyNames);
|
||||||
|
var aotAnalyzer = new Analyzer(new Analyzer.Options
|
||||||
|
{
|
||||||
|
MaxIterationCount = Math.Min(10, gs.maxGenericReferenceIteration),
|
||||||
|
Collector = aotCollector,
|
||||||
|
ComputeAotAssembly = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
aotAnalyzer.Run();
|
||||||
|
|
||||||
|
var (resultTypes, resultMethods) = ExcludeExistAOTGenericTypeAndMethodss(hotUpdateAnalyzer.AotGenericTypes.ToList(), hotUpdateAnalyzer.AotGenericMethods.ToList(), aotAnalyzer.AotGenericTypes.ToList(), aotAnalyzer.AotGenericMethods.ToList());
|
||||||
|
var writer = new GenericReferenceWriter();
|
||||||
|
writer.Write(resultTypes, resultMethods, $"{Application.dataPath}/{gs.outputAOTGenericReferenceFile}");
|
||||||
|
AssetDatabase.Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static (List<GenericClass>, List<GenericMethod>) ExcludeExistAOTGenericTypeAndMethodss(List<GenericClass> hotUpdateTypes, List<GenericMethod> hotUpdateMethods, List<GenericClass> aotTypes, List<GenericMethod> aotMethods)
|
||||||
|
{
|
||||||
|
var types = new List<GenericClass>();
|
||||||
|
|
||||||
|
var typeSig2Type = hotUpdateTypes.ToDictionary(t => t.Type.DefinitionAssembly.Name + ":" + t.ToTypeSig(), t => t);
|
||||||
|
foreach (var t in aotTypes)
|
||||||
|
{
|
||||||
|
string key = t.Type.DefinitionAssembly.Name + ":" + t.ToTypeSig();
|
||||||
|
if (typeSig2Type.TryGetValue(key, out var removedType))
|
||||||
|
{
|
||||||
|
typeSig2Type.Remove(key);
|
||||||
|
Debug.Log($"remove AOT type:{removedType.ToTypeSig()} ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var methodSig2Method = hotUpdateMethods.ToDictionary(m => m.Method.DeclaringType.DefinitionAssembly.Name + ":" + m.ToMethodSpec().ToString(), m => m);
|
||||||
|
foreach (var m in aotMethods)
|
||||||
|
{
|
||||||
|
string key = m.Method.DeclaringType.DefinitionAssembly.Name + ":" + m.ToMethodSpec().ToString();
|
||||||
|
if (methodSig2Method.TryGetValue(key, out var removedMethod))
|
||||||
|
{
|
||||||
|
methodSig2Method.Remove(key);
|
||||||
|
Debug.Log($"remove AOT method:{removedMethod.ToMethodSpec()} ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (typeSig2Type.Values.ToList(), methodSig2Method.Values.ToList());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,10 +25,15 @@ namespace HybridCLR.Editor.Commands
|
||||||
#if UNITY_2022
|
#if UNITY_2022
|
||||||
UnityEditor.EditorUtility.ClearProgressBar();
|
UnityEditor.EditorUtility.ClearProgressBar();
|
||||||
#endif
|
#endif
|
||||||
Debug.Log("compile finish!!!");
|
Debug.Log($"compile finish!!! buildDir:{buildDir} target:{target} development:{developmentBuild}");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void CompileDll(BuildTarget target, bool developmentBuild = false)
|
public static void CompileDll(BuildTarget target)
|
||||||
|
{
|
||||||
|
CompileDll(target, EditorUserBuildSettings.development);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void CompileDll(BuildTarget target, bool developmentBuild)
|
||||||
{
|
{
|
||||||
CompileDll(SettingsUtil.GetHotUpdateDllsOutputDirByTarget(target), target, developmentBuild);
|
CompileDll(SettingsUtil.GetHotUpdateDllsOutputDirByTarget(target), target, developmentBuild);
|
||||||
}
|
}
|
||||||
|
|
@ -36,10 +41,16 @@ namespace HybridCLR.Editor.Commands
|
||||||
[MenuItem("HybridCLR/CompileDll/ActiveBuildTarget", priority = 100)]
|
[MenuItem("HybridCLR/CompileDll/ActiveBuildTarget", priority = 100)]
|
||||||
public static void CompileDllActiveBuildTarget()
|
public static void CompileDllActiveBuildTarget()
|
||||||
{
|
{
|
||||||
CompileDll(EditorUserBuildSettings.activeBuildTarget);
|
CompileDll(EditorUserBuildSettings.activeBuildTarget, EditorUserBuildSettings.development);
|
||||||
}
|
}
|
||||||
|
|
||||||
[MenuItem("HybridCLR/CompileDll/ActiveBuildTarget_Development", priority = 101)]
|
[MenuItem("HybridCLR/CompileDll/ActiveBuildTarget_Release", priority = 102)]
|
||||||
|
public static void CompileDllActiveBuildTargetRelease()
|
||||||
|
{
|
||||||
|
CompileDll(EditorUserBuildSettings.activeBuildTarget, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MenuItem("HybridCLR/CompileDll/ActiveBuildTarget_Development", priority = 104)]
|
||||||
public static void CompileDllActiveBuildTargetDevelopment()
|
public static void CompileDllActiveBuildTargetDevelopment()
|
||||||
{
|
{
|
||||||
CompileDll(EditorUserBuildSettings.activeBuildTarget, true);
|
CompileDll(EditorUserBuildSettings.activeBuildTarget, true);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using HybridCLR.Editor.Link;
|
using HybridCLR.Editor.Link;
|
||||||
|
using HybridCLR.Editor.Settings;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
@ -19,8 +20,10 @@ namespace HybridCLR.Editor.Commands
|
||||||
{
|
{
|
||||||
UnityVersion = Application.unityVersion,
|
UnityVersion = Application.unityVersion,
|
||||||
HotUpdateAssemblies = SettingsUtil.HotUpdateAssemblyNamesIncludePreserved,
|
HotUpdateAssemblies = SettingsUtil.HotUpdateAssemblyNamesIncludePreserved,
|
||||||
OutputFile = $"{SettingsUtil.LocalIl2CppDir}/libil2cpp/hybridclr/generated/UnityVersion.h",
|
UnityVersionTemplateFile = $"{SettingsUtil.TemplatePathInPackage}/UnityVersion.h.tpl",
|
||||||
OutputFile2 = $"{SettingsUtil.LocalIl2CppDir}/libil2cpp/hybridclr/generated/AssemblyManifest.cpp",
|
UnityVersionOutputFile = $"{SettingsUtil.LocalIl2CppDir}/libil2cpp/hybridclr/generated/UnityVersion.h",
|
||||||
|
AssemblyManifestTemplateFile = $"{SettingsUtil.TemplatePathInPackage}/AssemblyManifest.cpp.tpl",
|
||||||
|
AssemblyManifestOutputFile = $"{SettingsUtil.LocalIl2CppDir}/libil2cpp/hybridclr/generated/AssemblyManifest.cpp",
|
||||||
};
|
};
|
||||||
|
|
||||||
var g = new Il2CppDef.Il2CppDefGenerator(options);
|
var g = new Il2CppDef.Il2CppDefGenerator(options);
|
||||||
|
|
|
||||||
|
|
@ -30,29 +30,31 @@ namespace HybridCLR.Editor.Commands
|
||||||
Directory.Delete(il2cppBuildCachePath, true);
|
Directory.Delete(il2cppBuildCachePath, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void GenerateMethodBridgeCppFile(Analyzer analyzer, string outputFile)
|
private static void GenerateMethodBridgeCppFile(IReadOnlyCollection<GenericMethod> genericMethods, List<RawMonoPInvokeCallbackMethodInfo> reversePInvokeMethods, IReadOnlyCollection<CallNativeMethodSignatureInfo> calliMethodSignatures, string tempFile, string outputFile)
|
||||||
{
|
{
|
||||||
string templateCode = File.ReadAllText(outputFile, Encoding.UTF8);
|
string templateCode = File.ReadAllText(tempFile, Encoding.UTF8);
|
||||||
var g = new Generator(new Generator.Options()
|
var g = new Generator(new Generator.Options()
|
||||||
{
|
{
|
||||||
TemplateCode = templateCode,
|
TemplateCode = templateCode,
|
||||||
OutputFile = outputFile,
|
OutputFile = outputFile,
|
||||||
GenericMethods = analyzer.GenericMethods,
|
GenericMethods = genericMethods,
|
||||||
|
ReversePInvokeMethods = reversePInvokeMethods,
|
||||||
|
CalliMethodSignatures = calliMethodSignatures,
|
||||||
|
Development = EditorUserBuildSettings.development,
|
||||||
});
|
});
|
||||||
|
|
||||||
g.PrepareMethods();
|
|
||||||
g.Generate();
|
g.Generate();
|
||||||
Debug.LogFormat("[MethodBridgeGeneratorCommand] output:{0}", outputFile);
|
Debug.LogFormat("[MethodBridgeGeneratorCommand] output:{0}", outputFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
[MenuItem("HybridCLR/Generate/MethodBridge", priority = 101)]
|
[MenuItem("HybridCLR/Generate/MethodBridgeAndReversePInvokeWrapper", priority = 101)]
|
||||||
public static void CompileAndGenerateMethodBridge()
|
public static void GenerateMethodBridgeAndReversePInvokeWrapper()
|
||||||
{
|
{
|
||||||
BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
|
BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
|
||||||
GenerateMethodBridge(target);
|
GenerateMethodBridgeAndReversePInvokeWrapper(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void GenerateMethodBridge(BuildTarget target)
|
public static void GenerateMethodBridgeAndReversePInvokeWrapper(BuildTarget target)
|
||||||
{
|
{
|
||||||
string aotDllDir = SettingsUtil.GetAssembliesPostIl2CppStripDir(target);
|
string aotDllDir = SettingsUtil.GetAssembliesPostIl2CppStripDir(target);
|
||||||
List<string> aotAssemblyNames = Directory.Exists(aotDllDir) ?
|
List<string> aotAssemblyNames = Directory.Exists(aotDllDir) ?
|
||||||
|
|
@ -62,18 +64,33 @@ namespace HybridCLR.Editor.Commands
|
||||||
{
|
{
|
||||||
throw new Exception($"no aot assembly found. please run `HybridCLR/Generate/All` or `HybridCLR/Generate/AotDlls` to generate aot dlls before runing `HybridCLR/Generate/MethodBridge`");
|
throw new Exception($"no aot assembly found. please run `HybridCLR/Generate/All` or `HybridCLR/Generate/AotDlls` to generate aot dlls before runing `HybridCLR/Generate/MethodBridge`");
|
||||||
}
|
}
|
||||||
using (AssemblyReferenceDeepCollector collector = new AssemblyReferenceDeepCollector(MetaUtil.CreateAOTAssemblyResolver(target), aotAssemblyNames))
|
AssemblyReferenceDeepCollector collector = new AssemblyReferenceDeepCollector(MetaUtil.CreateAOTAssemblyResolver(target), aotAssemblyNames);
|
||||||
{
|
|
||||||
var analyzer = new Analyzer(new Analyzer.Options
|
|
||||||
{
|
|
||||||
MaxIterationCount = Math.Min(20, SettingsUtil.HybridCLRSettings.maxMethodBridgeGenericIteration),
|
|
||||||
Collector = collector,
|
|
||||||
});
|
|
||||||
|
|
||||||
analyzer.Run();
|
var methodBridgeAnalyzer = new Analyzer(new Analyzer.Options
|
||||||
string outputFile = $"{SettingsUtil.GeneratedCppDir}/MethodBridge.cpp";
|
{
|
||||||
GenerateMethodBridgeCppFile(analyzer, outputFile);
|
MaxIterationCount = Math.Min(20, SettingsUtil.HybridCLRSettings.maxMethodBridgeGenericIteration),
|
||||||
}
|
Collector = collector,
|
||||||
|
});
|
||||||
|
|
||||||
|
methodBridgeAnalyzer.Run();
|
||||||
|
|
||||||
|
List<string> hotUpdateDlls = SettingsUtil.HotUpdateAssemblyNamesExcludePreserved;
|
||||||
|
var cache = new AssemblyCache(MetaUtil.CreateHotUpdateAndAOTAssemblyResolver(target, hotUpdateDlls));
|
||||||
|
|
||||||
|
var reversePInvokeAnalyzer = new MonoPInvokeCallbackAnalyzer(cache, hotUpdateDlls);
|
||||||
|
reversePInvokeAnalyzer.Run();
|
||||||
|
|
||||||
|
var calliAnalyzer = new CalliAnalyzer(cache, hotUpdateDlls);
|
||||||
|
calliAnalyzer.Run();
|
||||||
|
var pinvokeAnalyzer = new PInvokeAnalyzer(cache, hotUpdateDlls);
|
||||||
|
pinvokeAnalyzer.Run();
|
||||||
|
var callPInvokeMethodSignatures = pinvokeAnalyzer.PInvokeMethodSignatures;
|
||||||
|
|
||||||
|
string templateFile = $"{SettingsUtil.TemplatePathInPackage}/MethodBridge.cpp.tpl";
|
||||||
|
string outputFile = $"{SettingsUtil.GeneratedCppDir}/MethodBridge.cpp";
|
||||||
|
|
||||||
|
var callNativeMethodSignatures = calliAnalyzer.CalliMethodSignatures.Concat(pinvokeAnalyzer.PInvokeMethodSignatures).ToList();
|
||||||
|
GenerateMethodBridgeCppFile(methodBridgeAnalyzer.GenericMethods, reversePInvokeAnalyzer.ReversePInvokeMethods, callNativeMethodSignatures, templateFile, outputFile);
|
||||||
|
|
||||||
CleanIl2CppBuildCache();
|
CleanIl2CppBuildCache();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
|
using UnityEditor.Build;
|
||||||
|
|
||||||
namespace HybridCLR.Editor.Commands
|
namespace HybridCLR.Editor.Commands
|
||||||
{
|
{
|
||||||
|
|
@ -15,8 +16,13 @@ namespace HybridCLR.Editor.Commands
|
||||||
[MenuItem("HybridCLR/Generate/All", priority = 200)]
|
[MenuItem("HybridCLR/Generate/All", priority = 200)]
|
||||||
public static void GenerateAll()
|
public static void GenerateAll()
|
||||||
{
|
{
|
||||||
|
var installer = new Installer.InstallerController();
|
||||||
|
if (!installer.HasInstalledHybridCLR())
|
||||||
|
{
|
||||||
|
throw new BuildFailedException($"You have not initialized HybridCLR, please install it via menu 'HybridCLR/Installer'");
|
||||||
|
}
|
||||||
BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
|
BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
|
||||||
CompileDllCommand.CompileDll(target);
|
CompileDllCommand.CompileDll(target, EditorUserBuildSettings.development);
|
||||||
Il2CppDefGeneratorCommand.GenerateIl2CppDef();
|
Il2CppDefGeneratorCommand.GenerateIl2CppDef();
|
||||||
|
|
||||||
// 这几个生成依赖HotUpdateDlls
|
// 这几个生成依赖HotUpdateDlls
|
||||||
|
|
@ -26,8 +32,7 @@ namespace HybridCLR.Editor.Commands
|
||||||
StripAOTDllCommand.GenerateStripedAOTDlls(target);
|
StripAOTDllCommand.GenerateStripedAOTDlls(target);
|
||||||
|
|
||||||
// 桥接函数生成依赖于AOT dll,必须保证已经build过,生成AOT dll
|
// 桥接函数生成依赖于AOT dll,必须保证已经build过,生成AOT dll
|
||||||
MethodBridgeGeneratorCommand.GenerateMethodBridge(target);
|
MethodBridgeGeneratorCommand.GenerateMethodBridgeAndReversePInvokeWrapper(target);
|
||||||
ReversePInvokeWrapperGeneratorCommand.GenerateReversePInvokeWrapper(target);
|
|
||||||
AOTReferenceGeneratorCommand.GenerateAOTGenericReference(target);
|
AOTReferenceGeneratorCommand.GenerateAOTGenericReference(target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
using HybridCLR.Editor.ABI;
|
|
||||||
using HybridCLR.Editor.Link;
|
|
||||||
using HybridCLR.Editor.Meta;
|
|
||||||
using HybridCLR.Editor.ReversePInvokeWrap;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using UnityEditor;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace HybridCLR.Editor.Commands
|
|
||||||
{
|
|
||||||
|
|
||||||
public static class ReversePInvokeWrapperGeneratorCommand
|
|
||||||
{
|
|
||||||
|
|
||||||
[MenuItem("HybridCLR/Generate/ReversePInvokeWrapper", priority = 103)]
|
|
||||||
|
|
||||||
public static void CompileAndGenerateReversePInvokeWrapper()
|
|
||||||
{
|
|
||||||
BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
|
|
||||||
CompileDllCommand.CompileDll(target);
|
|
||||||
GenerateReversePInvokeWrapper(target);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void GenerateReversePInvokeWrapper(BuildTarget target)
|
|
||||||
{
|
|
||||||
List<string> hotUpdateDlls = SettingsUtil.HotUpdateAssemblyNamesExcludePreserved;
|
|
||||||
using (var cache = new AssemblyCache(MetaUtil.CreateHotUpdateAndAOTAssemblyResolver(target, hotUpdateDlls)))
|
|
||||||
{
|
|
||||||
var analyzer = new ReversePInvokeWrap.Analyzer(cache, hotUpdateDlls);
|
|
||||||
analyzer.Run();
|
|
||||||
|
|
||||||
string outputFile = $"{SettingsUtil.GeneratedCppDir}/ReversePInvokeMethodStub.cpp";
|
|
||||||
|
|
||||||
List<ABIReversePInvokeMethodInfo> methods = analyzer.BuildABIMethods();
|
|
||||||
Debug.Log($"GenerateReversePInvokeWrapper. wraperCount:{methods.Sum(m => m.Count)} output:{outputFile}");
|
|
||||||
var generator = new Generator();
|
|
||||||
generator.Generate(methods, outputFile);
|
|
||||||
Debug.LogFormat("[ReversePInvokeWrapperGeneratorCommand] output:{0}", outputFile);
|
|
||||||
}
|
|
||||||
MethodBridgeGeneratorCommand.CleanIl2CppBuildCache();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using HybridCLR.Editor.Installer;
|
using HybridCLR.Editor.BuildProcessors;
|
||||||
|
using HybridCLR.Editor.Installer;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
@ -56,11 +57,11 @@ namespace HybridCLR.Editor.Commands
|
||||||
switch(target)
|
switch(target)
|
||||||
{
|
{
|
||||||
case BuildTarget.StandaloneWindows:
|
case BuildTarget.StandaloneWindows:
|
||||||
case BuildTarget.StandaloneWindows64: return $"{buildDir}/{target}";
|
case BuildTarget.StandaloneWindows64: return $"{buildDir}/{PlayerSettings.productName}.exe";
|
||||||
case BuildTarget.StandaloneOSX: return buildDir;
|
case BuildTarget.StandaloneOSX: return buildDir;
|
||||||
case BuildTarget.iOS: return buildDir;
|
case BuildTarget.iOS: return buildDir;
|
||||||
case BuildTarget.Android: return buildDir;
|
case BuildTarget.Android: return buildDir;
|
||||||
case BuildTarget.StandaloneLinux64: return buildDir;
|
case BuildTarget.StandaloneLinux64: return $"{buildDir}/{PlayerSettings.productName}";
|
||||||
default: return buildDir;
|
default: return buildDir;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -71,9 +72,6 @@ namespace HybridCLR.Editor.Commands
|
||||||
BashUtil.RemoveDir(outputPath);
|
BashUtil.RemoveDir(outputPath);
|
||||||
|
|
||||||
var buildOptions = GetBuildPlayerOptions(target);
|
var buildOptions = GetBuildPlayerOptions(target);
|
||||||
#if UNITY_2021_2_OR_NEWER
|
|
||||||
buildOptions |= BuildOptions.CleanBuildCache;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
bool oldExportAndroidProj = EditorUserBuildSettings.exportAsGoogleAndroidProject;
|
bool oldExportAndroidProj = EditorUserBuildSettings.exportAsGoogleAndroidProject;
|
||||||
#if UNITY_EDITOR_OSX
|
#if UNITY_EDITOR_OSX
|
||||||
|
|
@ -81,103 +79,116 @@ namespace HybridCLR.Editor.Commands
|
||||||
#elif UNITY_EDITOR_WIN
|
#elif UNITY_EDITOR_WIN
|
||||||
bool oldCreateSolution = UnityEditor.WindowsStandalone.UserBuildSettings.createSolution;
|
bool oldCreateSolution = UnityEditor.WindowsStandalone.UserBuildSettings.createSolution;
|
||||||
#endif
|
#endif
|
||||||
#if TUANJIE_2022
|
#if TUANJIE_2022_3_OR_NEWER
|
||||||
bool oldOpenHarmonyProj = EditorUserBuildSettings.exportAsOpenHarmonyProject;
|
bool oldOpenHarmonyProj = EditorUserBuildSettings.exportAsOpenHarmonyProject;
|
||||||
#endif
|
#endif
|
||||||
bool oldBuildScriptsOnly = EditorUserBuildSettings.buildScriptsOnly;
|
bool oldBuildScriptsOnly = EditorUserBuildSettings.buildScriptsOnly;
|
||||||
EditorUserBuildSettings.buildScriptsOnly = true;
|
|
||||||
|
|
||||||
string location = GetLocationPathName(outputPath, target);
|
|
||||||
string oldBuildLocation = EditorUserBuildSettings.GetBuildLocation(target);
|
string oldBuildLocation = EditorUserBuildSettings.GetBuildLocation(target);
|
||||||
EditorUserBuildSettings.SetBuildLocation(target, location);
|
try
|
||||||
|
|
||||||
switch (target)
|
|
||||||
{
|
{
|
||||||
case BuildTarget.StandaloneWindows:
|
CheckSettings.DisableMethodBridgeDevelopmentFlagChecking = true;
|
||||||
case BuildTarget.StandaloneWindows64:
|
EditorUserBuildSettings.buildScriptsOnly = true;
|
||||||
|
|
||||||
|
string location = GetLocationPathName(outputPath, target);
|
||||||
|
EditorUserBuildSettings.SetBuildLocation(target, location);
|
||||||
|
|
||||||
|
switch (target)
|
||||||
{
|
{
|
||||||
#if UNITY_EDITOR_WIN
|
case BuildTarget.StandaloneWindows:
|
||||||
UnityEditor.WindowsStandalone.UserBuildSettings.createSolution = true;
|
case BuildTarget.StandaloneWindows64:
|
||||||
#endif
|
{
|
||||||
|
#if UNITY_EDITOR_WIN
|
||||||
|
UnityEditor.WindowsStandalone.UserBuildSettings.createSolution = true;
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case BuildTarget.StandaloneOSX:
|
||||||
|
{
|
||||||
|
#if UNITY_EDITOR_OSX
|
||||||
|
UnityEditor.OSXStandalone.UserBuildSettings.createXcodeProject = true;
|
||||||
|
#endif
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
#if TUANJIE_2022_3_OR_NEWER
|
||||||
|
case BuildTarget.HMIAndroid:
|
||||||
|
#endif
|
||||||
|
case BuildTarget.Android:
|
||||||
|
{
|
||||||
|
EditorUserBuildSettings.exportAsGoogleAndroidProject = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#if TUANJIE_2022_3_OR_NEWER
|
||||||
|
case BuildTarget.OpenHarmony:
|
||||||
|
{
|
||||||
|
EditorUserBuildSettings.exportAsOpenHarmonyProject = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
case BuildTarget.StandaloneOSX:
|
|
||||||
|
Debug.Log($"GenerateStripedAOTDlls build option:{buildOptions}");
|
||||||
|
|
||||||
|
BuildPlayerOptions buildPlayerOptions = new BuildPlayerOptions()
|
||||||
{
|
{
|
||||||
#if UNITY_EDITOR_OSX
|
scenes = EditorBuildSettings.scenes.Where(s => s.enabled).Select(s => s.path).ToArray(),
|
||||||
UnityEditor.OSXStandalone.UserBuildSettings.createXcodeProject = true;
|
locationPathName = location,
|
||||||
|
options = buildOptions,
|
||||||
|
target = target,
|
||||||
|
targetGroup = BuildPipeline.GetBuildTargetGroup(target),
|
||||||
|
#if UNITY_SERVER
|
||||||
|
subtarget = (int)StandaloneBuildSubtarget.Server,
|
||||||
#endif
|
#endif
|
||||||
break;
|
};
|
||||||
}
|
|
||||||
#if TUANJIE_2022
|
var report = BuildPipeline.BuildPlayer(buildPlayerOptions);
|
||||||
case BuildTarget.HMIAndroid:
|
|
||||||
#endif
|
|
||||||
case BuildTarget.Android:
|
|
||||||
|
if (report.summary.result != UnityEditor.Build.Reporting.BuildResult.Succeeded)
|
||||||
{
|
{
|
||||||
EditorUserBuildSettings.exportAsGoogleAndroidProject = true;
|
throw new Exception("GenerateStripedAOTDlls failed");
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
#if TUANJIE_2022
|
|
||||||
case BuildTarget.OpenHarmony:
|
|
||||||
{
|
|
||||||
EditorUserBuildSettings.exportAsOpenHarmonyProject = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
Debug.Log($"GenerateStripedAOTDlls build option:{buildOptions}");
|
|
||||||
|
|
||||||
BuildPlayerOptions buildPlayerOptions = new BuildPlayerOptions()
|
|
||||||
{
|
{
|
||||||
scenes = EditorBuildSettings.scenes.Where(s => s.enabled).Select(s => s.path).ToArray(),
|
CheckSettings.DisableMethodBridgeDevelopmentFlagChecking = false;
|
||||||
locationPathName = location,
|
EditorUserBuildSettings.buildScriptsOnly = oldBuildScriptsOnly;
|
||||||
options = buildOptions,
|
EditorUserBuildSettings.SetBuildLocation(target, oldBuildLocation);
|
||||||
target = target,
|
|
||||||
targetGroup = BuildPipeline.GetBuildTargetGroup(target),
|
|
||||||
};
|
|
||||||
|
|
||||||
var report = BuildPipeline.BuildPlayer(buildPlayerOptions);
|
switch (target)
|
||||||
|
{
|
||||||
EditorUserBuildSettings.buildScriptsOnly = oldBuildScriptsOnly;
|
case BuildTarget.StandaloneWindows:
|
||||||
EditorUserBuildSettings.SetBuildLocation(target, oldBuildLocation);
|
case BuildTarget.StandaloneWindows64:
|
||||||
|
|
||||||
switch (target)
|
|
||||||
{
|
|
||||||
case BuildTarget.StandaloneWindows:
|
|
||||||
case BuildTarget.StandaloneWindows64:
|
|
||||||
{
|
{
|
||||||
#if UNITY_EDITOR_WIN
|
#if UNITY_EDITOR_WIN
|
||||||
UnityEditor.WindowsStandalone.UserBuildSettings.createSolution = oldCreateSolution;
|
UnityEditor.WindowsStandalone.UserBuildSettings.createSolution = oldCreateSolution;
|
||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case BuildTarget.StandaloneOSX:
|
case BuildTarget.StandaloneOSX:
|
||||||
{
|
{
|
||||||
#if UNITY_EDITOR_OSX
|
#if UNITY_EDITOR_OSX
|
||||||
UnityEditor.OSXStandalone.UserBuildSettings.createXcodeProject = oldCreateSolution;
|
UnityEditor.OSXStandalone.UserBuildSettings.createXcodeProject = oldCreateSolution;
|
||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
#if TUANJIE_2022
|
#if TUANJIE_2022_3_OR_NEWER
|
||||||
case BuildTarget.HMIAndroid:
|
case BuildTarget.HMIAndroid:
|
||||||
#endif
|
#endif
|
||||||
case BuildTarget.Android:
|
case BuildTarget.Android:
|
||||||
{
|
{
|
||||||
EditorUserBuildSettings.exportAsGoogleAndroidProject = oldExportAndroidProj;
|
EditorUserBuildSettings.exportAsGoogleAndroidProject = oldExportAndroidProj;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
#if TUANJIE_2022
|
#if TUANJIE_2022_3_OR_NEWER
|
||||||
case BuildTarget.OpenHarmony:
|
case BuildTarget.OpenHarmony:
|
||||||
{
|
{
|
||||||
EditorUserBuildSettings.exportAsOpenHarmonyProject = oldOpenHarmonyProj;
|
EditorUserBuildSettings.exportAsOpenHarmonyProject = oldOpenHarmonyProj;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
if (report.summary.result != UnityEditor.Build.Reporting.BuildResult.Succeeded)
|
|
||||||
{
|
|
||||||
throw new Exception("GenerateStripedAOTDlls failed");
|
|
||||||
}
|
}
|
||||||
Debug.Log($"GenerateStripedAOTDlls target:{target} path:{outputPath}");
|
Debug.Log($"GenerateStripedAOTDlls target:{target} path:{outputPath}");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,17 +13,19 @@ namespace HybridCLR.Editor.HotUpdate
|
||||||
{
|
{
|
||||||
private readonly HashSet<string> _aotAssNames;
|
private readonly HashSet<string> _aotAssNames;
|
||||||
|
|
||||||
|
private readonly HashSet<string> _hotUpdateAssNames;
|
||||||
|
|
||||||
private readonly AssemblyCache _assCache;
|
private readonly AssemblyCache _assCache;
|
||||||
|
|
||||||
public MissingMetadataChecker(string aotDllDir, IEnumerable<string> excludeDllNames)
|
public MissingMetadataChecker(string aotDllDir, IEnumerable<string> hotUpdateAssNames)
|
||||||
{
|
{
|
||||||
|
|
||||||
var excludeDllNameSet = new HashSet<string>(excludeDllNames ?? new List<string>());
|
_hotUpdateAssNames = new HashSet<string>(hotUpdateAssNames ?? new List<string>());
|
||||||
_aotAssNames = new HashSet<string>();
|
_aotAssNames = new HashSet<string>();
|
||||||
foreach (var aotFile in Directory.GetFiles(aotDllDir, "*.dll"))
|
foreach (var aotFile in Directory.GetFiles(aotDllDir, "*.dll"))
|
||||||
{
|
{
|
||||||
string aotAssName = Path.GetFileNameWithoutExtension(aotFile);
|
string aotAssName = Path.GetFileNameWithoutExtension(aotFile);
|
||||||
if (excludeDllNameSet.Contains(aotAssName))
|
if (_hotUpdateAssNames.Contains(aotAssName))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -34,6 +36,8 @@ namespace HybridCLR.Editor.HotUpdate
|
||||||
|
|
||||||
public bool Check(string hotUpdateDllPath)
|
public bool Check(string hotUpdateDllPath)
|
||||||
{
|
{
|
||||||
|
bool anyMissing = false;
|
||||||
|
|
||||||
ModuleDef mod = ModuleDefMD.Load(File.ReadAllBytes(hotUpdateDllPath), _assCache.ModCtx);
|
ModuleDef mod = ModuleDefMD.Load(File.ReadAllBytes(hotUpdateDllPath), _assCache.ModCtx);
|
||||||
|
|
||||||
foreach (var refass in mod.GetAssemblyRefs())
|
foreach (var refass in mod.GetAssemblyRefs())
|
||||||
|
|
@ -43,9 +47,13 @@ namespace HybridCLR.Editor.HotUpdate
|
||||||
{
|
{
|
||||||
_assCache.LoadModule(refass.Name, true);
|
_assCache.LoadModule(refass.Name, true);
|
||||||
}
|
}
|
||||||
|
else if (!_hotUpdateAssNames.Contains(refAssName))
|
||||||
|
{
|
||||||
|
UnityEngine.Debug.LogError($"Missing AOT Assembly: {refAssName}");
|
||||||
|
anyMissing = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool anyMissing = false;
|
|
||||||
|
|
||||||
foreach (TypeRef typeRef in mod.GetTypeRefs())
|
foreach (TypeRef typeRef in mod.GetTypeRefs())
|
||||||
{
|
{
|
||||||
|
|
@ -61,31 +69,36 @@ namespace HybridCLR.Editor.HotUpdate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (IMethodDefOrRef methodRef in mod.GetMemberRefs())
|
foreach (IMethodDefOrRef memberRef in mod.GetMemberRefs())
|
||||||
{
|
{
|
||||||
if (methodRef.DeclaringType.DefinitionAssembly == null)
|
if (memberRef.DeclaringType.DefinitionAssembly == null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
string defAssName = methodRef.DeclaringType.DefinitionAssembly.Name;
|
string defAssName = memberRef.DeclaringType.DefinitionAssembly.Name;
|
||||||
if (!_aotAssNames.Contains(defAssName))
|
if (!_aotAssNames.Contains(defAssName))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (methodRef.IsField)
|
if (memberRef.IsField)
|
||||||
{
|
{
|
||||||
|
IField field = (IField)memberRef;
|
||||||
|
if (field.ResolveFieldDef() == null)
|
||||||
|
{
|
||||||
|
UnityEngine.Debug.LogError($"Missing Field: {memberRef.FullName}");
|
||||||
|
anyMissing = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (methodRef.IsMethod)
|
else if (memberRef.IsMethod)
|
||||||
{
|
{
|
||||||
TypeSig declaringTypeSig = methodRef.DeclaringType.ToTypeSig();
|
TypeSig declaringTypeSig = memberRef.DeclaringType.ToTypeSig();
|
||||||
if (methodRef.ResolveMethodDef() == null)
|
if (memberRef.ResolveMethodDef() == null)
|
||||||
{
|
{
|
||||||
if (declaringTypeSig.ElementType == ElementType.Array || declaringTypeSig.ElementType == ElementType.SZArray)
|
if (declaringTypeSig.ElementType == ElementType.Array || declaringTypeSig.ElementType == ElementType.SZArray)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
UnityEngine.Debug.LogError($"Missing Method: {methodRef.FullName}");
|
UnityEngine.Debug.LogError($"Missing Method: {memberRef.FullName}");
|
||||||
anyMissing = true;
|
anyMissing = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,13 @@ namespace HybridCLR.Editor.Il2CppDef
|
||||||
{
|
{
|
||||||
public List<string> HotUpdateAssemblies { get; set; }
|
public List<string> HotUpdateAssemblies { get; set; }
|
||||||
|
|
||||||
public string OutputFile { get; set; }
|
public string UnityVersionTemplateFile { get; set; }
|
||||||
|
|
||||||
public string OutputFile2 { get; set; }
|
public string UnityVersionOutputFile { get; set; }
|
||||||
|
|
||||||
|
public string AssemblyManifestTemplateFile { get; set; }
|
||||||
|
|
||||||
|
public string AssemblyManifestOutputFile { get; set; }
|
||||||
|
|
||||||
public string UnityVersion { get; set; }
|
public string UnityVersion { get; set; }
|
||||||
}
|
}
|
||||||
|
|
@ -39,15 +43,9 @@ namespace HybridCLR.Editor.Il2CppDef
|
||||||
GeneratePlaceHolderAssemblies();
|
GeneratePlaceHolderAssemblies();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static bool IsTuanJie()
|
|
||||||
{
|
|
||||||
return Regex.IsMatch(Application.unityVersion, @".*t\d+$");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GenerateIl2CppConfig()
|
private void GenerateIl2CppConfig()
|
||||||
{
|
{
|
||||||
var frr = new FileRegionReplace(File.ReadAllText(_options.OutputFile));
|
var frr = new FileRegionReplace(File.ReadAllText(_options.UnityVersionTemplateFile));
|
||||||
|
|
||||||
List<string> lines = new List<string>();
|
List<string> lines = new List<string>();
|
||||||
|
|
||||||
|
|
@ -58,7 +56,14 @@ namespace HybridCLR.Editor.Il2CppDef
|
||||||
|
|
||||||
lines.Add($"#define HYBRIDCLR_UNITY_VERSION {majorVer}{minorVer1.ToString("D2")}{minorVer2.ToString("D2")}");
|
lines.Add($"#define HYBRIDCLR_UNITY_VERSION {majorVer}{minorVer1.ToString("D2")}{minorVer2.ToString("D2")}");
|
||||||
lines.Add($"#define HYBRIDCLR_UNITY_{majorVer} 1");
|
lines.Add($"#define HYBRIDCLR_UNITY_{majorVer} 1");
|
||||||
for (int ver = 2019; ver <= 2024; ver++)
|
for (int ver = 2019; ver <= 2023; ver++)
|
||||||
|
{
|
||||||
|
if (majorVer >= ver)
|
||||||
|
{
|
||||||
|
lines.Add($"#define HYBRIDCLR_UNITY_{ver}_OR_NEW 1");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int ver = 6000; ver <= 6100; ver++)
|
||||||
{
|
{
|
||||||
if (majorVer >= ver)
|
if (majorVer >= ver)
|
||||||
{
|
{
|
||||||
|
|
@ -66,20 +71,25 @@ namespace HybridCLR.Editor.Il2CppDef
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsTuanJie())
|
#if TUANJIE_1_1_OR_NEWER
|
||||||
{
|
var tuanjieMatch = Regex.Matches(Application.tuanjieVersion, @"(\d+)\.(\d+)\.(\d+)");
|
||||||
lines.Add($"#define UNITY_ENGINE_TUANJIE 1");
|
int tuanjieMajorVer = int.Parse(tuanjieMatch[0].Groups[1].Value);
|
||||||
}
|
int tuanjieMinorVer1 = int.Parse(tuanjieMatch[0].Groups[2].Value);
|
||||||
|
int tuanjieMinorVer2 = int.Parse(tuanjieMatch[0].Groups[3].Value);
|
||||||
|
lines.Add($"#define HYBRIDCLR_TUANJIE_VERSION {tuanjieMajorVer}{tuanjieMinorVer1.ToString("D2")}{tuanjieMinorVer2.ToString("D2")}");
|
||||||
|
#elif TUANJIE_2022_3_OR_NEWER
|
||||||
|
lines.Add($"#define HYBRIDCLR_TUANJIE_VERSION 10000");
|
||||||
|
#endif
|
||||||
|
|
||||||
frr.Replace("UNITY_VERSION", string.Join("\n", lines));
|
frr.Replace("UNITY_VERSION", string.Join("\n", lines));
|
||||||
|
|
||||||
frr.Commit(_options.OutputFile);
|
frr.Commit(_options.UnityVersionOutputFile);
|
||||||
Debug.Log($"[HybridCLR.Editor.Il2CppDef.Generator] output:{_options.OutputFile}");
|
Debug.Log($"[HybridCLR.Editor.Il2CppDef.Generator] output:{_options.UnityVersionOutputFile}");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GeneratePlaceHolderAssemblies()
|
private void GeneratePlaceHolderAssemblies()
|
||||||
{
|
{
|
||||||
var frr = new FileRegionReplace(File.ReadAllText(_options.OutputFile2));
|
var frr = new FileRegionReplace(File.ReadAllText(_options.AssemblyManifestTemplateFile));
|
||||||
|
|
||||||
List<string> lines = new List<string>();
|
List<string> lines = new List<string>();
|
||||||
|
|
||||||
|
|
@ -90,8 +100,8 @@ namespace HybridCLR.Editor.Il2CppDef
|
||||||
|
|
||||||
frr.Replace("PLACE_HOLDER", string.Join("\n", lines));
|
frr.Replace("PLACE_HOLDER", string.Join("\n", lines));
|
||||||
|
|
||||||
frr.Commit(_options.OutputFile2);
|
frr.Commit(_options.AssemblyManifestOutputFile);
|
||||||
Debug.Log($"[HybridCLR.Editor.Il2CppDef.Generator] output:{_options.OutputFile2}");
|
Debug.Log($"[HybridCLR.Editor.Il2CppDef.Generator] output:{_options.AssemblyManifestOutputFile}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ namespace HybridCLR.Editor.Installer
|
||||||
{
|
{
|
||||||
RemoveDir(subDir);
|
RemoveDir(subDir);
|
||||||
}
|
}
|
||||||
Directory.Delete(dir);
|
Directory.Delete(dir, true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
|
|
@ -102,42 +102,23 @@ namespace HybridCLR.Editor.Installer
|
||||||
}
|
}
|
||||||
Directory.CreateDirectory(dir);
|
Directory.CreateDirectory(dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void CopyWithCheckLongFile(string srcFile, string dstFile)
|
|
||||||
{
|
|
||||||
var maxPathLength = 255;
|
|
||||||
#if UNITY_EDITOR_OSX
|
|
||||||
maxPathLength = 1024;
|
|
||||||
#endif
|
|
||||||
if (srcFile.Length > maxPathLength)
|
|
||||||
{
|
|
||||||
UnityEngine.Debug.LogError($"srcFile:{srcFile} path is too long. copy ignore!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (dstFile.Length > maxPathLength)
|
|
||||||
{
|
|
||||||
UnityEngine.Debug.LogError($"dstFile:{dstFile} path is too long. copy ignore!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
File.Copy(srcFile, dstFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void CopyDir(string src, string dst, bool log = false)
|
public static void CopyDir(string src, string dst, bool log = false)
|
||||||
{
|
{
|
||||||
if (log)
|
if (log)
|
||||||
{
|
{
|
||||||
UnityEngine.Debug.Log($"[BashUtil] CopyDir {src} => {dst}");
|
UnityEngine.Debug.Log($"[BashUtil] CopyDir {src} => {dst}");
|
||||||
}
|
}
|
||||||
RemoveDir(dst);
|
if (Directory.Exists(dst))
|
||||||
Directory.CreateDirectory(dst);
|
|
||||||
foreach(var file in Directory.GetFiles(src))
|
|
||||||
{
|
{
|
||||||
CopyWithCheckLongFile(file, $"{dst}/{Path.GetFileName(file)}");
|
RemoveDir(dst);
|
||||||
}
|
}
|
||||||
foreach(var subDir in Directory.GetDirectories(src))
|
else
|
||||||
{
|
{
|
||||||
CopyDir(subDir, $"{dst}/{Path.GetFileName(subDir)}");
|
string parentDir = Path.GetDirectoryName(Path.GetFullPath(dst));
|
||||||
|
Directory.CreateDirectory(parentDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UnityEditor.FileUtil.CopyFileOrDirectory(src, dst);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ namespace HybridCLR.Editor.Installer
|
||||||
{
|
{
|
||||||
_curVersion = ParseUnityVersion(Application.unityVersion);
|
_curVersion = ParseUnityVersion(Application.unityVersion);
|
||||||
_versionManifest = GetHybridCLRVersionManifest();
|
_versionManifest = GetHybridCLRVersionManifest();
|
||||||
_curDefaultVersion = _versionManifest.versions.FirstOrDefault(v => v.unity_version == _curVersion.major.ToString());
|
_curDefaultVersion = _versionManifest.versions.FirstOrDefault(v => _curVersion.isTuanjieEngine ? v.unity_version == $"{_curVersion.major}-tuanjie" : v.unity_version == _curVersion.major.ToString());
|
||||||
PackageVersion = LoadPackageInfo().version;
|
PackageVersion = LoadPackageInfo().version;
|
||||||
InstalledLibil2cppVersion = ReadLocalVersion();
|
InstalledLibil2cppVersion = ReadLocalVersion();
|
||||||
}
|
}
|
||||||
|
|
@ -88,6 +88,7 @@ namespace HybridCLR.Editor.Installer
|
||||||
public int major;
|
public int major;
|
||||||
public int minor1;
|
public int minor1;
|
||||||
public int minor2;
|
public int minor2;
|
||||||
|
public bool isTuanjieEngine;
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
|
|
@ -108,7 +109,8 @@ namespace HybridCLR.Editor.Installer
|
||||||
int major = int.Parse(match.Groups[1].Value);
|
int major = int.Parse(match.Groups[1].Value);
|
||||||
int minor1 = int.Parse(match.Groups[2].Value);
|
int minor1 = int.Parse(match.Groups[2].Value);
|
||||||
int minor2 = int.Parse(match.Groups[3].Value);
|
int minor2 = int.Parse(match.Groups[3].Value);
|
||||||
return new UnityVersion { major = major, minor1 = minor1, minor2 = minor2 };
|
bool isTuanjieEngine = versionStr.Contains("t");
|
||||||
|
return new UnityVersion { major = major, minor1 = minor1, minor2 = minor2, isTuanjieEngine = isTuanjieEngine };
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetCurrentUnityVersionMinCompatibleVersionStr()
|
public string GetCurrentUnityVersionMinCompatibleVersionStr()
|
||||||
|
|
@ -120,10 +122,12 @@ namespace HybridCLR.Editor.Installer
|
||||||
{
|
{
|
||||||
switch(majorVersion)
|
switch(majorVersion)
|
||||||
{
|
{
|
||||||
case 2019: return $"2019.4.0";
|
case 2019: return "2019.4.0";
|
||||||
case 2020: return $"2020.3.0";
|
case 2020: return "2020.3.0";
|
||||||
case 2021: return $"2021.3.0";
|
case 2021: return "2021.3.0";
|
||||||
case 2022: return $"2022.3.0";
|
case 2022: return "2022.3.0";
|
||||||
|
case 2023: return "2023.2.0";
|
||||||
|
case 6000: return "6000.0.0";
|
||||||
default: return $"2020.3.0";
|
default: return $"2020.3.0";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -142,7 +146,8 @@ namespace HybridCLR.Editor.Installer
|
||||||
{
|
{
|
||||||
return CompatibleType.Incompatible;
|
return CompatibleType.Incompatible;
|
||||||
}
|
}
|
||||||
if ((version.major == 2019 && version.minor1 < 4) || (version.major >= 2020 && version.minor1 < 3))
|
if ((version.major == 2019 && version.minor1 < 4)
|
||||||
|
|| (version.major >= 2020 && version.major <= 2022 && version.minor1 < 3))
|
||||||
{
|
{
|
||||||
return CompatibleType.MaybeIncompatible;
|
return CompatibleType.MaybeIncompatible;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,9 +44,9 @@ namespace HybridCLR.Editor.Installer
|
||||||
EditorGUILayout.LabelField($"Installed: {hasInstall}", EditorStyles.boldLabel);
|
EditorGUILayout.LabelField($"Installed: {hasInstall}", EditorStyles.boldLabel);
|
||||||
GUILayout.Space(10f);
|
GUILayout.Space(10f);
|
||||||
|
|
||||||
EditorGUILayout.LabelField($"Package Version: v{_controller.PackageVersion}");
|
EditorGUILayout.LabelField($"Package Version: v{_controller.PackageVersion}");
|
||||||
GUILayout.Space(5f);
|
GUILayout.Space(5f);
|
||||||
EditorGUILayout.LabelField($"Installed Version: {_controller.InstalledLibil2cppVersion ?? "Unknown"}");
|
EditorGUILayout.LabelField($"Installed Version: v{_controller.InstalledLibil2cppVersion ?? " Unknown"}");
|
||||||
GUILayout.Space(5f);
|
GUILayout.Space(5f);
|
||||||
|
|
||||||
GUILayout.Space(10f);
|
GUILayout.Space(10f);
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ using System.Threading.Tasks;
|
||||||
using dnlib.DotNet;
|
using dnlib.DotNet;
|
||||||
using HybridCLR.Editor.Meta;
|
using HybridCLR.Editor.Meta;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
using IAssemblyResolver = HybridCLR.Editor.Meta.IAssemblyResolver;
|
using IAssemblyResolver = HybridCLR.Editor.Meta.IAssemblyResolver;
|
||||||
|
|
||||||
namespace HybridCLR.Editor.Link
|
namespace HybridCLR.Editor.Link
|
||||||
|
|
@ -22,26 +23,27 @@ namespace HybridCLR.Editor.Link
|
||||||
|
|
||||||
public HashSet<TypeRef> CollectRefs(List<string> rootAssemblies)
|
public HashSet<TypeRef> CollectRefs(List<string> rootAssemblies)
|
||||||
{
|
{
|
||||||
using (var assCollector = new AssemblyCache(_resolver))
|
var assCollector = new AssemblyCache(_resolver);
|
||||||
{
|
var rootAssemblyNames = new HashSet<string>(rootAssemblies);
|
||||||
var rootAssemblyNames = new HashSet<string>(rootAssemblies);
|
|
||||||
|
|
||||||
var typeRefs = new HashSet<TypeRef>(TypeEqualityComparer.Instance);
|
var typeRefs = new HashSet<TypeRef>(TypeEqualityComparer.Instance);
|
||||||
foreach (var rootAss in rootAssemblies)
|
foreach (var rootAss in rootAssemblies)
|
||||||
|
{
|
||||||
|
var dnAss = assCollector.LoadModule(rootAss, false);
|
||||||
|
foreach (var type in dnAss.GetTypeRefs())
|
||||||
{
|
{
|
||||||
var dnAss = assCollector.LoadModule(rootAss, false);
|
if (type.DefinitionAssembly == null)
|
||||||
foreach (var type in dnAss.GetTypeRefs())
|
|
||||||
{
|
{
|
||||||
if (!rootAssemblyNames.Contains(type.DefinitionAssembly.Name.ToString()))
|
Debug.LogWarning($"assembly:{dnAss.Name} TypeRef {type.FullName} has no DefinitionAssembly");
|
||||||
{
|
continue;
|
||||||
typeRefs.Add(type);
|
}
|
||||||
}
|
if (!rootAssemblyNames.Contains(type.DefinitionAssembly.Name.ToString()))
|
||||||
|
{
|
||||||
|
typeRefs.Add(type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assCollector.Dispose();
|
|
||||||
return typeRefs;
|
|
||||||
}
|
}
|
||||||
|
return typeRefs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace HybridCLR.Editor.Link
|
namespace HybridCLR.Editor.Link
|
||||||
{
|
{
|
||||||
internal class LinkXmlWriter
|
public class LinkXmlWriter
|
||||||
{
|
{
|
||||||
public void Write(string outputLinkXmlFile, HashSet<TypeRef> refTypes)
|
public void Write(string outputLinkXmlFile, HashSet<TypeRef> refTypes)
|
||||||
{
|
{
|
||||||
|
|
@ -32,6 +32,12 @@ namespace HybridCLR.Editor.Link
|
||||||
assTypeNames.Sort(string.CompareOrdinal);
|
assTypeNames.Sort(string.CompareOrdinal);
|
||||||
foreach(var typeName in assTypeNames)
|
foreach(var typeName in assTypeNames)
|
||||||
{
|
{
|
||||||
|
#if UNITY_2023_1_OR_NEWER
|
||||||
|
if (typeName == "UnityEngine.Debug")
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
writer.WriteStartElement("type");
|
writer.WriteStartElement("type");
|
||||||
writer.WriteAttributeString("fullname", typeName);
|
writer.WriteAttributeString("fullname", typeName);
|
||||||
writer.WriteAttributeString("preserve", "all");
|
writer.WriteAttributeString("preserve", "all");
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,11 @@ using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace HybridCLR.Editor.Meta
|
namespace HybridCLR.Editor.Meta
|
||||||
{
|
{
|
||||||
public abstract class AssemblyCacheBase : IDisposable
|
public abstract class AssemblyCacheBase
|
||||||
{
|
{
|
||||||
private readonly IAssemblyResolver _assemblyPathResolver;
|
private readonly IAssemblyResolver _assemblyPathResolver;
|
||||||
private readonly ModuleContext _modCtx;
|
private readonly ModuleContext _modCtx;
|
||||||
private readonly AssemblyResolver _asmResolver;
|
private readonly AssemblyResolver _asmResolver;
|
||||||
private bool disposedValue;
|
|
||||||
private bool _loadedNetstandard;
|
private bool _loadedNetstandard;
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -91,32 +90,10 @@ namespace HybridCLR.Editor.Meta
|
||||||
{
|
{
|
||||||
//Debug.Log($"do load module:{dllPath}");
|
//Debug.Log($"do load module:{dllPath}");
|
||||||
ModuleDefMD mod = ModuleDefMD.Load(File.ReadAllBytes(dllPath), _modCtx);
|
ModuleDefMD mod = ModuleDefMD.Load(File.ReadAllBytes(dllPath), _modCtx);
|
||||||
|
mod.EnableTypeDefFindCache = true;
|
||||||
_asmResolver.AddToCache(mod);
|
_asmResolver.AddToCache(mod);
|
||||||
_loadedModulesIncludeNetstandard.Add(mod);
|
_loadedModulesIncludeNetstandard.Add(mod);
|
||||||
return mod;
|
return mod;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (!disposedValue)
|
|
||||||
{
|
|
||||||
if (disposing)
|
|
||||||
{
|
|
||||||
foreach (var mod in _loadedModulesIncludeNetstandard)
|
|
||||||
{
|
|
||||||
mod.Dispose();
|
|
||||||
}
|
|
||||||
_loadedModulesIncludeNetstandard.Clear();
|
|
||||||
LoadedModules.Clear();
|
|
||||||
}
|
|
||||||
disposedValue = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
Dispose(disposing: true);
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,105 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace HybridCLR.Editor.Meta
|
||||||
|
{
|
||||||
|
|
||||||
|
public class AssemblySorter
|
||||||
|
{
|
||||||
|
class Node
|
||||||
|
{
|
||||||
|
public string Name;
|
||||||
|
public List<Node> Dependencies = new List<Node>();
|
||||||
|
|
||||||
|
public Node(string name)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TopologicalSorter
|
||||||
|
{
|
||||||
|
|
||||||
|
public static List<Node> Sort(List<Node> nodes)
|
||||||
|
{
|
||||||
|
List<Node> sorted = new List<Node>();
|
||||||
|
HashSet<Node> visited = new HashSet<Node>();
|
||||||
|
HashSet<Node> tempMarks = new HashSet<Node>();
|
||||||
|
|
||||||
|
foreach (var node in nodes)
|
||||||
|
{
|
||||||
|
if (!visited.Contains(node))
|
||||||
|
{
|
||||||
|
Visit(node, visited, tempMarks, sorted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sorted;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Visit(Node node, HashSet<Node> visited, HashSet<Node> tempMarks, List<Node> sorted)
|
||||||
|
{
|
||||||
|
if (tempMarks.Contains(node))
|
||||||
|
{
|
||||||
|
throw new Exception("Detected cyclic dependency!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!visited.Contains(node))
|
||||||
|
{
|
||||||
|
tempMarks.Add(node);
|
||||||
|
foreach (var dependency in node.Dependencies)
|
||||||
|
{
|
||||||
|
Visit(dependency, visited, tempMarks, sorted);
|
||||||
|
}
|
||||||
|
tempMarks.Remove(node);
|
||||||
|
visited.Add(node);
|
||||||
|
sorted.Add(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<string> SortAssemblyByReferenceOrder(IEnumerable<string> assemblies, Dictionary<string, HashSet<string>> refs)
|
||||||
|
{
|
||||||
|
var nodes = new List<Node>();
|
||||||
|
var nodeMap = new Dictionary<string, Node>();
|
||||||
|
foreach (var assembly in assemblies)
|
||||||
|
{
|
||||||
|
var node = new Node(assembly);
|
||||||
|
nodes.Add(node);
|
||||||
|
nodeMap.Add(assembly, node);
|
||||||
|
}
|
||||||
|
foreach (var assembly in assemblies)
|
||||||
|
{
|
||||||
|
var node = nodeMap[assembly];
|
||||||
|
foreach (var refAssembly in refs[assembly])
|
||||||
|
{
|
||||||
|
node.Dependencies.Add(nodeMap[refAssembly]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var sortedNodes = TopologicalSorter.Sort(nodes);
|
||||||
|
return sortedNodes.Select(node => node.Name).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<string> SortAssemblyByReferenceOrder(IEnumerable<string> assemblies, IAssemblyResolver assemblyResolver)
|
||||||
|
{
|
||||||
|
var assCache = new AssemblyCache(assemblyResolver);
|
||||||
|
var assRefAssemblies = new Dictionary<string, HashSet<string>>();
|
||||||
|
foreach (var assName in assemblies)
|
||||||
|
{
|
||||||
|
var refAssemblies = new HashSet<string>();
|
||||||
|
var mod = assCache.LoadModule(assName, false);
|
||||||
|
foreach (var refAss in mod.GetAssemblyRefs())
|
||||||
|
{
|
||||||
|
if (assemblies.Contains(refAss.Name.ToString()))
|
||||||
|
{
|
||||||
|
refAssemblies.Add(refAss.Name.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assRefAssemblies.Add(assName, refAssemblies);
|
||||||
|
}
|
||||||
|
return SortAssemblyByReferenceOrder(assemblies, assRefAssemblies);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 7db18e1736f593c4089c85d764cf8620
|
guid: b9b8eb45398fa344daa8c6e9b9fbf291
|
||||||
MonoImporter:
|
MonoImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
|
|
@ -29,6 +29,12 @@ namespace HybridCLR.Editor.Meta
|
||||||
Debug.Log($"[FixedSetAssemblyResolver] resolve:{assemblyName} path:{assemblyPath}");
|
Debug.Log($"[FixedSetAssemblyResolver] resolve:{assemblyName} path:{assemblyPath}");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
assemblyPath = $"{_rootDir}/{assemblyName}.dll.bytes";
|
||||||
|
if (File.Exists(assemblyPath))
|
||||||
|
{
|
||||||
|
Debug.Log($"[FixedSetAssemblyResolver] resolve:{assemblyName} path:{assemblyPath}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
assemblyPath = null;
|
assemblyPath = null;
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,11 @@ using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace HybridCLR.Editor.Meta
|
namespace HybridCLR.Editor.Meta
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Replaces generic type/method var with its generic argument
|
public class GenericArgumentContext
|
||||||
/// </summary>
|
|
||||||
public sealed class GenericArgumentContext
|
|
||||||
{
|
{
|
||||||
List<TypeSig> typeArgsStack = new List<TypeSig>();
|
private readonly List<TypeSig> typeArgsStack;
|
||||||
List<TypeSig> methodArgsStack = new List<TypeSig>();
|
private readonly List<TypeSig> methodArgsStack;
|
||||||
|
|
||||||
public GenericArgumentContext(List<TypeSig> typeArgsStack, List<TypeSig> methodArgsStack)
|
public GenericArgumentContext(List<TypeSig> typeArgsStack, List<TypeSig> methodArgsStack)
|
||||||
{
|
{
|
||||||
|
|
@ -21,16 +19,6 @@ namespace HybridCLR.Editor.Meta
|
||||||
this.methodArgsStack = methodArgsStack;
|
this.methodArgsStack = methodArgsStack;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Replaces a generic type/method var with its generic argument (if any). If
|
|
||||||
/// <paramref name="typeSig"/> isn't a generic type/method var or if it can't
|
|
||||||
/// be resolved, it itself is returned. Else the resolved type is returned.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="typeSig">Type signature</param>
|
|
||||||
/// <returns>New <see cref="TypeSig"/> which is never <c>null</c> unless
|
|
||||||
/// <paramref name="typeSig"/> is <c>null</c></returns>
|
|
||||||
public TypeSig Resolve(TypeSig typeSig)
|
public TypeSig Resolve(TypeSig typeSig)
|
||||||
{
|
{
|
||||||
if (!typeSig.ContainsGenericParameter)
|
if (!typeSig.ContainsGenericParameter)
|
||||||
|
|
@ -41,9 +29,9 @@ namespace HybridCLR.Editor.Meta
|
||||||
switch (typeSig.ElementType)
|
switch (typeSig.ElementType)
|
||||||
{
|
{
|
||||||
case ElementType.Ptr: return new PtrSig(Resolve(typeSig.Next));
|
case ElementType.Ptr: return new PtrSig(Resolve(typeSig.Next));
|
||||||
case ElementType.ByRef: return new PtrSig(Resolve(typeSig.Next));
|
case ElementType.ByRef: return new ByRefSig(Resolve(typeSig.Next));
|
||||||
|
|
||||||
case ElementType.SZArray: return new PtrSig(Resolve(typeSig.Next));
|
case ElementType.SZArray: return new SZArraySig(Resolve(typeSig.Next));
|
||||||
case ElementType.Array:
|
case ElementType.Array:
|
||||||
{
|
{
|
||||||
var ara = (ArraySig)typeSig;
|
var ara = (ArraySig)typeSig;
|
||||||
|
|
@ -53,7 +41,7 @@ namespace HybridCLR.Editor.Meta
|
||||||
case ElementType.Var:
|
case ElementType.Var:
|
||||||
{
|
{
|
||||||
GenericVar genericVar = (GenericVar)typeSig;
|
GenericVar genericVar = (GenericVar)typeSig;
|
||||||
var newSig = Resolve(typeArgsStack, genericVar.Number, true);
|
var newSig = Resolve(typeArgsStack, genericVar.Number);
|
||||||
if (newSig == null)
|
if (newSig == null)
|
||||||
{
|
{
|
||||||
throw new Exception();
|
throw new Exception();
|
||||||
|
|
@ -64,7 +52,7 @@ namespace HybridCLR.Editor.Meta
|
||||||
case ElementType.MVar:
|
case ElementType.MVar:
|
||||||
{
|
{
|
||||||
GenericMVar genericVar = (GenericMVar)typeSig;
|
GenericMVar genericVar = (GenericMVar)typeSig;
|
||||||
var newSig = Resolve(methodArgsStack, genericVar.Number, true);
|
var newSig = Resolve(methodArgsStack, genericVar.Number);
|
||||||
if (newSig == null)
|
if (newSig == null)
|
||||||
{
|
{
|
||||||
throw new Exception();
|
throw new Exception();
|
||||||
|
|
@ -79,7 +67,29 @@ namespace HybridCLR.Editor.Meta
|
||||||
|
|
||||||
case ElementType.FnPtr:
|
case ElementType.FnPtr:
|
||||||
{
|
{
|
||||||
throw new NotSupportedException(typeSig.ToString());
|
var fptr = (FnPtrSig)typeSig;
|
||||||
|
var cs = fptr.Signature;
|
||||||
|
CallingConventionSig ccs;
|
||||||
|
switch (cs)
|
||||||
|
{
|
||||||
|
case MethodSig ms:
|
||||||
|
{
|
||||||
|
ccs = new MethodSig(ms.GetCallingConvention(), ms.GenParamCount, Resolve(ms.RetType), ms.Params.Select(p => Resolve(p)).ToList());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PropertySig ps:
|
||||||
|
{
|
||||||
|
ccs = new PropertySig(ps.HasThis, Resolve(ps.RetType));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case GenericInstMethodSig gims:
|
||||||
|
{
|
||||||
|
ccs = new GenericInstMethodSig(gims.GenericArguments.Select(ga => Resolve(ga)).ToArray());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: throw new NotSupportedException(cs.ToString());
|
||||||
|
}
|
||||||
|
return new FnPtrSig(ccs);
|
||||||
}
|
}
|
||||||
|
|
||||||
case ElementType.ValueArray:
|
case ElementType.ValueArray:
|
||||||
|
|
@ -91,13 +101,9 @@ namespace HybridCLR.Editor.Meta
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private TypeSig Resolve(List<TypeSig> args, uint number, bool isTypeVar)
|
private TypeSig Resolve(List<TypeSig> args, uint number)
|
||||||
{
|
{
|
||||||
var typeSig = args[(int)number];
|
return args[(int)number];
|
||||||
var gvar = typeSig as GenericSig;
|
|
||||||
if (gvar is null || gvar.IsTypeVar != isTypeVar)
|
|
||||||
return typeSig;
|
|
||||||
return gvar;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,7 @@ namespace HybridCLR.Editor.Meta
|
||||||
methodDef = method.ResolveMethodDef();
|
methodDef = method.ResolveMethodDef();
|
||||||
if (methodDef == null)
|
if (methodDef == null)
|
||||||
{
|
{
|
||||||
Debug.LogWarning($"method:{method} ResolveMethodDef() == null");
|
//Debug.LogWarning($"method:{method} ResolveMethodDef() == null");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (method is MethodSpec methodSpec)
|
if (method is MethodSpec methodSpec)
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,7 @@ namespace HybridCLR.Editor.Meta
|
||||||
}
|
}
|
||||||
return new GenericInstSig(gia.GenericType, gia.GenericArguments.Select(ga => ToShareTypeSig(corTypes, ga)).ToList());
|
return new GenericInstSig(gia.GenericType, gia.GenericArguments.Select(ga => ToShareTypeSig(corTypes, ga)).ToList());
|
||||||
}
|
}
|
||||||
case ElementType.FnPtr: return corTypes.IntPtr;
|
case ElementType.FnPtr: return corTypes.UIntPtr;
|
||||||
case ElementType.ValueArray: return typeSig;
|
case ElementType.ValueArray: return typeSig;
|
||||||
case ElementType.Module: return typeSig;
|
case ElementType.Module: return typeSig;
|
||||||
default:
|
default:
|
||||||
|
|
@ -189,5 +189,29 @@ namespace HybridCLR.Editor.Meta
|
||||||
}
|
}
|
||||||
return methodGenericParams;
|
return methodGenericParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool IsSupportedPInvokeTypeSig(TypeSig typeSig)
|
||||||
|
{
|
||||||
|
typeSig = typeSig.RemovePinnedAndModifiers();
|
||||||
|
if (typeSig.IsByRef)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
switch (typeSig.ElementType)
|
||||||
|
{
|
||||||
|
case ElementType.SZArray:
|
||||||
|
case ElementType.Array:
|
||||||
|
//case ElementType.Class:
|
||||||
|
case ElementType.String:
|
||||||
|
//case ElementType.Object:
|
||||||
|
return false;
|
||||||
|
default: return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsSupportedPInvokeMethodSignature(MethodSig methodSig)
|
||||||
|
{
|
||||||
|
return IsSupportedPInvokeTypeSig(methodSig.RetType) && methodSig.Params.All(p => IsSupportedPInvokeTypeSig(p));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,11 +20,16 @@ namespace HybridCLR.Editor.Meta
|
||||||
{
|
{
|
||||||
foreach(var path in _searchPaths)
|
foreach(var path in _searchPaths)
|
||||||
{
|
{
|
||||||
string assPath = Path.Combine(path, assemblyName + ".dll");
|
assemblyPath = Path.Combine(path, $"{assemblyName}.dll");
|
||||||
if (File.Exists(assPath))
|
if (File.Exists(assemblyPath))
|
||||||
{
|
{
|
||||||
Debug.Log($"resolve {assemblyName} at {assPath}");
|
Debug.Log($"resolve {assemblyName} at {assemblyPath}");
|
||||||
assemblyPath = assPath;
|
return true;
|
||||||
|
}
|
||||||
|
assemblyPath = Path.Combine(path, $"{assemblyName}.dll.bytes");
|
||||||
|
if (File.Exists(assemblyPath))
|
||||||
|
{
|
||||||
|
Debug.Log($"resolve {assemblyName} at {assemblyPath}");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using dnlib.DotNet;
|
using dnlib.DotNet;
|
||||||
|
using HybridCLR.Editor.ABI;
|
||||||
using HybridCLR.Editor.Meta;
|
using HybridCLR.Editor.Meta;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
using dnlib.DotNet;
|
||||||
|
|
||||||
|
namespace HybridCLR.Editor.MethodBridge
|
||||||
|
{
|
||||||
|
public class CallNativeMethodSignatureInfo
|
||||||
|
{
|
||||||
|
public MethodSig MethodSig { get; set; }
|
||||||
|
|
||||||
|
public CallingConvention? Callvention { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 851c44a8da67a9742a7ea68815383f27
|
guid: d2e4ca0a49975a84a8a72dbc70ec7795
|
||||||
MonoImporter:
|
MonoImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
using dnlib.DotNet;
|
||||||
|
using HybridCLR.Editor.Meta;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace HybridCLR.Editor.MethodBridge
|
||||||
|
{
|
||||||
|
|
||||||
|
public class CalliAnalyzer
|
||||||
|
{
|
||||||
|
private readonly List<ModuleDefMD> _rootModules = new List<ModuleDefMD>();
|
||||||
|
|
||||||
|
private readonly List<CallNativeMethodSignatureInfo> _calliMethodSignatures = new List<CallNativeMethodSignatureInfo>();
|
||||||
|
|
||||||
|
public List<CallNativeMethodSignatureInfo> CalliMethodSignatures => _calliMethodSignatures;
|
||||||
|
|
||||||
|
public CalliAnalyzer(AssemblyCache cache, List<string> assemblyNames)
|
||||||
|
{
|
||||||
|
foreach (var assemblyName in assemblyNames)
|
||||||
|
{
|
||||||
|
_rootModules.Add(cache.LoadModule(assemblyName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CollectCalli()
|
||||||
|
{
|
||||||
|
foreach (var mod in _rootModules)
|
||||||
|
{
|
||||||
|
Debug.Log($"ass:{mod.FullName} methodcount:{mod.Metadata.TablesStream.MethodTable.Rows}");
|
||||||
|
for (uint rid = 1, n = mod.Metadata.TablesStream.MethodTable.Rows; rid <= n; rid++)
|
||||||
|
{
|
||||||
|
var method = mod.ResolveMethod(rid);
|
||||||
|
//Debug.Log($"method:{method}");
|
||||||
|
if (!method.HasBody)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var il in method.Body.Instructions)
|
||||||
|
{
|
||||||
|
if (il.OpCode.Code == dnlib.DotNet.Emit.Code.Calli)
|
||||||
|
{
|
||||||
|
MethodSig methodSig = (MethodSig)il.Operand;
|
||||||
|
|
||||||
|
_calliMethodSignatures.Add(new CallNativeMethodSignatureInfo()
|
||||||
|
{
|
||||||
|
MethodSig = methodSig,
|
||||||
|
});
|
||||||
|
Debug.Log($"method:{method} calli method signature:{methodSig}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Run()
|
||||||
|
{
|
||||||
|
CollectCalli();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 093a9e6c1e7399244bbcd8983fdbfdee
|
guid: 6ce33c8e48da5a649b261ba3a60fd3b9
|
||||||
MonoImporter:
|
MonoImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
|
|
@ -13,6 +13,9 @@ using System.Threading.Tasks;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using TypeInfo = HybridCLR.Editor.ABI.TypeInfo;
|
using TypeInfo = HybridCLR.Editor.ABI.TypeInfo;
|
||||||
|
using CallingConvention = System.Runtime.InteropServices.CallingConvention;
|
||||||
|
using TypeAttributes = dnlib.DotNet.TypeAttributes;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace HybridCLR.Editor.MethodBridge
|
namespace HybridCLR.Editor.MethodBridge
|
||||||
{
|
{
|
||||||
|
|
@ -25,14 +28,46 @@ namespace HybridCLR.Editor.MethodBridge
|
||||||
public string OutputFile { get; set; }
|
public string OutputFile { get; set; }
|
||||||
|
|
||||||
public IReadOnlyCollection<GenericMethod> GenericMethods { get; set; }
|
public IReadOnlyCollection<GenericMethod> GenericMethods { get; set; }
|
||||||
|
|
||||||
|
public List<RawMonoPInvokeCallbackMethodInfo> ReversePInvokeMethods { get; set; }
|
||||||
|
|
||||||
|
public IReadOnlyCollection<CallNativeMethodSignatureInfo> CalliMethodSignatures { get; set; }
|
||||||
|
|
||||||
|
public bool Development { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ABIReversePInvokeMethodInfo
|
||||||
|
{
|
||||||
|
public MethodDesc Method { get; set; }
|
||||||
|
|
||||||
|
public CallingConvention Callvention { get; set; }
|
||||||
|
|
||||||
|
public int Count { get; set; }
|
||||||
|
|
||||||
|
public string Signature { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CalliMethodInfo
|
||||||
|
{
|
||||||
|
public MethodDesc Method { get; set; }
|
||||||
|
|
||||||
|
public CallingConvention Callvention { get; set; }
|
||||||
|
|
||||||
|
public string Signature { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly List<GenericMethod> _genericMethods;
|
private readonly List<GenericMethod> _genericMethods;
|
||||||
|
|
||||||
|
private readonly List<RawMonoPInvokeCallbackMethodInfo> _originalReversePInvokeMethods;
|
||||||
|
|
||||||
|
private readonly List<CallNativeMethodSignatureInfo> _originalCalliMethodSignatures;
|
||||||
|
|
||||||
private readonly string _templateCode;
|
private readonly string _templateCode;
|
||||||
|
|
||||||
private readonly string _outputFile;
|
private readonly string _outputFile;
|
||||||
|
|
||||||
|
private readonly bool _development;
|
||||||
|
|
||||||
private readonly TypeCreator _typeCreator;
|
private readonly TypeCreator _typeCreator;
|
||||||
|
|
||||||
private readonly HashSet<MethodDesc> _managed2nativeMethodSet = new HashSet<MethodDesc>();
|
private readonly HashSet<MethodDesc> _managed2nativeMethodSet = new HashSet<MethodDesc>();
|
||||||
|
|
@ -41,15 +76,22 @@ namespace HybridCLR.Editor.MethodBridge
|
||||||
|
|
||||||
private readonly HashSet<MethodDesc> _adjustThunkMethodSet = new HashSet<MethodDesc>();
|
private readonly HashSet<MethodDesc> _adjustThunkMethodSet = new HashSet<MethodDesc>();
|
||||||
|
|
||||||
|
private List<ABIReversePInvokeMethodInfo> _reversePInvokeMethods;
|
||||||
|
|
||||||
|
private List<CalliMethodInfo> _callidMethods;
|
||||||
|
|
||||||
public Generator(Options options)
|
public Generator(Options options)
|
||||||
{
|
{
|
||||||
List<(GenericMethod, string)> genericMethodInfo = options.GenericMethods.Select(m => (m, m.ToString())).ToList();
|
List<(GenericMethod, string)> genericMethodInfo = options.GenericMethods.Select(m => (m, m.ToString())).ToList();
|
||||||
genericMethodInfo.Sort((a, b) => string.CompareOrdinal(a.Item2, b.Item2));
|
genericMethodInfo.Sort((a, b) => string.CompareOrdinal(a.Item2, b.Item2));
|
||||||
_genericMethods = genericMethodInfo.Select(m => m.Item1).ToList();
|
_genericMethods = genericMethodInfo.Select(m => m.Item1).ToList();
|
||||||
|
_originalReversePInvokeMethods = options.ReversePInvokeMethods;
|
||||||
|
_originalCalliMethodSignatures = options.CalliMethodSignatures.ToList();
|
||||||
|
|
||||||
_templateCode = options.TemplateCode;
|
_templateCode = options.TemplateCode;
|
||||||
_outputFile = options.OutputFile;
|
_outputFile = options.OutputFile;
|
||||||
_typeCreator = new TypeCreator();
|
_typeCreator = new TypeCreator();
|
||||||
|
_development = options.Development;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly Dictionary<string, TypeInfo> _sig2Types = new Dictionary<string, TypeInfo>();
|
private readonly Dictionary<string, TypeInfo> _sig2Types = new Dictionary<string, TypeInfo>();
|
||||||
|
|
@ -98,6 +140,30 @@ namespace HybridCLR.Editor.MethodBridge
|
||||||
return mbs;
|
return mbs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private MethodDesc CreateMethodDesc(TypeSig returnType, List<TypeSig> parameters)
|
||||||
|
{
|
||||||
|
var paramInfos = new List<ParamInfo>();
|
||||||
|
if (returnType.ContainsGenericParameter)
|
||||||
|
{
|
||||||
|
throw new Exception($"[PreservedMethod] method has generic parameters");
|
||||||
|
}
|
||||||
|
foreach (var paramInfo in parameters)
|
||||||
|
{
|
||||||
|
if (paramInfo.ContainsGenericParameter)
|
||||||
|
{
|
||||||
|
throw new Exception($"[PreservedMethod] method has generic parameters");
|
||||||
|
}
|
||||||
|
paramInfos.Add(new ParamInfo() { Type = GetSharedTypeInfo(paramInfo) });
|
||||||
|
}
|
||||||
|
var mbs = new MethodDesc()
|
||||||
|
{
|
||||||
|
MethodDef = null,
|
||||||
|
ReturnInfo = new ReturnInfo() { Type = returnType != null ? GetSharedTypeInfo(returnType) : TypeInfo.s_void },
|
||||||
|
ParamInfos = paramInfos,
|
||||||
|
};
|
||||||
|
return mbs;
|
||||||
|
}
|
||||||
|
|
||||||
private void AddManaged2NativeMethod(MethodDesc method)
|
private void AddManaged2NativeMethod(MethodDesc method)
|
||||||
{
|
{
|
||||||
method.Init();
|
method.Init();
|
||||||
|
|
@ -167,12 +233,23 @@ namespace HybridCLR.Editor.MethodBridge
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PrepareMethods()
|
private void PrepareMethodBridges()
|
||||||
{
|
{
|
||||||
foreach(var method in _genericMethods)
|
foreach (var method in _genericMethods)
|
||||||
{
|
{
|
||||||
ProcessMethod(method.Method, method.KlassInst, method.MethodInst);
|
ProcessMethod(method.Method, method.KlassInst, method.MethodInst);
|
||||||
}
|
}
|
||||||
|
foreach (var reversePInvokeMethod in _originalReversePInvokeMethods)
|
||||||
|
{
|
||||||
|
MethodDef method = reversePInvokeMethod.Method;
|
||||||
|
ICorLibTypes corLibTypes = method.Module.CorLibTypes;
|
||||||
|
|
||||||
|
var returnType = MetaUtil.ToShareTypeSig(corLibTypes, method.ReturnType);
|
||||||
|
var parameters = method.Parameters.Select(p => MetaUtil.ToShareTypeSig(corLibTypes, p.Type)).ToList();
|
||||||
|
var sharedMethod = CreateMethodDesc(method, true, returnType, parameters);
|
||||||
|
sharedMethod.Init();
|
||||||
|
AddNative2ManagedMethod(sharedMethod);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void CheckUnique(IEnumerable<string> names)
|
static void CheckUnique(IEnumerable<string> names)
|
||||||
|
|
@ -210,6 +287,7 @@ namespace HybridCLR.Editor.MethodBridge
|
||||||
CollectStructDefs(_managed2NativeMethodList0, structTypeSet);
|
CollectStructDefs(_managed2NativeMethodList0, structTypeSet);
|
||||||
CollectStructDefs(_native2ManagedMethodList0, structTypeSet);
|
CollectStructDefs(_native2ManagedMethodList0, structTypeSet);
|
||||||
CollectStructDefs(_adjustThunkMethodList0, structTypeSet);
|
CollectStructDefs(_adjustThunkMethodList0, structTypeSet);
|
||||||
|
CollectStructDefs(_originalCalliMethodSignatures.Select(m => m.MethodSig).ToList(), structTypeSet);
|
||||||
_structTypes0 = structTypeSet.ToList();
|
_structTypes0 = structTypeSet.ToList();
|
||||||
_structTypes0.Sort((a, b) => a.TypeId - b.TypeId);
|
_structTypes0.Sort((a, b) => a.TypeId - b.TypeId);
|
||||||
|
|
||||||
|
|
@ -220,39 +298,114 @@ namespace HybridCLR.Editor.MethodBridge
|
||||||
_managed2NativeMethodList0.Count, _native2ManagedMethodList0.Count, _adjustThunkMethodList0.Count, _structTypes0.Count);
|
_managed2NativeMethodList0.Count, _native2ManagedMethodList0.Count, _adjustThunkMethodList0.Count, _structTypes0.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class AnalyzeFieldInfo
|
||||||
|
{
|
||||||
|
public FieldDef field;
|
||||||
|
|
||||||
|
public TypeInfo type;
|
||||||
|
}
|
||||||
|
|
||||||
private class AnalyzeTypeInfo
|
private class AnalyzeTypeInfo
|
||||||
{
|
{
|
||||||
public TypeInfo toSharedType;
|
public TypeInfo isoType;
|
||||||
public List<TypeInfo> fields;
|
public List<AnalyzeFieldInfo> fields;
|
||||||
public string signature;
|
public string signature;
|
||||||
|
public uint originalPackingSize;
|
||||||
|
public uint packingSize;
|
||||||
|
public uint classSize;
|
||||||
|
public LayoutKind layout;
|
||||||
|
public bool blittable;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly Dictionary<TypeInfo, AnalyzeTypeInfo> _analyzeTypeInfos = new Dictionary<TypeInfo, AnalyzeTypeInfo>();
|
private readonly Dictionary<TypeInfo, AnalyzeTypeInfo> _analyzeTypeInfos = new Dictionary<TypeInfo, AnalyzeTypeInfo>();
|
||||||
|
|
||||||
private readonly Dictionary<string, TypeInfo> _signature2Type = new Dictionary<string, TypeInfo>();
|
private readonly Dictionary<string, TypeInfo> _signature2Type = new Dictionary<string, TypeInfo>();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private bool IsBlittable(TypeSig typeSig)
|
||||||
|
{
|
||||||
|
typeSig = typeSig.RemovePinnedAndModifiers();
|
||||||
|
if (typeSig.IsByRef)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
switch (typeSig.ElementType)
|
||||||
|
{
|
||||||
|
case ElementType.Void: return false;
|
||||||
|
case ElementType.Boolean:
|
||||||
|
case ElementType.I1:
|
||||||
|
case ElementType.U1:
|
||||||
|
case ElementType.I2:
|
||||||
|
case ElementType.Char:
|
||||||
|
case ElementType.U2:
|
||||||
|
case ElementType.I4:
|
||||||
|
case ElementType.U4:
|
||||||
|
case ElementType.I8:
|
||||||
|
case ElementType.U8:
|
||||||
|
case ElementType.R4:
|
||||||
|
case ElementType.R8:
|
||||||
|
case ElementType.I:
|
||||||
|
case ElementType.U:
|
||||||
|
case ElementType.Ptr:
|
||||||
|
case ElementType.ByRef:
|
||||||
|
case ElementType.FnPtr:
|
||||||
|
case ElementType.TypedByRef: return true;
|
||||||
|
case ElementType.String:
|
||||||
|
case ElementType.Class:
|
||||||
|
case ElementType.Array:
|
||||||
|
case ElementType.SZArray:
|
||||||
|
case ElementType.Object:
|
||||||
|
case ElementType.Module:
|
||||||
|
case ElementType.Var:
|
||||||
|
case ElementType.MVar: return false;
|
||||||
|
case ElementType.ValueType:
|
||||||
|
{
|
||||||
|
TypeDef typeDef = typeSig.ToTypeDefOrRef().ResolveTypeDef();
|
||||||
|
if (typeDef == null)
|
||||||
|
{
|
||||||
|
throw new Exception($"type:{typeSig} definition could not be found. Please try `HybridCLR/Genergate/LinkXml`, then Build once to generate the AOT dll, and then regenerate the bridge function");
|
||||||
|
}
|
||||||
|
if (typeDef.IsEnum)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return CalculateAnalyzeTypeInfoBasic(GetSharedTypeInfo(typeSig)).blittable;
|
||||||
|
}
|
||||||
|
case ElementType.GenericInst:
|
||||||
|
{
|
||||||
|
GenericInstSig gis = (GenericInstSig)typeSig;
|
||||||
|
if (!gis.GenericType.IsValueType)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
TypeDef typeDef = gis.GenericType.ToTypeDefOrRef().ResolveTypeDef();
|
||||||
|
if (typeDef.IsEnum)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return CalculateAnalyzeTypeInfoBasic(GetSharedTypeInfo(typeSig)).blittable;
|
||||||
|
}
|
||||||
|
default: throw new NotSupportedException($"{typeSig.ElementType}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private AnalyzeTypeInfo CalculateAnalyzeTypeInfoBasic(TypeInfo typeInfo)
|
private AnalyzeTypeInfo CalculateAnalyzeTypeInfoBasic(TypeInfo typeInfo)
|
||||||
{
|
{
|
||||||
|
Debug.Assert(typeInfo.IsStruct);
|
||||||
|
if (_analyzeTypeInfos.TryGetValue(typeInfo, out var ati))
|
||||||
|
{
|
||||||
|
return ati;
|
||||||
|
}
|
||||||
TypeSig type = typeInfo.Klass;
|
TypeSig type = typeInfo.Klass;
|
||||||
TypeDef typeDef = type.ToTypeDefOrRef().ResolveTypeDefThrow();
|
TypeDef typeDef = type.ToTypeDefOrRef().ResolveTypeDefThrow();
|
||||||
|
|
||||||
List<TypeSig> klassInst = type.ToGenericInstSig()?.GenericArguments?.ToList();
|
List<TypeSig> klassInst = type.ToGenericInstSig()?.GenericArguments?.ToList();
|
||||||
GenericArgumentContext ctx = klassInst != null ? new GenericArgumentContext(klassInst, null) : null;
|
GenericArgumentContext ctx = klassInst != null ? new GenericArgumentContext(klassInst, null) : null;
|
||||||
|
|
||||||
ClassLayout sa = typeDef.ClassLayout;
|
var fields = new List<AnalyzeFieldInfo>();
|
||||||
var analyzeTypeInfo = new AnalyzeTypeInfo();
|
|
||||||
|
|
||||||
// don't share type with explicit layout
|
|
||||||
if (sa != null)
|
|
||||||
{
|
|
||||||
analyzeTypeInfo.toSharedType = typeInfo;
|
|
||||||
analyzeTypeInfo.signature = typeInfo.CreateSigName();
|
|
||||||
_signature2Type.Add(analyzeTypeInfo.signature, typeInfo);
|
|
||||||
return analyzeTypeInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
var fields = analyzeTypeInfo.fields = new List<TypeInfo>();
|
|
||||||
|
|
||||||
|
bool blittable = true;
|
||||||
foreach (FieldDef field in typeDef.Fields)
|
foreach (FieldDef field in typeDef.Fields)
|
||||||
{
|
{
|
||||||
if (field.IsStatic)
|
if (field.IsStatic)
|
||||||
|
|
@ -260,8 +413,39 @@ namespace HybridCLR.Editor.MethodBridge
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
TypeSig fieldType = ctx != null ? MetaUtil.Inflate(field.FieldType, ctx) : field.FieldType;
|
TypeSig fieldType = ctx != null ? MetaUtil.Inflate(field.FieldType, ctx) : field.FieldType;
|
||||||
fields.Add(GetSharedTypeInfo(fieldType));
|
blittable &= IsBlittable(fieldType);
|
||||||
|
TypeInfo sharedFieldTypeInfo = GetSharedTypeInfo(fieldType);
|
||||||
|
TypeInfo isoType = ToIsomorphicType(sharedFieldTypeInfo);
|
||||||
|
fields.Add(new AnalyzeFieldInfo { field = field, type = isoType });
|
||||||
}
|
}
|
||||||
|
//analyzeTypeInfo.blittable = blittable;
|
||||||
|
//analyzeTypeInfo.packingSize = blittable ? analyzeTypeInfo.originalPackingSize : 0;
|
||||||
|
|
||||||
|
ClassLayout sa = typeDef.ClassLayout;
|
||||||
|
uint originalPackingSize = sa?.PackingSize ?? 0;
|
||||||
|
var analyzeTypeInfo = new AnalyzeTypeInfo()
|
||||||
|
{
|
||||||
|
originalPackingSize = originalPackingSize,
|
||||||
|
packingSize = blittable && !typeDef.IsAutoLayout ? originalPackingSize : 0,
|
||||||
|
classSize = sa?.ClassSize ?? 0,
|
||||||
|
layout = typeDef.IsAutoLayout ? LayoutKind.Auto : (typeDef.IsExplicitLayout ? LayoutKind.Explicit : LayoutKind.Sequential),
|
||||||
|
fields = fields,
|
||||||
|
blittable = blittable,
|
||||||
|
};
|
||||||
|
_analyzeTypeInfos.Add(typeInfo, analyzeTypeInfo);
|
||||||
|
analyzeTypeInfo.signature = GetOrCalculateTypeInfoSignature(typeInfo);
|
||||||
|
|
||||||
|
if (_signature2Type.TryGetValue(analyzeTypeInfo.signature, out var sharedType))
|
||||||
|
{
|
||||||
|
// Debug.Log($"[ToIsomorphicType] type:{type.Klass} ==> sharedType:{sharedType.Klass} signature:{signature} ");
|
||||||
|
analyzeTypeInfo.isoType = sharedType;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
analyzeTypeInfo.isoType = typeInfo;
|
||||||
|
_signature2Type.Add(analyzeTypeInfo.signature, typeInfo);
|
||||||
|
}
|
||||||
|
|
||||||
return analyzeTypeInfo;
|
return analyzeTypeInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -286,9 +470,14 @@ namespace HybridCLR.Editor.MethodBridge
|
||||||
}
|
}
|
||||||
|
|
||||||
var sigBuf = new StringBuilder();
|
var sigBuf = new StringBuilder();
|
||||||
|
if (ati.packingSize != 0 || ati.classSize != 0 || ati.layout != LayoutKind.Sequential || !ati.blittable)
|
||||||
|
{
|
||||||
|
sigBuf.Append($"[{ati.classSize}|{ati.packingSize}|{ati.layout}|{(ati.blittable ? 0 : 1)}]");
|
||||||
|
}
|
||||||
foreach (var field in ati.fields)
|
foreach (var field in ati.fields)
|
||||||
{
|
{
|
||||||
sigBuf.Append(GetOrCalculateTypeInfoSignature(ToIsomorphicType(field)));
|
string fieldOffset = field.field.FieldOffset != null ? field.field.FieldOffset.ToString() + "|" : "";
|
||||||
|
sigBuf.Append("{" + fieldOffset + GetOrCalculateTypeInfoSignature(ToIsomorphicType(field.type)) + "}");
|
||||||
}
|
}
|
||||||
return ati.signature = sigBuf.ToString();
|
return ati.signature = sigBuf.ToString();
|
||||||
}
|
}
|
||||||
|
|
@ -299,27 +488,7 @@ namespace HybridCLR.Editor.MethodBridge
|
||||||
{
|
{
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
if (!_analyzeTypeInfos.TryGetValue(type, out var ati))
|
return CalculateAnalyzeTypeInfoBasic(type).isoType;
|
||||||
{
|
|
||||||
ati = CalculateAnalyzeTypeInfoBasic(type);
|
|
||||||
_analyzeTypeInfos.Add(type, ati);
|
|
||||||
}
|
|
||||||
if (ati.toSharedType == null)
|
|
||||||
{
|
|
||||||
string signature = GetOrCalculateTypeInfoSignature(type);
|
|
||||||
Debug.Assert(signature == ati.signature);
|
|
||||||
if (_signature2Type.TryGetValue(signature, out var sharedType))
|
|
||||||
{
|
|
||||||
// Debug.Log($"[ToIsomorphicType] type:{type.Klass} ==> sharedType:{sharedType.Klass} signature:{signature} ");
|
|
||||||
ati.toSharedType = sharedType;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ati.toSharedType = type;
|
|
||||||
_signature2Type.Add(signature, type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ati.toSharedType;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private MethodDesc ToIsomorphicMethod(MethodDesc method)
|
private MethodDesc ToIsomorphicMethod(MethodDesc method)
|
||||||
|
|
@ -370,11 +539,131 @@ namespace HybridCLR.Editor.MethodBridge
|
||||||
return methodMap.Values.ToList();
|
return methodMap.Values.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private static string MakeReversePInvokeSignature(MethodDesc desc, CallingConvention CallingConventionention)
|
||||||
|
{
|
||||||
|
string convStr = ((char)('A' + (int)CallingConventionention - 1)).ToString();
|
||||||
|
return $"{convStr}{desc.Sig}";
|
||||||
|
}
|
||||||
|
private static string MakeCalliSignature(MethodDesc desc, CallingConvention CallingConventionention)
|
||||||
|
{
|
||||||
|
string convStr = ((char)('A' + Math.Max((int)CallingConventionention - 1, 0))).ToString();
|
||||||
|
return $"{convStr}{desc.Sig}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CallingConvention GetCallingConvention(MethodDef method)
|
||||||
|
{
|
||||||
|
var monoPInvokeCallbackAttr = method.CustomAttributes.FirstOrDefault(ca => ca.AttributeType.Name == "MonoPInvokeCallbackAttribute");
|
||||||
|
if (monoPInvokeCallbackAttr == null)
|
||||||
|
{
|
||||||
|
return CallingConvention.Winapi;
|
||||||
|
}
|
||||||
|
object delegateTypeSig = monoPInvokeCallbackAttr.ConstructorArguments[0].Value;
|
||||||
|
|
||||||
|
TypeDef delegateTypeDef;
|
||||||
|
if (delegateTypeSig is ClassSig classSig)
|
||||||
|
{
|
||||||
|
delegateTypeDef = classSig.TypeDefOrRef.ResolveTypeDefThrow();
|
||||||
|
}
|
||||||
|
else if (delegateTypeSig is GenericInstSig genericInstSig)
|
||||||
|
{
|
||||||
|
delegateTypeDef = genericInstSig.GenericType.TypeDefOrRef.ResolveTypeDefThrow();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
delegateTypeDef = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delegateTypeDef == null)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException($"Unsupported delegate type: {delegateTypeSig}");
|
||||||
|
}
|
||||||
|
var attr = delegateTypeDef.CustomAttributes.FirstOrDefault(ca => ca.AttributeType.FullName == "System.Runtime.InteropServices.UnmanagedFunctionPointerAttribute");
|
||||||
|
if (attr == null)
|
||||||
|
{
|
||||||
|
return CallingConvention.Winapi;
|
||||||
|
}
|
||||||
|
var conv = attr.ConstructorArguments[0].Value;
|
||||||
|
return (CallingConvention)conv;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ABIReversePInvokeMethodInfo> BuildABIMethods(List<RawMonoPInvokeCallbackMethodInfo> rawMethods)
|
||||||
|
{
|
||||||
|
var methodsBySig = new Dictionary<string, ABIReversePInvokeMethodInfo>();
|
||||||
|
foreach (var method in rawMethods)
|
||||||
|
{
|
||||||
|
var sharedMethod = new MethodDesc
|
||||||
|
{
|
||||||
|
MethodDef = method.Method,
|
||||||
|
ReturnInfo = new ReturnInfo { Type = GetSharedTypeInfo(method.Method.ReturnType) },
|
||||||
|
ParamInfos = method.Method.Parameters.Select(p => new ParamInfo { Type = GetSharedTypeInfo(p.Type) }).ToList(),
|
||||||
|
};
|
||||||
|
sharedMethod.Init();
|
||||||
|
sharedMethod = ToIsomorphicMethod(sharedMethod);
|
||||||
|
|
||||||
|
CallingConvention callingConv = GetCallingConvention(method.Method);
|
||||||
|
string signature = MakeReversePInvokeSignature(sharedMethod, callingConv);
|
||||||
|
|
||||||
|
if (!methodsBySig.TryGetValue(signature, out var arm))
|
||||||
|
{
|
||||||
|
arm = new ABIReversePInvokeMethodInfo()
|
||||||
|
{
|
||||||
|
Method = sharedMethod,
|
||||||
|
Signature = signature,
|
||||||
|
Count = 0,
|
||||||
|
Callvention = callingConv,
|
||||||
|
};
|
||||||
|
methodsBySig.Add(signature, arm);
|
||||||
|
}
|
||||||
|
int preserveCount = method.GenerationAttribute != null ? (int)method.GenerationAttribute.ConstructorArguments[0].Value : 1;
|
||||||
|
arm.Count += preserveCount;
|
||||||
|
}
|
||||||
|
var newMethods = methodsBySig.Values.ToList();
|
||||||
|
newMethods.Sort((a, b) => string.CompareOrdinal(a.Signature, b.Signature));
|
||||||
|
return newMethods;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<CalliMethodInfo> BuildCalliMethods(List<CallNativeMethodSignatureInfo> rawMethods)
|
||||||
|
{
|
||||||
|
var methodsBySig = new Dictionary<string, CalliMethodInfo>();
|
||||||
|
foreach (var method in rawMethods)
|
||||||
|
{
|
||||||
|
var sharedMethod = new MethodDesc
|
||||||
|
{
|
||||||
|
MethodDef = null,
|
||||||
|
ReturnInfo = new ReturnInfo { Type = GetSharedTypeInfo(method.MethodSig.RetType) },
|
||||||
|
ParamInfos = method.MethodSig.Params.Select(p => new ParamInfo { Type = GetSharedTypeInfo(p) }).ToList(),
|
||||||
|
};
|
||||||
|
sharedMethod.Init();
|
||||||
|
sharedMethod = ToIsomorphicMethod(sharedMethod);
|
||||||
|
|
||||||
|
CallingConvention callingConv = (CallingConvention)((int)((method.Callvention ?? method.MethodSig.CallingConvention) & dnlib.DotNet.CallingConvention.Mask) + 1);
|
||||||
|
string signature = MakeCalliSignature(sharedMethod, callingConv);
|
||||||
|
|
||||||
|
if (!methodsBySig.TryGetValue(signature, out var arm))
|
||||||
|
{
|
||||||
|
arm = new CalliMethodInfo()
|
||||||
|
{
|
||||||
|
Method = sharedMethod,
|
||||||
|
Signature = signature,
|
||||||
|
Callvention = callingConv,
|
||||||
|
};
|
||||||
|
methodsBySig.Add(signature, arm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var newMethods = methodsBySig.Values.ToList();
|
||||||
|
newMethods.Sort((a, b) => string.CompareOrdinal(a.Signature, b.Signature));
|
||||||
|
return newMethods;
|
||||||
|
}
|
||||||
|
|
||||||
private void BuildOptimizedMethods()
|
private void BuildOptimizedMethods()
|
||||||
{
|
{
|
||||||
_managed2NativeMethodList = ToUniqueOrderedList(_managed2NativeMethodList0);
|
_managed2NativeMethodList = ToUniqueOrderedList(_managed2NativeMethodList0);
|
||||||
_native2ManagedMethodList = ToUniqueOrderedList(_native2ManagedMethodList0);
|
_native2ManagedMethodList = ToUniqueOrderedList(_native2ManagedMethodList0);
|
||||||
_adjustThunkMethodList = ToUniqueOrderedList(_adjustThunkMethodList0);
|
_adjustThunkMethodList = ToUniqueOrderedList(_adjustThunkMethodList0);
|
||||||
|
_reversePInvokeMethods = BuildABIMethods(_originalReversePInvokeMethods);
|
||||||
|
_callidMethods = BuildCalliMethods(_originalCalliMethodSignatures);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OptimizationTypesAndMethods()
|
private void OptimizationTypesAndMethods()
|
||||||
|
|
@ -389,10 +678,15 @@ namespace HybridCLR.Editor.MethodBridge
|
||||||
{
|
{
|
||||||
var frr = new FileRegionReplace(_templateCode);
|
var frr = new FileRegionReplace(_templateCode);
|
||||||
|
|
||||||
List<string> lines = new List<string>(20_0000);
|
List<string> lines = new List<string>(20_0000)
|
||||||
|
{
|
||||||
|
"\n",
|
||||||
|
$"// DEVELOPMENT={(_development ? 1 : 0)}",
|
||||||
|
"\n"
|
||||||
|
};
|
||||||
|
|
||||||
var classInfos = new List<ClassInfo>();
|
var classInfos = new List<ClassInfo>();
|
||||||
var classTypeSet = new HashSet<TypeInfo>();
|
var classTypeSet = new Dictionary<TypeInfo, ClassInfo>();
|
||||||
foreach (var type in structTypes)
|
foreach (var type in structTypes)
|
||||||
{
|
{
|
||||||
GenerateClassInfo(type, classTypeSet, classInfos);
|
GenerateClassInfo(type, classTypeSet, classInfos);
|
||||||
|
|
@ -424,6 +718,15 @@ namespace HybridCLR.Editor.MethodBridge
|
||||||
|
|
||||||
GenerateAdjustThunkStub(_adjustThunkMethodList, lines);
|
GenerateAdjustThunkStub(_adjustThunkMethodList, lines);
|
||||||
|
|
||||||
|
GenerateReversePInvokeWrappers(_reversePInvokeMethods, lines);
|
||||||
|
|
||||||
|
foreach (var method in _callidMethods)
|
||||||
|
{
|
||||||
|
GenerateManaged2NativeFunctionPointerMethod(method, lines);
|
||||||
|
}
|
||||||
|
GenerateManaged2NativeFunctionPointerMethodStub(_callidMethods, lines);
|
||||||
|
|
||||||
|
|
||||||
frr.Replace("CODE", string.Join("\n", lines));
|
frr.Replace("CODE", string.Join("\n", lines));
|
||||||
|
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(_outputFile));
|
Directory.CreateDirectory(Path.GetDirectoryName(_outputFile));
|
||||||
|
|
@ -431,8 +734,69 @@ namespace HybridCLR.Editor.MethodBridge
|
||||||
frr.Commit(_outputFile);
|
frr.Commit(_outputFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string GetIl2cppCallConventionName(CallingConvention conv)
|
||||||
|
{
|
||||||
|
switch (conv)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
case CallingConvention.Winapi:
|
||||||
|
return "DEFAULT_CALL";
|
||||||
|
case CallingConvention.Cdecl:
|
||||||
|
return "CDECL";
|
||||||
|
case CallingConvention.StdCall:
|
||||||
|
return "STDCALL";
|
||||||
|
case CallingConvention.ThisCall:
|
||||||
|
return "THISCALL";
|
||||||
|
case CallingConvention.FastCall:
|
||||||
|
return "FASTCALL";
|
||||||
|
default:
|
||||||
|
throw new NotSupportedException($"Unsupported CallingConvention {conv}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GenerateReversePInvokeWrappers(List<ABIReversePInvokeMethodInfo> methods, List<string> lines)
|
||||||
|
{
|
||||||
|
int methodIndex = 0;
|
||||||
|
var stubCodes = new List<string>();
|
||||||
|
foreach (var methodInfo in methods)
|
||||||
|
{
|
||||||
|
MethodDesc method = methodInfo.Method;
|
||||||
|
string il2cppCallConventionName = GetIl2cppCallConventionName(methodInfo.Callvention);
|
||||||
|
string paramDeclaringListWithoutMethodInfoStr = string.Join(", ", method.ParamInfos.Select(p => $"{p.Type.GetTypeName()} __arg{p.Index}"));
|
||||||
|
string paramNameListWithoutMethodInfoStr = string.Join(", ", method.ParamInfos.Select(p => $"__arg{p.Index}").Concat(new string[] { "method" }));
|
||||||
|
string paramTypeListWithMethodInfoStr = string.Join(", ", method.ParamInfos.Select(p => $"{p.Type.GetTypeName()}").Concat(new string[] { "const MethodInfo*" }));
|
||||||
|
string methodTypeDef = $"typedef {method.ReturnInfo.Type.GetTypeName()} (*Callback)({paramTypeListWithMethodInfoStr})";
|
||||||
|
for (int i = 0; i < methodInfo.Count; i++, methodIndex++)
|
||||||
|
{
|
||||||
|
lines.Add($@"
|
||||||
|
{method.ReturnInfo.Type.GetTypeName()} {il2cppCallConventionName} __ReversePInvokeMethod_{methodIndex}({paramDeclaringListWithoutMethodInfoStr})
|
||||||
|
{{
|
||||||
|
il2cpp::vm::ScopedThreadAttacher _vmThreadHelper;
|
||||||
|
const MethodInfo* method = InterpreterModule::GetMethodInfoByReversePInvokeWrapperIndex({methodIndex});
|
||||||
|
{methodTypeDef};
|
||||||
|
{(method.ReturnInfo.IsVoid ? "" : "return ")}((Callback)(method->methodPointerCallByInterp))({paramNameListWithoutMethodInfoStr});
|
||||||
|
}}
|
||||||
|
");
|
||||||
|
stubCodes.Add($"\t{{\"{methodInfo.Signature}\", (Il2CppMethodPointer)__ReversePInvokeMethod_{methodIndex}}},");
|
||||||
|
}
|
||||||
|
Debug.Log($"[ReversePInvokeWrap.Generator] method:{method.MethodDef} wrapperCount:{methodInfo.Count}");
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.Add(@"
|
||||||
|
const ReversePInvokeMethodData hybridclr::interpreter::g_reversePInvokeMethodStub[]
|
||||||
|
{
|
||||||
|
");
|
||||||
|
lines.AddRange(stubCodes);
|
||||||
|
|
||||||
|
lines.Add(@"
|
||||||
|
{nullptr, nullptr},
|
||||||
|
};
|
||||||
|
");
|
||||||
|
}
|
||||||
|
|
||||||
public void Generate()
|
public void Generate()
|
||||||
{
|
{
|
||||||
|
PrepareMethodBridges();
|
||||||
CollectTypesAndMethods();
|
CollectTypesAndMethods();
|
||||||
OptimizationTypesAndMethods();
|
OptimizationTypesAndMethods();
|
||||||
GenerateCode();
|
GenerateCode();
|
||||||
|
|
@ -466,6 +830,38 @@ namespace HybridCLR.Editor.MethodBridge
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void CollectStructDefs(List<MethodSig> methods, HashSet<TypeInfo> structTypes)
|
||||||
|
{
|
||||||
|
ICorLibTypes corLibTypes = _genericMethods[0].Method.Module.CorLibTypes;
|
||||||
|
|
||||||
|
foreach (var method in methods)
|
||||||
|
{
|
||||||
|
foreach (var paramInfo in method.Params)
|
||||||
|
{
|
||||||
|
var paramType = GetSharedTypeInfo(MetaUtil.ToShareTypeSig(corLibTypes, paramInfo));
|
||||||
|
if (paramType.IsStruct)
|
||||||
|
{
|
||||||
|
structTypes.Add(paramType);
|
||||||
|
if (paramType.Klass.ContainsGenericParameter)
|
||||||
|
{
|
||||||
|
throw new Exception($"[CollectStructDefs] method:{method} type:{paramType.Klass} contains generic parameter");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
var returnType = GetSharedTypeInfo(MetaUtil.ToShareTypeSig(corLibTypes, method.RetType));
|
||||||
|
if (returnType.IsStruct)
|
||||||
|
{
|
||||||
|
structTypes.Add(returnType);
|
||||||
|
if (returnType.Klass.ContainsGenericParameter)
|
||||||
|
{
|
||||||
|
throw new Exception($"[CollectStructDefs] method:{method} type:{returnType.Klass} contains generic parameter");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
class FieldInfo
|
class FieldInfo
|
||||||
{
|
{
|
||||||
public FieldDef field;
|
public FieldDef field;
|
||||||
|
|
@ -475,47 +871,68 @@ namespace HybridCLR.Editor.MethodBridge
|
||||||
class ClassInfo
|
class ClassInfo
|
||||||
{
|
{
|
||||||
public TypeInfo type;
|
public TypeInfo type;
|
||||||
|
public List<AnalyzeFieldInfo> fields;
|
||||||
public TypeDef typeDef;
|
public uint packingSize;
|
||||||
|
public uint classSize;
|
||||||
public List<FieldInfo> fields = new List<FieldInfo>();
|
public LayoutKind layout;
|
||||||
|
public bool blittable;
|
||||||
public ClassLayout layout;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GenerateClassInfo(TypeInfo type, HashSet<TypeInfo> typeSet, List<ClassInfo> classInfos)
|
private void GenerateClassInfo(TypeInfo type, Dictionary<TypeInfo, ClassInfo> typeSet, List<ClassInfo> classInfos)
|
||||||
{
|
{
|
||||||
if (!typeSet.Add(type))
|
if (typeSet.ContainsKey(type))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
TypeSig typeSig = type.Klass;
|
|
||||||
var fields = new List<FieldInfo>();
|
|
||||||
|
|
||||||
TypeDef typeDef = typeSig.ToTypeDefOrRef().ResolveTypeDefThrow();
|
AnalyzeTypeInfo ati = CalculateAnalyzeTypeInfoBasic(type);
|
||||||
|
//TypeSig typeSig = type.Klass;
|
||||||
|
//var fields = new List<FieldInfo>();
|
||||||
|
|
||||||
List<TypeSig> klassInst = typeSig.ToGenericInstSig()?.GenericArguments?.ToList();
|
//TypeDef typeDef = typeSig.ToTypeDefOrRef().ResolveTypeDefThrow();
|
||||||
GenericArgumentContext ctx = klassInst != null ? new GenericArgumentContext(klassInst, null) : null;
|
|
||||||
|
|
||||||
ClassLayout sa = typeDef.ClassLayout;
|
//List<TypeSig> klassInst = typeSig.ToGenericInstSig()?.GenericArguments?.ToList();
|
||||||
|
//GenericArgumentContext ctx = klassInst != null ? new GenericArgumentContext(klassInst, null) : null;
|
||||||
|
|
||||||
ICorLibTypes corLibTypes = typeDef.Module.CorLibTypes;
|
//ClassLayout sa = typeDef.ClassLayout;
|
||||||
foreach (FieldDef field in typeDef.Fields)
|
|
||||||
|
//ICorLibTypes corLibTypes = typeDef.Module.CorLibTypes;
|
||||||
|
//bool blittable = true;
|
||||||
|
//foreach (FieldDef field in typeDef.Fields)
|
||||||
|
//{
|
||||||
|
// if (field.IsStatic)
|
||||||
|
// {
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
// TypeSig fieldType = ctx != null ? MetaUtil.Inflate(field.FieldType, ctx) : field.FieldType;
|
||||||
|
// fieldType = MetaUtil.ToShareTypeSig(corLibTypes, fieldType);
|
||||||
|
// var fieldTypeInfo = ToIsomorphicType(GetSharedTypeInfo(fieldType));
|
||||||
|
// if (fieldTypeInfo.IsStruct)
|
||||||
|
// {
|
||||||
|
// GenerateClassInfo(fieldTypeInfo, typeSet, classInfos);
|
||||||
|
// }
|
||||||
|
// blittable &= IsBlittable(fieldType, fieldTypeInfo, typeSet);
|
||||||
|
// fields.Add(new FieldInfo { field = field, type = fieldTypeInfo });
|
||||||
|
//}
|
||||||
|
|
||||||
|
foreach (var field in ati.fields)
|
||||||
{
|
{
|
||||||
if (field.IsStatic)
|
if (field.type.IsStruct)
|
||||||
{
|
{
|
||||||
continue;
|
GenerateClassInfo(field.type, typeSet, classInfos);
|
||||||
}
|
}
|
||||||
TypeSig fieldType = ctx != null ? MetaUtil.Inflate(field.FieldType, ctx) : field.FieldType;
|
|
||||||
fieldType = MetaUtil.ToShareTypeSig(corLibTypes, fieldType);
|
|
||||||
var fieldTypeInfo = ToIsomorphicType(_typeCreator.CreateTypeInfo(fieldType));
|
|
||||||
if (fieldTypeInfo.IsStruct)
|
|
||||||
{
|
|
||||||
GenerateClassInfo(fieldTypeInfo, typeSet, classInfos);
|
|
||||||
}
|
|
||||||
fields.Add(new FieldInfo { field = field, type = fieldTypeInfo });
|
|
||||||
}
|
}
|
||||||
classInfos.Add(new ClassInfo() { type = type, typeDef = typeDef, fields = fields, layout = sa });
|
var classInfo = new ClassInfo()
|
||||||
|
{
|
||||||
|
type = type,
|
||||||
|
fields = ati.fields,
|
||||||
|
packingSize = ati.packingSize,
|
||||||
|
classSize = ati.classSize,
|
||||||
|
layout = ati.layout,
|
||||||
|
blittable = ati.blittable,
|
||||||
|
};
|
||||||
|
typeSet.Add(type, classInfo);
|
||||||
|
classInfos.Add(classInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GenerateStructDefines(List<ClassInfo> classInfos, List<string> lines)
|
private void GenerateStructDefines(List<ClassInfo> classInfos, List<string> lines)
|
||||||
|
|
@ -523,16 +940,13 @@ namespace HybridCLR.Editor.MethodBridge
|
||||||
foreach (var ci in classInfos)
|
foreach (var ci in classInfos)
|
||||||
{
|
{
|
||||||
lines.Add($"// {ci.type.Klass}");
|
lines.Add($"// {ci.type.Klass}");
|
||||||
uint packingSize = ci.layout?.PackingSize ?? 0;
|
uint packingSize = ci.packingSize;
|
||||||
if (packingSize != 0)
|
uint classSize = ci.classSize;
|
||||||
{
|
|
||||||
lines.Add($"#pragma pack(push, {packingSize})");
|
|
||||||
}
|
|
||||||
uint classSize = ci.layout?.ClassSize ?? 0;
|
|
||||||
|
|
||||||
if (ci.typeDef.IsExplicitLayout)
|
if (ci.layout == LayoutKind.Explicit)
|
||||||
{
|
{
|
||||||
lines.Add($"union {ci.type.GetTypeName()} {{");
|
lines.Add($"struct {ci.type.GetTypeName()} {{");
|
||||||
|
lines.Add("\tunion {");
|
||||||
if (classSize > 0)
|
if (classSize > 0)
|
||||||
{
|
{
|
||||||
lines.Add($"\tstruct {{ char __fieldSize_offsetPadding[{classSize}];}};");
|
lines.Add($"\tstruct {{ char __fieldSize_offsetPadding[{classSize}];}};");
|
||||||
|
|
@ -544,14 +958,28 @@ namespace HybridCLR.Editor.MethodBridge
|
||||||
string fieldName = $"__{index}";
|
string fieldName = $"__{index}";
|
||||||
string commentFieldName = $"{field.field.Name}";
|
string commentFieldName = $"{field.field.Name}";
|
||||||
lines.Add("\t#pragma pack(push, 1)");
|
lines.Add("\t#pragma pack(push, 1)");
|
||||||
lines.Add($"\tstruct {{ {(offset > 0 ? $"char {fieldName}_offsetPadding[{offset}];" : "")} {field.type.GetTypeName()} {fieldName};}}; // {commentFieldName}");
|
lines.Add($"\tstruct {{ {(offset > 0 ? $"char {fieldName}_offsetPadding[{offset}]; " : "")}{field.type.GetTypeName()} {fieldName};}}; // {commentFieldName}");
|
||||||
lines.Add($"\t#pragma pack(pop)");
|
lines.Add($"\t#pragma pack(pop)");
|
||||||
lines.Add($"\tstruct {{ {field.type.GetTypeName()} {fieldName}_forAlignmentOnly;}}; // {commentFieldName}");
|
if (packingSize > 0)
|
||||||
|
{
|
||||||
|
lines.Add($"\t#pragma pack(push, {packingSize})");
|
||||||
|
}
|
||||||
|
lines.Add($"\tstruct {{ {(offset > 0 ? $"char {fieldName}_offsetPadding_forAlignmentOnly[{offset}]; " : "")}{field.type.GetTypeName()} {fieldName}_forAlignmentOnly;}}; // {commentFieldName}");
|
||||||
|
if (packingSize > 0)
|
||||||
|
{
|
||||||
|
lines.Add($"\t#pragma pack(pop)");
|
||||||
|
}
|
||||||
++index;
|
++index;
|
||||||
}
|
}
|
||||||
|
lines.Add("\t};");
|
||||||
|
lines.Add("};");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
if (packingSize != 0)
|
||||||
|
{
|
||||||
|
lines.Add($"#pragma pack(push, {packingSize})");
|
||||||
|
}
|
||||||
lines.Add($"{(classSize > 0 ? "union" : "struct")} {ci.type.GetTypeName()} {{");
|
lines.Add($"{(classSize > 0 ? "union" : "struct")} {ci.type.GetTypeName()} {{");
|
||||||
if (classSize > 0)
|
if (classSize > 0)
|
||||||
{
|
{
|
||||||
|
|
@ -570,18 +998,18 @@ namespace HybridCLR.Editor.MethodBridge
|
||||||
{
|
{
|
||||||
lines.Add("\t};");
|
lines.Add("\t};");
|
||||||
}
|
}
|
||||||
}
|
lines.Add("};");
|
||||||
lines.Add("};");
|
if (packingSize != 0)
|
||||||
if (packingSize != 0)
|
{
|
||||||
{
|
lines.Add($"#pragma pack(pop)");
|
||||||
lines.Add($"#pragma pack(pop)");
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public const string SigOfObj = "u";
|
private const string SigOfObj = "u";
|
||||||
|
|
||||||
public static string ToFullName(TypeSig type)
|
private static string ToFullName(TypeSig type)
|
||||||
{
|
{
|
||||||
type = type.RemovePinnedAndModifiers();
|
type = type.RemovePinnedAndModifiers();
|
||||||
switch (type.ElementType)
|
switch (type.ElementType)
|
||||||
|
|
@ -676,9 +1104,9 @@ namespace HybridCLR.Editor.MethodBridge
|
||||||
return $"{Path.GetFileNameWithoutExtension(typeDef.Module.Name)}:{typeDef.FullName}";
|
return $"{Path.GetFileNameWithoutExtension(typeDef.Module.Name)}:{typeDef.FullName}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public void GenerateStructureSignatureStub(List<TypeInfo> types, List<string> lines)
|
private void GenerateStructureSignatureStub(List<TypeInfo> types, List<string> lines)
|
||||||
{
|
{
|
||||||
lines.Add("FullName2Signature hybridclr::interpreter::g_fullName2SignatureStub[] = {");
|
lines.Add("const FullName2Signature hybridclr::interpreter::g_fullName2SignatureStub[] = {");
|
||||||
foreach (var type in types)
|
foreach (var type in types)
|
||||||
{
|
{
|
||||||
TypeInfo isoType = ToIsomorphicType(type);
|
TypeInfo isoType = ToIsomorphicType(type);
|
||||||
|
|
@ -688,10 +1116,10 @@ namespace HybridCLR.Editor.MethodBridge
|
||||||
lines.Add("};");
|
lines.Add("};");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void GenerateManaged2NativeStub(List<MethodDesc> methods, List<string> lines)
|
private void GenerateManaged2NativeStub(List<MethodDesc> methods, List<string> lines)
|
||||||
{
|
{
|
||||||
lines.Add($@"
|
lines.Add($@"
|
||||||
Managed2NativeMethodInfo hybridclr::interpreter::g_managed2nativeStub[] =
|
const Managed2NativeMethodInfo hybridclr::interpreter::g_managed2nativeStub[] =
|
||||||
{{
|
{{
|
||||||
");
|
");
|
||||||
|
|
||||||
|
|
@ -704,10 +1132,10 @@ Managed2NativeMethodInfo hybridclr::interpreter::g_managed2nativeStub[] =
|
||||||
lines.Add("};");
|
lines.Add("};");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void GenerateNative2ManagedStub(List<MethodDesc> methods, List<string> lines)
|
private void GenerateNative2ManagedStub(List<MethodDesc> methods, List<string> lines)
|
||||||
{
|
{
|
||||||
lines.Add($@"
|
lines.Add($@"
|
||||||
Native2ManagedMethodInfo hybridclr::interpreter::g_native2managedStub[] =
|
const Native2ManagedMethodInfo hybridclr::interpreter::g_native2managedStub[] =
|
||||||
{{
|
{{
|
||||||
");
|
");
|
||||||
|
|
||||||
|
|
@ -720,10 +1148,10 @@ Native2ManagedMethodInfo hybridclr::interpreter::g_native2managedStub[] =
|
||||||
lines.Add("};");
|
lines.Add("};");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void GenerateAdjustThunkStub(List<MethodDesc> methods, List<string> lines)
|
private void GenerateAdjustThunkStub(List<MethodDesc> methods, List<string> lines)
|
||||||
{
|
{
|
||||||
lines.Add($@"
|
lines.Add($@"
|
||||||
NativeAdjustThunkMethodInfo hybridclr::interpreter::g_adjustThunkStub[] =
|
const NativeAdjustThunkMethodInfo hybridclr::interpreter::g_adjustThunkStub[] =
|
||||||
{{
|
{{
|
||||||
");
|
");
|
||||||
|
|
||||||
|
|
@ -746,7 +1174,7 @@ NativeAdjustThunkMethodInfo hybridclr::interpreter::g_adjustThunkStub[] =
|
||||||
return type.NeedExpandValue() ? $"(uint64_t)({varName})" : $"N2MAsUint64ValueOrAddress<{type.GetTypeName()}>({varName})";
|
return type.NeedExpandValue() ? $"(uint64_t)({varName})" : $"N2MAsUint64ValueOrAddress<{type.GetTypeName()}>({varName})";
|
||||||
}
|
}
|
||||||
|
|
||||||
public void GenerateManaged2NativeMethod(MethodDesc method, List<string> lines)
|
private void GenerateManaged2NativeMethod(MethodDesc method, List<string> lines)
|
||||||
{
|
{
|
||||||
string paramListStr = string.Join(", ", method.ParamInfos.Select(p => $"{p.Type.GetTypeName()} __arg{p.Index}").Concat(new string[] { "const MethodInfo* method" }));
|
string paramListStr = string.Join(", ", method.ParamInfos.Select(p => $"{p.Type.GetTypeName()} __arg{p.Index}").Concat(new string[] { "const MethodInfo* method" }));
|
||||||
string paramNameListStr = string.Join(", ", method.ParamInfos.Select(p => GetManaged2NativePassParam(p.Type, $"localVarBase+argVarIndexs[{p.Index}]")).Concat(new string[] { "method" }));
|
string paramNameListStr = string.Join(", ", method.ParamInfos.Select(p => GetManaged2NativePassParam(p.Type, $"localVarBase+argVarIndexs[{p.Index}]")).Concat(new string[] { "method" }));
|
||||||
|
|
@ -760,7 +1188,7 @@ static void __M2N_{method.CreateCallSigName()}(const MethodInfo* method, uint16_
|
||||||
");
|
");
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GenerateArgumentSizeAndOffset(List<ParamInfo> paramInfos)
|
private string GenerateArgumentSizeAndOffset(List<ParamInfo> paramInfos)
|
||||||
{
|
{
|
||||||
StringBuilder s = new StringBuilder();
|
StringBuilder s = new StringBuilder();
|
||||||
int index = 0;
|
int index = 0;
|
||||||
|
|
@ -774,7 +1202,7 @@ static void __M2N_{method.CreateCallSigName()}(const MethodInfo* method, uint16_
|
||||||
return s.ToString();
|
return s.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GenerateCopyArgumentToInterpreterStack(List<ParamInfo> paramInfos)
|
private string GenerateCopyArgumentToInterpreterStack(List<ParamInfo> paramInfos)
|
||||||
{
|
{
|
||||||
StringBuilder s = new StringBuilder();
|
StringBuilder s = new StringBuilder();
|
||||||
int index = 0;
|
int index = 0;
|
||||||
|
|
@ -815,14 +1243,46 @@ static {method.ReturnInfo.Type.GetTypeName()} __N2M_{(adjustorThunk ? "AdjustorT
|
||||||
");
|
");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void GenerateNative2ManagedMethod(MethodDesc method, List<string> lines)
|
private void GenerateNative2ManagedMethod(MethodDesc method, List<string> lines)
|
||||||
{
|
{
|
||||||
GenerateNative2ManagedMethod0(method, false, lines);
|
GenerateNative2ManagedMethod0(method, false, lines);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void GenerateAdjustThunkMethod(MethodDesc method, List<string> lines)
|
private void GenerateAdjustThunkMethod(MethodDesc method, List<string> lines)
|
||||||
{
|
{
|
||||||
GenerateNative2ManagedMethod0(method, true, lines);
|
GenerateNative2ManagedMethod0(method, true, lines);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void GenerateManaged2NativeFunctionPointerMethod(CalliMethodInfo methodInfo, List<string> lines)
|
||||||
|
{
|
||||||
|
MethodDesc method = methodInfo.Method;
|
||||||
|
string paramListStr = string.Join(", ", method.ParamInfos.Select(p => $"{p.Type.GetTypeName()} __arg{p.Index}"));
|
||||||
|
string paramNameListStr = string.Join(", ", method.ParamInfos.Select(p => GetManaged2NativePassParam(p.Type, $"localVarBase+argVarIndexs[{p.Index}]")));
|
||||||
|
string il2cppCallConventionName = GetIl2cppCallConventionName(methodInfo.Callvention);
|
||||||
|
lines.Add($@"
|
||||||
|
static void __M2NF_{methodInfo.Signature}(Il2CppMethodPointer methodPointer, uint16_t* argVarIndexs, StackObject* localVarBase, void* ret)
|
||||||
|
{{
|
||||||
|
typedef {method.ReturnInfo.Type.GetTypeName()} ({il2cppCallConventionName} *NativeMethod)({paramListStr});
|
||||||
|
{(!method.ReturnInfo.IsVoid ? $"*({method.ReturnInfo.Type.GetTypeName()}*)ret = " : "")}((NativeMethod)(methodPointer))({paramNameListStr});
|
||||||
|
}}
|
||||||
|
");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GenerateManaged2NativeFunctionPointerMethodStub(List<CalliMethodInfo> calliMethodSignatures, List<string> lines)
|
||||||
|
{
|
||||||
|
lines.Add(@"
|
||||||
|
const Managed2NativeFunctionPointerCallData hybridclr::interpreter::g_managed2NativeFunctionPointerCallStub[]
|
||||||
|
{
|
||||||
|
");
|
||||||
|
foreach (var method in calliMethodSignatures)
|
||||||
|
{
|
||||||
|
lines.Add($"\t{{\"{method.Signature}\", __M2NF_{method.Signature}}},");
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.Add(@"
|
||||||
|
{nullptr, nullptr},
|
||||||
|
};
|
||||||
|
");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
using dnlib.DotNet;
|
||||||
|
using HybridCLR.Editor.ABI;
|
||||||
|
using HybridCLR.Editor.Meta;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using CallingConvention = System.Runtime.InteropServices.CallingConvention;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace HybridCLR.Editor.MethodBridge
|
||||||
|
{
|
||||||
|
public class RawMonoPInvokeCallbackMethodInfo
|
||||||
|
{
|
||||||
|
public MethodDef Method { get; set; }
|
||||||
|
|
||||||
|
public CustomAttribute GenerationAttribute { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MonoPInvokeCallbackAnalyzer
|
||||||
|
{
|
||||||
|
|
||||||
|
private readonly List<ModuleDefMD> _rootModules = new List<ModuleDefMD>();
|
||||||
|
|
||||||
|
private readonly List<RawMonoPInvokeCallbackMethodInfo> _reversePInvokeMethods = new List<RawMonoPInvokeCallbackMethodInfo>();
|
||||||
|
|
||||||
|
public List<RawMonoPInvokeCallbackMethodInfo> ReversePInvokeMethods => _reversePInvokeMethods;
|
||||||
|
|
||||||
|
public MonoPInvokeCallbackAnalyzer(AssemblyCache cache, List<string> assemblyNames)
|
||||||
|
{
|
||||||
|
foreach (var assemblyName in assemblyNames)
|
||||||
|
{
|
||||||
|
_rootModules.Add(cache.LoadModule(assemblyName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CollectReversePInvokeMethods()
|
||||||
|
{
|
||||||
|
foreach (var mod in _rootModules)
|
||||||
|
{
|
||||||
|
Debug.Log($"ass:{mod.FullName} method count:{mod.Metadata.TablesStream.MethodTable.Rows}");
|
||||||
|
for (uint rid = 1, n = mod.Metadata.TablesStream.MethodTable.Rows; rid <= n; rid++)
|
||||||
|
{
|
||||||
|
var method = mod.ResolveMethod(rid);
|
||||||
|
//Debug.Log($"method:{method}");
|
||||||
|
if (!method.IsStatic || !method.HasCustomAttributes)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
CustomAttribute wa = method.CustomAttributes.FirstOrDefault(ca => ca.AttributeType.Name == "MonoPInvokeCallbackAttribute");
|
||||||
|
if (wa == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!MetaUtil.IsSupportedPInvokeMethodSignature(method.MethodSig))
|
||||||
|
{
|
||||||
|
Debug.LogError($"MonoPInvokeCallback method {method.FullName} has unsupported parameter or return type. Please check the method signature.");
|
||||||
|
}
|
||||||
|
//foreach (var ca in method.CustomAttributes)
|
||||||
|
//{
|
||||||
|
// Debug.Log($"{ca.AttributeType.FullName} {ca.TypeFullName}");
|
||||||
|
//}
|
||||||
|
_reversePInvokeMethods.Add(new RawMonoPInvokeCallbackMethodInfo()
|
||||||
|
{
|
||||||
|
Method = method,
|
||||||
|
GenerationAttribute = method.CustomAttributes.FirstOrDefault(ca => ca.AttributeType.FullName == "HybridCLR.ReversePInvokeWrapperGenerationAttribute"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Run()
|
||||||
|
{
|
||||||
|
CollectReversePInvokeMethods();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
using dnlib.DotNet;
|
||||||
|
using HybridCLR.Editor.Meta;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace HybridCLR.Editor.MethodBridge
|
||||||
|
{
|
||||||
|
|
||||||
|
public class PInvokeAnalyzer
|
||||||
|
{
|
||||||
|
private readonly List<ModuleDefMD> _rootModules = new List<ModuleDefMD>();
|
||||||
|
|
||||||
|
private readonly List<CallNativeMethodSignatureInfo> _pinvokeMethodSignatures = new List<CallNativeMethodSignatureInfo>();
|
||||||
|
|
||||||
|
public List<CallNativeMethodSignatureInfo> PInvokeMethodSignatures => _pinvokeMethodSignatures;
|
||||||
|
|
||||||
|
public PInvokeAnalyzer(AssemblyCache cache, List<string> assemblyNames)
|
||||||
|
{
|
||||||
|
foreach (var assemblyName in assemblyNames)
|
||||||
|
{
|
||||||
|
_rootModules.Add(cache.LoadModule(assemblyName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private CallingConvention GetCallingConvention(MethodDef method)
|
||||||
|
{
|
||||||
|
switch (method.ImplMap.CallConv)
|
||||||
|
{
|
||||||
|
case PInvokeAttributes.CallConvWinapi: return CallingConvention.Default;
|
||||||
|
case PInvokeAttributes.CallConvCdecl: return CallingConvention.C;
|
||||||
|
case PInvokeAttributes.CallConvStdCall: return CallingConvention.StdCall;
|
||||||
|
case PInvokeAttributes.CallConvThiscall: return CallingConvention.ThisCall;
|
||||||
|
case PInvokeAttributes.CallConvFastcall: return CallingConvention.FastCall;
|
||||||
|
default: return CallingConvention.Default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Run()
|
||||||
|
{
|
||||||
|
foreach (var mod in _rootModules)
|
||||||
|
{
|
||||||
|
foreach (TypeDef type in mod.GetTypes())
|
||||||
|
{
|
||||||
|
foreach (MethodDef method in type.Methods)
|
||||||
|
{
|
||||||
|
if (method.IsPinvokeImpl)
|
||||||
|
{
|
||||||
|
if (!MetaUtil.IsSupportedPInvokeMethodSignature(method.MethodSig))
|
||||||
|
{
|
||||||
|
Debug.LogError($"PInvoke method {method.FullName} has unsupported parameter or return type. Please check the method signature.");
|
||||||
|
}
|
||||||
|
_pinvokeMethodSignatures.Add(new CallNativeMethodSignatureInfo
|
||||||
|
{
|
||||||
|
MethodSig = method.MethodSig,
|
||||||
|
Callvention = method.HasImplMap? GetCallingConvention(method) : (CallingConvention?)null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 9923175c961b78849aeaf99708e294ce
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 259c1cb7fe681f74eb435ab8f268890d
|
|
||||||
folderAsset: yes
|
|
||||||
DefaultImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
|
|
@ -1,108 +0,0 @@
|
||||||
using dnlib.DotNet;
|
|
||||||
using HybridCLR.Editor.ABI;
|
|
||||||
using HybridCLR.Editor.Meta;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace HybridCLR.Editor.ReversePInvokeWrap
|
|
||||||
{
|
|
||||||
public class RawReversePInvokeMethodInfo
|
|
||||||
{
|
|
||||||
public MethodDef Method { get; set; }
|
|
||||||
|
|
||||||
public CustomAttribute GenerationAttribute { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ABIReversePInvokeMethodInfo
|
|
||||||
{
|
|
||||||
public MethodDesc Method { get; set; }
|
|
||||||
|
|
||||||
public int Count { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Analyzer
|
|
||||||
{
|
|
||||||
|
|
||||||
private readonly List<ModuleDefMD> _rootModules = new List<ModuleDefMD>();
|
|
||||||
|
|
||||||
private readonly List<RawReversePInvokeMethodInfo> _reversePInvokeMethods = new List<RawReversePInvokeMethodInfo>();
|
|
||||||
|
|
||||||
public Analyzer(AssemblyCache cache, List<string> assemblyNames)
|
|
||||||
{
|
|
||||||
foreach (var assemblyName in assemblyNames)
|
|
||||||
{
|
|
||||||
_rootModules.Add(cache.LoadModule(assemblyName));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CollectReversePInvokeMethods()
|
|
||||||
{
|
|
||||||
foreach (var mod in _rootModules)
|
|
||||||
{
|
|
||||||
Debug.Log($"ass:{mod.FullName} methodcount:{mod.Metadata.TablesStream.MethodTable.Rows}");
|
|
||||||
for (uint rid = 1, n = mod.Metadata.TablesStream.MethodTable.Rows; rid <= n; rid++)
|
|
||||||
{
|
|
||||||
var method = mod.ResolveMethod(rid);
|
|
||||||
//Debug.Log($"method:{method}");
|
|
||||||
if (!method.IsStatic || !method.HasCustomAttributes)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
CustomAttribute wa = method.CustomAttributes.FirstOrDefault(ca => ca.AttributeType.Name == "MonoPInvokeCallbackAttribute");
|
|
||||||
if (wa == null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
//foreach (var ca in method.CustomAttributes)
|
|
||||||
//{
|
|
||||||
// Debug.Log($"{ca.AttributeType.FullName} {ca.TypeFullName}");
|
|
||||||
//}
|
|
||||||
_reversePInvokeMethods.Add(new RawReversePInvokeMethodInfo()
|
|
||||||
{
|
|
||||||
Method = method,
|
|
||||||
GenerationAttribute = method.CustomAttributes.FirstOrDefault(ca => ca.AttributeType.FullName == "HybridCLR.ReversePInvokeWrapperGenerationAttribute"),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<ABIReversePInvokeMethodInfo> BuildABIMethods()
|
|
||||||
{
|
|
||||||
var methodsBySig = new Dictionary<string, ABIReversePInvokeMethodInfo>();
|
|
||||||
var typeCreator = new TypeCreator();
|
|
||||||
foreach(var method in _reversePInvokeMethods)
|
|
||||||
{
|
|
||||||
MethodDesc desc = new MethodDesc
|
|
||||||
{
|
|
||||||
MethodDef = method.Method,
|
|
||||||
ReturnInfo = new ReturnInfo { Type = typeCreator.CreateTypeInfo(method.Method.ReturnType)},
|
|
||||||
ParamInfos = method.Method.Parameters.Select(p => new ParamInfo { Type = typeCreator.CreateTypeInfo(p.Type)}).ToList(),
|
|
||||||
};
|
|
||||||
desc.Init();
|
|
||||||
if (!methodsBySig.TryGetValue(desc.Sig, out var arm))
|
|
||||||
{
|
|
||||||
arm = new ABIReversePInvokeMethodInfo()
|
|
||||||
{
|
|
||||||
Method = desc,
|
|
||||||
Count = 0,
|
|
||||||
};
|
|
||||||
methodsBySig.Add(desc.Sig, arm);
|
|
||||||
}
|
|
||||||
int preserveCount = method.GenerationAttribute != null ? (int)method.GenerationAttribute.ConstructorArguments[0].Value : 1;
|
|
||||||
arm.Count += preserveCount;
|
|
||||||
}
|
|
||||||
var methods = methodsBySig.Values.ToList();
|
|
||||||
methods.Sort((a, b) => String.CompareOrdinal(a.Method.Sig, b.Method.Sig));
|
|
||||||
return methods;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Run()
|
|
||||||
{
|
|
||||||
CollectReversePInvokeMethods();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
using HybridCLR.Editor.ABI;
|
|
||||||
using HybridCLR.Editor.Template;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace HybridCLR.Editor.ReversePInvokeWrap
|
|
||||||
{
|
|
||||||
public class Generator
|
|
||||||
{
|
|
||||||
public void Generate(List<ABIReversePInvokeMethodInfo> methods, string outputFile)
|
|
||||||
{
|
|
||||||
string template = File.ReadAllText(outputFile, Encoding.UTF8);
|
|
||||||
var frr = new FileRegionReplace(template);
|
|
||||||
var codes = new List<string>();
|
|
||||||
|
|
||||||
int methodIndex = 0;
|
|
||||||
var stubCodes = new List<string>();
|
|
||||||
foreach(var methodInfo in methods)
|
|
||||||
{
|
|
||||||
MethodDesc method = methodInfo.Method;
|
|
||||||
string paramDeclaringListWithoutMethodInfoStr = string.Join(", ", method.ParamInfos.Select(p => $"{p.Type.GetTypeName()} __arg{p.Index}"));
|
|
||||||
string paramNameListWithoutMethodInfoStr = string.Join(", ", method.ParamInfos.Select(p => $"__arg{p.Index}").Concat(new string[] { "method" }));
|
|
||||||
string paramTypeListWithMethodInfoStr = string.Join(", ", method.ParamInfos.Select(p => $"{p.Type.GetTypeName()}").Concat(new string[] { "const MethodInfo*" }));
|
|
||||||
string methodTypeDef = $"typedef {method.ReturnInfo.Type.GetTypeName()} (*Callback)({paramTypeListWithMethodInfoStr})";
|
|
||||||
for (int i = 0; i < methodInfo.Count; i++, methodIndex++)
|
|
||||||
{
|
|
||||||
codes.Add($@"
|
|
||||||
{method.ReturnInfo.Type.GetTypeName()} __ReversePInvokeMethod_{methodIndex}({paramDeclaringListWithoutMethodInfoStr})
|
|
||||||
{{
|
|
||||||
const MethodInfo* method = MetadataModule::GetMethodInfoByReversePInvokeWrapperIndex({methodIndex});
|
|
||||||
{methodTypeDef};
|
|
||||||
{(method.ReturnInfo.IsVoid ? "" : "return ")}((Callback)(method->methodPointerCallByInterp))({paramNameListWithoutMethodInfoStr});
|
|
||||||
}}
|
|
||||||
");
|
|
||||||
stubCodes.Add($"\t\t{{\"{method.Sig}\", (Il2CppMethodPointer)__ReversePInvokeMethod_{methodIndex}}},\n");
|
|
||||||
}
|
|
||||||
Debug.Log($"[ReversePInvokeWrap.Generator] method:{method.MethodDef} wrapperCount:{methodInfo.Count}");
|
|
||||||
}
|
|
||||||
|
|
||||||
codes.Add(@"
|
|
||||||
ReversePInvokeMethodData g_reversePInvokeMethodStub[]
|
|
||||||
{
|
|
||||||
");
|
|
||||||
codes.AddRange(stubCodes);
|
|
||||||
|
|
||||||
codes.Add(@"
|
|
||||||
{nullptr, nullptr},
|
|
||||||
};
|
|
||||||
");
|
|
||||||
|
|
||||||
frr.Replace("CODE", string.Join("", codes));
|
|
||||||
frr.Commit(outputFile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
using HybridCLR.Editor;
|
|
||||||
using System;
|
|
||||||
using UnityEditor;
|
|
||||||
using UnityEditorInternal;
|
|
||||||
|
|
||||||
namespace HybridCLR.Editor.Settings
|
|
||||||
{
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 监听编辑器状态,当编辑器重新 focus 时,重新加载实例,避免某些情景下 svn 、git 等外部修改了数据却无法同步的异常。
|
|
||||||
/// </summary>
|
|
||||||
[InitializeOnLoad]
|
|
||||||
public static class EditorStatusWatcher
|
|
||||||
{
|
|
||||||
public static Action OnEditorFocused;
|
|
||||||
static bool isFocused;
|
|
||||||
static EditorStatusWatcher() => EditorApplication.update += Update;
|
|
||||||
static void Update()
|
|
||||||
{
|
|
||||||
if (isFocused != InternalEditorUtility.isApplicationActive)
|
|
||||||
{
|
|
||||||
isFocused = InternalEditorUtility.isApplicationActive;
|
|
||||||
if (isFocused)
|
|
||||||
{
|
|
||||||
HybridCLRSettings.LoadOrCreate();
|
|
||||||
OnEditorFocused?.Invoke();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -18,20 +18,21 @@ namespace HybridCLR.Editor.Settings
|
||||||
private SerializedProperty _hotUpdateAssemblies;
|
private SerializedProperty _hotUpdateAssemblies;
|
||||||
private SerializedProperty _preserveHotUpdateAssemblies;
|
private SerializedProperty _preserveHotUpdateAssemblies;
|
||||||
private SerializedProperty _hotUpdateDllCompileOutputRootDir;
|
private SerializedProperty _hotUpdateDllCompileOutputRootDir;
|
||||||
private SerializedProperty _externalHotUpdateAssembliyDirs;
|
private SerializedProperty _externalHotUpdateAssemblyDirs;
|
||||||
private SerializedProperty _strippedAOTDllOutputRootDir;
|
private SerializedProperty _strippedAOTDllOutputRootDir;
|
||||||
private SerializedProperty _patchAOTAssemblies;
|
private SerializedProperty _patchAOTAssemblies;
|
||||||
private SerializedProperty _outputLinkFile;
|
private SerializedProperty _outputLinkFile;
|
||||||
private SerializedProperty _outputAOTGenericReferenceFile;
|
private SerializedProperty _outputAOTGenericReferenceFile;
|
||||||
private SerializedProperty _maxGenericReferenceIteration;
|
private SerializedProperty _maxGenericReferenceIteration;
|
||||||
private SerializedProperty _maxMethodBridgeGenericIteration;
|
private SerializedProperty _maxMethodBridgeGenericIteration;
|
||||||
private GUIStyle buttonStyle;
|
|
||||||
public HybridCLRSettingsProvider() : base("Project/HybridCLR Settings", SettingsScope.Project) { }
|
public HybridCLRSettingsProvider() : base("Project/HybridCLR Settings", SettingsScope.Project) { }
|
||||||
|
|
||||||
public override void OnActivate(string searchContext, VisualElement rootElement)
|
public override void OnActivate(string searchContext, VisualElement rootElement)
|
||||||
{
|
{
|
||||||
EditorStatusWatcher.OnEditorFocused += OnEditorFocused;
|
|
||||||
InitGUI();
|
InitGUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitGUI()
|
private void InitGUI()
|
||||||
{
|
{
|
||||||
var setting = HybridCLRSettings.LoadOrCreate();
|
var setting = HybridCLRSettings.LoadOrCreate();
|
||||||
|
|
@ -45,7 +46,7 @@ namespace HybridCLR.Editor.Settings
|
||||||
_hotUpdateAssemblies = _serializedObject.FindProperty("hotUpdateAssemblies");
|
_hotUpdateAssemblies = _serializedObject.FindProperty("hotUpdateAssemblies");
|
||||||
_preserveHotUpdateAssemblies = _serializedObject.FindProperty("preserveHotUpdateAssemblies");
|
_preserveHotUpdateAssemblies = _serializedObject.FindProperty("preserveHotUpdateAssemblies");
|
||||||
_hotUpdateDllCompileOutputRootDir = _serializedObject.FindProperty("hotUpdateDllCompileOutputRootDir");
|
_hotUpdateDllCompileOutputRootDir = _serializedObject.FindProperty("hotUpdateDllCompileOutputRootDir");
|
||||||
_externalHotUpdateAssembliyDirs = _serializedObject.FindProperty("externalHotUpdateAssembliyDirs");
|
_externalHotUpdateAssemblyDirs = _serializedObject.FindProperty("externalHotUpdateAssembliyDirs");
|
||||||
_strippedAOTDllOutputRootDir = _serializedObject.FindProperty("strippedAOTDllOutputRootDir");
|
_strippedAOTDllOutputRootDir = _serializedObject.FindProperty("strippedAOTDllOutputRootDir");
|
||||||
_patchAOTAssemblies = _serializedObject.FindProperty("patchAOTAssemblies");
|
_patchAOTAssemblies = _serializedObject.FindProperty("patchAOTAssemblies");
|
||||||
_outputLinkFile = _serializedObject.FindProperty("outputLinkFile");
|
_outputLinkFile = _serializedObject.FindProperty("outputLinkFile");
|
||||||
|
|
@ -53,126 +54,53 @@ namespace HybridCLR.Editor.Settings
|
||||||
_maxGenericReferenceIteration = _serializedObject.FindProperty("maxGenericReferenceIteration");
|
_maxGenericReferenceIteration = _serializedObject.FindProperty("maxGenericReferenceIteration");
|
||||||
_maxMethodBridgeGenericIteration = _serializedObject.FindProperty("maxMethodBridgeGenericIteration");
|
_maxMethodBridgeGenericIteration = _serializedObject.FindProperty("maxMethodBridgeGenericIteration");
|
||||||
}
|
}
|
||||||
private void OnEditorFocused()
|
|
||||||
{
|
|
||||||
InitGUI();
|
|
||||||
Repaint();
|
|
||||||
}
|
|
||||||
public override void OnTitleBarGUI()
|
|
||||||
{
|
|
||||||
base.OnTitleBarGUI();
|
|
||||||
var rect = GUILayoutUtility.GetLastRect();
|
|
||||||
buttonStyle = buttonStyle ?? GUI.skin.GetStyle("IconButton");
|
|
||||||
|
|
||||||
#region 绘制官方网站跳转按钮
|
|
||||||
var w = rect.x + rect.width;
|
|
||||||
rect.x = w - 57;
|
|
||||||
rect.y += 6;
|
|
||||||
rect.width = rect.height = 18;
|
|
||||||
var content = EditorGUIUtility.IconContent("_Help");
|
|
||||||
content.tooltip = "点击访问 HybridCLR 官方文档";
|
|
||||||
if (GUI.Button(rect, content, buttonStyle))
|
|
||||||
{
|
|
||||||
Application.OpenURL("https://focus-creative-games.github.io/hybridclr/");
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
#region 绘制 Preset
|
|
||||||
rect.x += 19;
|
|
||||||
content = EditorGUIUtility.IconContent("Preset.Context");
|
|
||||||
content.tooltip = "点击存储或加载 Preset .";
|
|
||||||
if (GUI.Button(rect, content, buttonStyle))
|
|
||||||
{
|
|
||||||
var target = HybridCLRSettings.Instance;
|
|
||||||
var receiver = ScriptableObject.CreateInstance<SettingsPresetReceiver>();
|
|
||||||
receiver.Init(target, this);
|
|
||||||
PresetSelector.ShowSelector(target, null, true, receiver);
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
#region 绘制 Reset
|
|
||||||
rect.x += 19;
|
|
||||||
content = EditorGUIUtility.IconContent(
|
|
||||||
#if UNITY_2021_3_OR_NEWER
|
|
||||||
"pane options"
|
|
||||||
#else
|
|
||||||
"_Popup"
|
|
||||||
#endif
|
|
||||||
);
|
|
||||||
content.tooltip = "Reset";
|
|
||||||
if (GUI.Button(rect, content, buttonStyle))
|
|
||||||
{
|
|
||||||
GenericMenu menu = new GenericMenu();
|
|
||||||
menu.AddItem(new GUIContent("Reset"), false, () =>
|
|
||||||
{
|
|
||||||
Undo.RecordObject(HybridCLRSettings.Instance, "Capture Value for Reset");
|
|
||||||
var dv = ScriptableObject.CreateInstance<HybridCLRSettings>();
|
|
||||||
var json = EditorJsonUtility.ToJson(dv);
|
|
||||||
UnityEngine.Object.DestroyImmediate(dv);
|
|
||||||
EditorJsonUtility.FromJsonOverwrite(json, HybridCLRSettings.Instance);
|
|
||||||
HybridCLRSettings.Save();
|
|
||||||
});
|
|
||||||
menu.ShowAsContext();
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
public override void OnGUI(string searchContext)
|
public override void OnGUI(string searchContext)
|
||||||
{
|
{
|
||||||
using (CreateSettingsWindowGUIScope())
|
if (_serializedObject == null || !_serializedObject.targetObject)
|
||||||
{
|
{
|
||||||
//解决编辑器打包时出现的 _serializedObject.targetObject 意外销毁的情况
|
InitGUI();
|
||||||
if (_serializedObject == null||!_serializedObject.targetObject)
|
}
|
||||||
{
|
_serializedObject.Update();
|
||||||
InitGUI();
|
EditorGUI.BeginChangeCheck();
|
||||||
}
|
EditorGUILayout.PropertyField(_enable);
|
||||||
_serializedObject.Update();
|
EditorGUILayout.PropertyField(_hybridclrRepoURL);
|
||||||
EditorGUI.BeginChangeCheck();
|
EditorGUILayout.PropertyField(_il2cppPlusRepoURL);
|
||||||
EditorGUILayout.PropertyField(_enable);
|
EditorGUILayout.PropertyField(_useGlobalIl2cpp);
|
||||||
EditorGUILayout.PropertyField(_hybridclrRepoURL);
|
EditorGUILayout.PropertyField(_hotUpdateAssemblyDefinitions);
|
||||||
EditorGUILayout.PropertyField(_il2cppPlusRepoURL);
|
EditorGUILayout.PropertyField(_hotUpdateAssemblies);
|
||||||
EditorGUILayout.PropertyField(_useGlobalIl2cpp);
|
EditorGUILayout.PropertyField(_preserveHotUpdateAssemblies);
|
||||||
EditorGUILayout.PropertyField(_hotUpdateAssemblyDefinitions);
|
EditorGUILayout.PropertyField(_hotUpdateDllCompileOutputRootDir);
|
||||||
EditorGUILayout.PropertyField(_hotUpdateAssemblies);
|
EditorGUILayout.PropertyField(_externalHotUpdateAssemblyDirs);
|
||||||
EditorGUILayout.PropertyField(_preserveHotUpdateAssemblies);
|
EditorGUILayout.PropertyField(_strippedAOTDllOutputRootDir);
|
||||||
EditorGUILayout.PropertyField(_hotUpdateDllCompileOutputRootDir);
|
EditorGUILayout.PropertyField(_patchAOTAssemblies);
|
||||||
EditorGUILayout.PropertyField(_externalHotUpdateAssembliyDirs);
|
EditorGUILayout.PropertyField(_outputLinkFile);
|
||||||
EditorGUILayout.PropertyField(_strippedAOTDllOutputRootDir);
|
EditorGUILayout.PropertyField(_outputAOTGenericReferenceFile);
|
||||||
EditorGUILayout.PropertyField(_patchAOTAssemblies);
|
EditorGUILayout.PropertyField(_maxGenericReferenceIteration);
|
||||||
EditorGUILayout.PropertyField(_outputLinkFile);
|
EditorGUILayout.PropertyField(_maxMethodBridgeGenericIteration);
|
||||||
EditorGUILayout.PropertyField(_outputAOTGenericReferenceFile);
|
if (EditorGUI.EndChangeCheck())
|
||||||
EditorGUILayout.PropertyField(_maxGenericReferenceIteration);
|
{
|
||||||
EditorGUILayout.PropertyField(_maxMethodBridgeGenericIteration);
|
_serializedObject.ApplyModifiedProperties();
|
||||||
if (EditorGUI.EndChangeCheck())
|
HybridCLRSettings.Save();
|
||||||
{
|
|
||||||
_serializedObject.ApplyModifiedProperties();
|
|
||||||
HybridCLRSettings.Save();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private IDisposable CreateSettingsWindowGUIScope()
|
|
||||||
{
|
|
||||||
var unityEditorAssembly = Assembly.GetAssembly(typeof(EditorWindow));
|
|
||||||
var type = unityEditorAssembly.GetType("UnityEditor.SettingsWindow+GUIScope");
|
|
||||||
return Activator.CreateInstance(type) as IDisposable;
|
|
||||||
}
|
|
||||||
public override void OnDeactivate()
|
public override void OnDeactivate()
|
||||||
{
|
{
|
||||||
base.OnDeactivate();
|
base.OnDeactivate();
|
||||||
EditorStatusWatcher.OnEditorFocused -= OnEditorFocused;
|
|
||||||
HybridCLRSettings.Save();
|
HybridCLRSettings.Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
static HybridCLRSettingsProvider provider;
|
static HybridCLRSettingsProvider s_provider;
|
||||||
|
|
||||||
[SettingsProvider]
|
[SettingsProvider]
|
||||||
public static SettingsProvider CreateMyCustomSettingsProvider()
|
public static SettingsProvider CreateMyCustomSettingsProvider()
|
||||||
{
|
{
|
||||||
if (HybridCLRSettings.Instance && provider == null)
|
if (s_provider == null)
|
||||||
{
|
{
|
||||||
provider = new HybridCLRSettingsProvider();
|
s_provider = new HybridCLRSettingsProvider();
|
||||||
using (var so = new SerializedObject(HybridCLRSettings.Instance))
|
|
||||||
{
|
|
||||||
provider.keywords = GetSearchKeywordsFromSerializedObject(so);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return provider;
|
return s_provider;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,54 +1,98 @@
|
||||||
|
using System.IO;
|
||||||
using UnityEditorInternal;
|
using UnityEditorInternal;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace HybridCLR.Editor.Settings
|
namespace HybridCLR.Editor.Settings
|
||||||
{
|
{
|
||||||
[FilePath("ProjectSettings/HybridCLRSettings.asset")]
|
|
||||||
public class HybridCLRSettings : ScriptableSingleton<HybridCLRSettings>
|
public class HybridCLRSettings : ScriptableObject
|
||||||
{
|
{
|
||||||
[Header("开启HybridCLR插件")]
|
[Tooltip("enable HybridCLR")]
|
||||||
public bool enable = true;
|
public bool enable = true;
|
||||||
|
|
||||||
[Header("使用全局安装的il2cpp")]
|
[Tooltip("use il2cpp in unity editor installation location")]
|
||||||
public bool useGlobalIl2cpp;
|
public bool useGlobalIl2cpp;
|
||||||
|
|
||||||
[Header("hybridclr 仓库 URL")]
|
[Tooltip("hybridclr repo URL")]
|
||||||
public string hybridclrRepoURL = "https://gitee.com/focus-creative-games/hybridclr";
|
public string hybridclrRepoURL = "https://gitee.com/focus-creative-games/hybridclr";
|
||||||
|
|
||||||
[Header("il2cpp_plus 仓库 URL")]
|
[Tooltip("il2cpp_plus repo URL")]
|
||||||
public string il2cppPlusRepoURL = "https://gitee.com/focus-creative-games/il2cpp_plus";
|
public string il2cppPlusRepoURL = "https://gitee.com/focus-creative-games/il2cpp_plus";
|
||||||
|
|
||||||
[Header("热更新Assembly Definitions")]
|
[Tooltip("hot update assembly definitions(asd)")]
|
||||||
public AssemblyDefinitionAsset[] hotUpdateAssemblyDefinitions;
|
public AssemblyDefinitionAsset[] hotUpdateAssemblyDefinitions;
|
||||||
|
|
||||||
[Header("热更新dlls")]
|
[Tooltip("hot update assembly names(without .dll suffix)")]
|
||||||
public string[] hotUpdateAssemblies;
|
public string[] hotUpdateAssemblies;
|
||||||
|
|
||||||
[Header("预留的热更新dlls")]
|
[Tooltip("preserved hot update assembly names(without .dll suffix)")]
|
||||||
public string[] preserveHotUpdateAssemblies;
|
public string[] preserveHotUpdateAssemblies;
|
||||||
|
|
||||||
[Header("热更新dll编译输出根目录")]
|
[Tooltip("output directory of compiling hot update assemblies")]
|
||||||
public string hotUpdateDllCompileOutputRootDir = "HybridCLRData/HotUpdateDlls";
|
public string hotUpdateDllCompileOutputRootDir = "HybridCLRData/HotUpdateDlls";
|
||||||
|
|
||||||
[Header("外部热更新dll搜索路径")]
|
[Tooltip("searching paths of external hot update assemblies")]
|
||||||
public string[] externalHotUpdateAssembliyDirs;
|
public string[] externalHotUpdateAssembliyDirs;
|
||||||
|
|
||||||
[Header("裁减后AOT dll输出根目录")]
|
[Tooltip("output directory of stripped AOT assemblies")]
|
||||||
public string strippedAOTDllOutputRootDir = "HybridCLRData/AssembliesPostIl2CppStrip";
|
public string strippedAOTDllOutputRootDir = "HybridCLRData/AssembliesPostIl2CppStrip";
|
||||||
|
|
||||||
[Header("补充元数据AOT dlls")]
|
[Tooltip("supplementary metadata assembly names(without .dll suffix)")]
|
||||||
public string[] patchAOTAssemblies;
|
public string[] patchAOTAssemblies;
|
||||||
|
|
||||||
[Header("生成的link.xml路径")]
|
[Tooltip("output file of automatic generated link.xml by scanning hot update assemblies")]
|
||||||
public string outputLinkFile = "HybridCLRGenerate/link.xml";
|
public string outputLinkFile = "HybridCLRGenerate/link.xml";
|
||||||
|
|
||||||
[Header("自动扫描生成的AOTGenericReferences.cs路径")]
|
[Tooltip("output file of automatic generated AOTGenericReferences.cs")]
|
||||||
public string outputAOTGenericReferenceFile = "HybridCLRGenerate/AOTGenericReferences.cs";
|
public string outputAOTGenericReferenceFile = "HybridCLRGenerate/AOTGenericReferences.cs";
|
||||||
|
|
||||||
[Header("AOT泛型实例化搜索迭代次数")]
|
[Tooltip("max iteration count of searching generic methods in hot update assemblies")]
|
||||||
public int maxGenericReferenceIteration = 10;
|
public int maxGenericReferenceIteration = 10;
|
||||||
|
|
||||||
[Header("MethodBridge泛型搜索迭代次数")]
|
[Tooltip("max iteration count of searching method bridge generic methods in AOT assemblies")]
|
||||||
public int maxMethodBridgeGenericIteration = 10;
|
public int maxMethodBridgeGenericIteration = 10;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private static HybridCLRSettings s_Instance;
|
||||||
|
|
||||||
|
public static HybridCLRSettings Instance
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (!s_Instance)
|
||||||
|
{
|
||||||
|
LoadOrCreate();
|
||||||
|
}
|
||||||
|
return s_Instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetFilePath()
|
||||||
|
{
|
||||||
|
return "ProjectSettings/HybridCLRSettings.asset";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HybridCLRSettings LoadOrCreate()
|
||||||
|
{
|
||||||
|
string filePath = GetFilePath();
|
||||||
|
Object[] objs = InternalEditorUtility.LoadSerializedFileAndForget(filePath);
|
||||||
|
s_Instance = objs.Length > 0 ? (HybridCLRSettings)objs[0] : (s_Instance ?? CreateInstance<HybridCLRSettings>());
|
||||||
|
return s_Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Save()
|
||||||
|
{
|
||||||
|
if (!s_Instance)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string filePath = GetFilePath();
|
||||||
|
string directoryName = Path.GetDirectoryName(filePath);
|
||||||
|
Directory.CreateDirectory(directoryName);
|
||||||
|
var obj = new Object[1] { s_Instance };
|
||||||
|
InternalEditorUtility.SaveToSerializedFileAndForget(obj, filePath, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ namespace HybridCLR.Editor.Settings
|
||||||
{
|
{
|
||||||
|
|
||||||
[MenuItem("HybridCLR/About", priority = 0)]
|
[MenuItem("HybridCLR/About", priority = 0)]
|
||||||
public static void OpenAbout() => Application.OpenURL("https://hybridclr.doc.code-philosophy.com/docs/intro");
|
public static void OpenAbout() => Application.OpenURL("https://www.hybridclr.cn/docs/intro");
|
||||||
|
|
||||||
[MenuItem("HybridCLR/Installer...", priority = 60)]
|
[MenuItem("HybridCLR/Installer...", priority = 60)]
|
||||||
private static void Open()
|
private static void Open()
|
||||||
|
|
@ -21,19 +21,19 @@ namespace HybridCLR.Editor.Settings
|
||||||
public static void OpenSettings() => SettingsService.OpenProjectSettings("Project/HybridCLR Settings");
|
public static void OpenSettings() => SettingsService.OpenProjectSettings("Project/HybridCLR Settings");
|
||||||
|
|
||||||
[MenuItem("HybridCLR/Documents/Quick Start")]
|
[MenuItem("HybridCLR/Documents/Quick Start")]
|
||||||
public static void OpenQuickStart() => Application.OpenURL("https://hybridclr.doc.code-philosophy.com/docs/beginner/quickstart");
|
public static void OpenQuickStart() => Application.OpenURL("https://www.hybridclr.cn/docs/beginner/quickstart");
|
||||||
|
|
||||||
[MenuItem("HybridCLR/Documents/Performance")]
|
[MenuItem("HybridCLR/Documents/Performance")]
|
||||||
public static void OpenPerformance() => Application.OpenURL("https://hybridclr.doc.code-philosophy.com/docs/basic/performance");
|
public static void OpenPerformance() => Application.OpenURL("https://www.hybridclr.cn/docs/basic/performance");
|
||||||
|
|
||||||
[MenuItem("HybridCLR/Documents/FAQ")]
|
[MenuItem("HybridCLR/Documents/FAQ")]
|
||||||
public static void OpenFAQ() => Application.OpenURL("https://hybridclr.doc.code-philosophy.com/docs/help/faq");
|
public static void OpenFAQ() => Application.OpenURL("https://www.hybridclr.cn/docs/help/faq");
|
||||||
|
|
||||||
[MenuItem("HybridCLR/Documents/Common Errors")]
|
[MenuItem("HybridCLR/Documents/Common Errors")]
|
||||||
public static void OpenCommonErrors() => Application.OpenURL("https://hybridclr.doc.code-philosophy.com/docs/help/commonerrors");
|
public static void OpenCommonErrors() => Application.OpenURL("https://www.hybridclr.cn/docs/help/commonerrors");
|
||||||
|
|
||||||
[MenuItem("HybridCLR/Documents/Bug Report")]
|
[MenuItem("HybridCLR/Documents/Bug Report")]
|
||||||
public static void OpenBugReport() => Application.OpenURL("https://hybridclr.doc.code-philosophy.com/docs/help/issue");
|
public static void OpenBugReport() => Application.OpenURL("https://www.hybridclr.cn/docs/help/issue");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,89 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using UnityEditor;
|
|
||||||
using UnityEditorInternal;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace HybridCLR.Editor.Settings
|
|
||||||
{
|
|
||||||
public class ScriptableSingleton<T> : ScriptableObject where T : ScriptableObject
|
|
||||||
{
|
|
||||||
private static T s_Instance;
|
|
||||||
public static T Instance
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (!s_Instance)
|
|
||||||
{
|
|
||||||
LoadOrCreate();
|
|
||||||
}
|
|
||||||
return s_Instance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public static T LoadOrCreate()
|
|
||||||
{
|
|
||||||
string filePath = GetFilePath();
|
|
||||||
if (!string.IsNullOrEmpty(filePath))
|
|
||||||
{
|
|
||||||
var arr = InternalEditorUtility.LoadSerializedFileAndForget(filePath);
|
|
||||||
s_Instance = arr.Length > 0 ? arr[0] as T : s_Instance??CreateInstance<T>();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Debug.LogError($"save location of {nameof(ScriptableSingleton<T>)} is invalid");
|
|
||||||
}
|
|
||||||
return s_Instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Save(bool saveAsText = true)
|
|
||||||
{
|
|
||||||
if (!s_Instance)
|
|
||||||
{
|
|
||||||
Debug.LogError("Cannot save ScriptableSingleton: no instance!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string filePath = GetFilePath();
|
|
||||||
if (!string.IsNullOrEmpty(filePath))
|
|
||||||
{
|
|
||||||
string directoryName = Path.GetDirectoryName(filePath);
|
|
||||||
if (!Directory.Exists(directoryName))
|
|
||||||
{
|
|
||||||
Directory.CreateDirectory(directoryName);
|
|
||||||
}
|
|
||||||
UnityEngine.Object[] obj = new T[1] { s_Instance };
|
|
||||||
InternalEditorUtility.SaveToSerializedFileAndForget(obj, filePath, saveAsText);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
protected static string GetFilePath()
|
|
||||||
{
|
|
||||||
return typeof(T).GetCustomAttributes(inherit: true)
|
|
||||||
.Where(v => v is FilePathAttribute)
|
|
||||||
.Cast<FilePathAttribute>()
|
|
||||||
.FirstOrDefault()
|
|
||||||
?.filepath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
[AttributeUsage(AttributeTargets.Class)]
|
|
||||||
public class FilePathAttribute : Attribute
|
|
||||||
{
|
|
||||||
internal string filepath;
|
|
||||||
/// <summary>
|
|
||||||
/// 单例存放路径
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="path">相对 Project 路径</param>
|
|
||||||
public FilePathAttribute(string path)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(path))
|
|
||||||
{
|
|
||||||
throw new ArgumentException("Invalid relative path (it is empty)");
|
|
||||||
}
|
|
||||||
if (path[0] == '/')
|
|
||||||
{
|
|
||||||
path = path.Substring(1);
|
|
||||||
}
|
|
||||||
filepath = path;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
using UnityEditor;
|
|
||||||
using UnityEditor.Presets;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace HybridCLR.Editor.Settings
|
|
||||||
{
|
|
||||||
public class SettingsPresetReceiver : PresetSelectorReceiver
|
|
||||||
{
|
|
||||||
private Object m_Target;
|
|
||||||
private Preset m_InitialValue;
|
|
||||||
private SettingsProvider m_Provider;
|
|
||||||
|
|
||||||
internal void Init(Object target, SettingsProvider provider)
|
|
||||||
{
|
|
||||||
m_Target = target;
|
|
||||||
m_InitialValue = new Preset(target);
|
|
||||||
m_Provider = provider;
|
|
||||||
}
|
|
||||||
public override void OnSelectionChanged(Preset selection)
|
|
||||||
{
|
|
||||||
if (selection != null)
|
|
||||||
{
|
|
||||||
Undo.RecordObject(m_Target, "Apply Preset " + selection.name);
|
|
||||||
selection.ApplyTo(m_Target);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Undo.RecordObject(m_Target, "Cancel Preset");
|
|
||||||
m_InitialValue.ApplyTo(m_Target);
|
|
||||||
}
|
|
||||||
m_Provider.Repaint();
|
|
||||||
}
|
|
||||||
public override void OnSelectionClosed(Preset selection)
|
|
||||||
{
|
|
||||||
OnSelectionChanged(selection);
|
|
||||||
Object.DestroyImmediate(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: d8373e0cb30c4894db7cd4d0b77a7a48
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
|
|
@ -65,9 +65,6 @@ namespace HybridCLR.Editor
|
||||||
public string name;
|
public string name;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 热更新dll列表。不包含 preserveHotUpdateAssemblies。
|
|
||||||
/// </summary>
|
|
||||||
public static List<string> HotUpdateAssemblyNamesExcludePreserved
|
public static List<string> HotUpdateAssemblyNamesExcludePreserved
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
|
||||||
2
LICENSE
2
LICENSE
|
|
@ -1,6 +1,6 @@
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2023 Code Philosophy Technology Ltd.
|
Copyright (c) 2025 Code Philosophy Technology Ltd.
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -1,87 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: b93c6604eb031674b80de14cd4458dc0
|
|
||||||
PluginImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
iconMap: {}
|
|
||||||
executionOrder: {}
|
|
||||||
defineConstraints: []
|
|
||||||
isPreloaded: 0
|
|
||||||
isOverridable: 0
|
|
||||||
isExplicitlyReferenced: 0
|
|
||||||
validateReferences: 1
|
|
||||||
platformData:
|
|
||||||
- first:
|
|
||||||
: Any
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
Exclude Android: 1
|
|
||||||
Exclude Editor: 0
|
|
||||||
Exclude Linux64: 1
|
|
||||||
Exclude OSXUniversal: 1
|
|
||||||
Exclude WebGL: 1
|
|
||||||
Exclude Win: 1
|
|
||||||
Exclude Win64: 1
|
|
||||||
Exclude iOS: 1
|
|
||||||
- first:
|
|
||||||
Android: Android
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
CPU: ARMv7
|
|
||||||
- first:
|
|
||||||
Any:
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings: {}
|
|
||||||
- first:
|
|
||||||
Editor: Editor
|
|
||||||
second:
|
|
||||||
enabled: 1
|
|
||||||
settings:
|
|
||||||
CPU: AnyCPU
|
|
||||||
DefaultValueInitialized: true
|
|
||||||
OS: AnyOS
|
|
||||||
- first:
|
|
||||||
Standalone: Linux64
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
CPU: None
|
|
||||||
- first:
|
|
||||||
Standalone: OSXUniversal
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
CPU: None
|
|
||||||
- first:
|
|
||||||
Standalone: Win
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
CPU: None
|
|
||||||
- first:
|
|
||||||
Standalone: Win64
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
CPU: None
|
|
||||||
- first:
|
|
||||||
Windows Store Apps: WindowsStoreApps
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
CPU: AnyCPU
|
|
||||||
- first:
|
|
||||||
iPhone: iOS
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
AddToEmbeddedBinaries: false
|
|
||||||
CPU: AnyCPU
|
|
||||||
CompileFlags:
|
|
||||||
FrameworkDependencies:
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
113
README.md
113
README.md
|
|
@ -1,7 +1,3 @@
|
||||||
|
|
||||||
- [README 中文](./README_zh.md)
|
|
||||||
- [README English](./README.md)
|
|
||||||
|
|
||||||
# HybridCLR
|
# HybridCLR
|
||||||
|
|
||||||
[](https://github.com/focus-creative-games/hybridclr/blob/main/LICENSE)
|
[](https://github.com/focus-creative-games/hybridclr/blob/main/LICENSE)
|
||||||
|
|
@ -11,81 +7,84 @@
|
||||||
<br/>
|
<br/>
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
HybridCLR is a **almost perfect** full-platform native c# hot update solution for Unity with complete features, zero cost, high performance, and low memory**.
|
[README 中文](./README.md) | [README English](./README_EN.md)
|
||||||
|
|
||||||
HybridCLR expands the ability of il2cpp, making it change from pure [AOT](https://en.wikipedia.org/wiki/Ahead-of-time_compilation) runtime to 'AOT+Interpreter' hybrid runtime, and then natively supports dynamic loading of assembly , so that the games packaged based on il2cpp backend can be executed not only on the Android platform, but also on IOS, Consoles and other platforms that limit JIT efficiently in **AOT+interpreter** hybrid mode, completely supporting hot updates from the bottom layer.
|
[Github](https://github.com/focus-creative-games/hybridclr) | [Gitee](https://gitee.com/focus-creative-games/hybridclr)
|
||||||
|
|
||||||
HybridCLR not only supports the traditional fully interpreted execution mode, but also pioneered [Differential Hybrid Execution](https://hybridclr.doc.code-philosophy.com/en/docs/business/differentialhybridexecution) technique. That is, you can add, delete, or modify the AOT dll at will, and intelligently make the changed or newly added classes and functions run in interpreter mode, but the unchanged classes and functions run in AOT mode, so that the running performance of the hot-updated game logic basically reaches the original AOT level.
|
HybridCLR是一个**特性完整、零成本、高性能、低内存**的**近乎完美**的Unity全平台原生c#热更新解决方案。
|
||||||
|
|
||||||
Welcome to embrace modern native C# hot update technology! ! !
|
HybridCLR扩充了il2cpp运行时代码,使它由纯[AOT](https://en.wikipedia.org/wiki/Ahead-of-time_compilation) runtime变成AOT+Interpreter 混合runtime,进而原生支持动态加载assembly,从底层彻底支持了热更新。使用HybridCLR技术的游戏不仅能在Android平台,也能在IOS、Consoles、WebGL等所有il2cpp支持的平台上高效运行。
|
||||||
|
|
||||||
## Documentation
|
由于HybridCLR对ECMA-335规范 的良好支持以及对Unity开发工作流的高度兼容,Unity项目在接入HybridCLR后,可以几乎无缝地获得C#代码热更新的能力,开发者不需要改变日常开发习惯和要求。HybridCLR首次实现了将Unity平台的全平台代码热更新方案的工程难度降到几乎为零的水平。
|
||||||
|
|
||||||
- [Official Documentation](https://hybridclr.doc.code-philosophy.com/en/docs/intro)
|
欢迎拥抱现代原生C#热更新技术 !!!
|
||||||
- [Quickstart](https://hybridclr.doc.code-philosophy.com/en/docs/beginner/quickstart)
|
|
||||||
- [Release Log](./RELEASELOG.md)
|
|
||||||
|
|
||||||
## Features
|
## 文档
|
||||||
|
|
||||||
- Features complete. A nearly complete implementation of the [ECMA-335 specification](https://www.ecma-international.org/publications-and-standards/standards/ecma-335/), with only a very small number of [unsupported features](https://hybridclr.doc.code-philosophy.com/en/docs/basic/notsupportedfeatures).
|
- [官方文档](https://www.hybridclr.cn/docs/intro)
|
||||||
- Zero learning and use costs. HybridCLR enhances the pure AOT runtime into a complete runtime, making hot update code work seamlessly with AOT code. The script class and the AOT class are in the same runtime, and you can freely write codes such as inheritance, reflection, and multi-threading (volatile, ThreadStatic, Task, async). No need to write any special code, no code generation, almost unlimited.
|
- [快速上手](https://www.hybridclr.cn/docs/beginner/quickstart)
|
||||||
- Efficient execution. Implemented an extremely efficient register interpreter, all indicators are significantly better than other hot update schemes. [Performance Test Report](https://hybridclr.doc.code-philosophy.com/en/docs/basic/performance)
|
- [商业项目案例](https://www.hybridclr.cn/docs/other/businesscase)
|
||||||
- Memory efficient. The classes defined in the hot update script occupy the same memory space as ordinary c# classes, which is far superior to other hot update solutions. [Memory and GC](https://hybridclr.doc.code-philosophy.com/en/docs/basic/memory)
|
|
||||||
- Due to the perfect support for generics, libraries that are not compatible with il2cpp due to AOT generics issues can now run perfectly under il2cpp
|
|
||||||
- Support some features not supported by il2cpp, such as __makeref, __reftype, __refvalue directives
|
|
||||||
- [Differential Hybrid Execution](https://hybridclr.doc.code-philosophy.com/en/docs/business/differentialhybridexecution)
|
|
||||||
|
|
||||||
## Working Principle
|
## 特性
|
||||||
|
|
||||||
HybridCLR is inspired by mono's [mixed mode execution](https://www.mono-project.com/news/2017/11/13/mono-interpreter/) technology, and provides additional AOT runtimes such as unity's il2cpp The interpreter module is provided to transform them from pure AOT runtime to "AOT + Interpreter" hybrid operation mode.
|
- 近乎完整实现了[ECMA-335规范](https://www.ecma-international.org/publications-and-standards/standards/ecma-335/),只有极少量的[不支持的特性](https://www.hybridclr.cn/docs/basic/notsupportedfeatures)。
|
||||||
|
- 零学习和使用成本。对绝大多数开发者来说写代码近乎没有限制。 热更新代码与AOT代码无缝工作,可以随意写继承、**泛型**、**反射**之类的代码。不需要额外写任何特殊代码、没有代码生成
|
||||||
|
- 完全支持多线程,包含但不限于 volatile、ThreadStatic、async Task等相关功能和特性。这是其他所有热更新方案都不支持的
|
||||||
|
- 几乎完全兼容Unity的工作流。包括且不限于支持热更新**MonoBehaviour**、ScriptableObject、**DOTS**技术,资源上挂载的热更新脚本可以正确实例化,这是其他所有热更新方案都不支持的
|
||||||
|
- 执行高效。实现了一个极其高效的寄存器解释器,所有指标都大幅优于其他热更新方案。[性能测试报告](https://www.hybridclr.cn/docs/basic/performance)
|
||||||
|
- 内存高效。 热更新脚本中定义的类跟普通c#类占用一样的内存空间,远优于其他热更新方案。[内存占用报告](https://www.hybridclr.cn/docs/basic/memory)
|
||||||
|
- 支持MonoPInvokeCallback,可以与native代码或者其他语言如lua、javascript、python良好交互
|
||||||
|
- 支持PInvoke
|
||||||
|
- 支持一些il2cpp不支持的特性,如\_\_makeref、 \_\_reftype、\_\_refvalue指令
|
||||||
|
- 支持独创的 **Differential Hybrid Execution(DHE)** 差分混合执行技术,即可以对AOT dll任意增删改,会智能地让未改动的函数以AOT方式运行,变化或者新增的函数以interpreter模式运行,让热更新的游戏逻辑的运行性能基本达到原生AOT的水平
|
||||||
|
- 支持**热重载**技术,可以100%卸载程序集
|
||||||
|
- 支持动态**Hotfix**技术,可以运行过程中无感修复代码bug
|
||||||
|
- 支持现代的dll加密技术,有效保障代码安全
|
||||||
|
|
||||||
|
## 支持的版本与平台
|
||||||
|
|
||||||
|
- 支持2019.4.x、2020.3.x、2021.3.x、2022.3.x、2023.2.x、6000.x.y 所有LTS版本
|
||||||
|
- 支持所有il2cpp支持的平台
|
||||||
|
- 支持团结引擎和鸿蒙平台
|
||||||
|
|
||||||
|
## 工作原理
|
||||||
|
|
||||||
|
HybridCLR从mono的 [mixed mode execution](https://www.mono-project.com/news/2017/11/13/mono-interpreter/) 技术中得到启发,为unity的il2cpp之类的AOT runtime额外提供了interpreter模块,将它们由纯AOT运行时改造为"AOT + Interpreter"混合运行方式。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
More specifically, HybridCLR does the following:
|
更具体地说,HybridCLR做了以下几点工作:
|
||||||
|
|
||||||
- Implemented an efficient metadata (dll) parsing library
|
- 实现了一个高效的元数据(dll)解析库
|
||||||
- Modified the metadata management module to realize the dynamic registration of metadata
|
- 改造了元数据管理模块,实现了元数据的动态注册
|
||||||
- Implemented a compiler from IL instruction set to custom register instruction set
|
- 实现了一个IL指令集到自定义的寄存器指令集的compiler
|
||||||
- Implemented an efficient register interpreter
|
- 实现了一个高效的寄存器解释器
|
||||||
- Provide a large number of instinct functions to improve the performance of the interpreter
|
- 额外提供大量的instinct函数,提升解释器性能
|
||||||
|
|
||||||
## The Difference From Other C# Hot-Update Solution
|
## 稳定性状况
|
||||||
|
|
||||||
HybridCLR is a native c# hot update solution. In layman's terms, il2cpp is equivalent to the aot module of mono, and HybridCLR is equivalent to the interpreter module of mono, and the combination of the two becomes a complete mono. HybridCLR makes il2cpp a full-featured runtime, natively (that is, through System.Reflection.Assembly.Load) supports dynamic loading of dlls, thereby supporting hot updates of the ios platform.
|
HybridCLR已经被广泛验证是非常高效、稳定的Unity热更新解决方案,良好满足大中型商业项目的稳定和性能要求。
|
||||||
|
|
||||||
Just because HybridCLR is implemented at the native runtime level, the types of the hot update part and the AOT part of the main project are completely equivalent and seamlessly unified. You can call, inherit, reflect, and multi-thread at will, without generating code or writing adapters.
|
目前已经有数千个商业游戏项目接入了HybridCLR,其中有超过千个已经在App Store和Google Player上线,仅仅iOS免费榜前500名中就有近百款使用了HybridCLR。上线的项目中包括MMORPG、重度卡牌、重度塔防之类的游戏。国内绝大多数**Top游戏公司**都已经在使用HybridCLR。
|
||||||
|
|
||||||
Other hot update solutions are independent vm, and the relationship with il2cpp is essentially equivalent to the relationship of embedding lua in mono. Therefore, the type system is not uniform. In order to allow the hot update type to inherit some AOT types, an adapter needs to be written, and the type in the interpreter cannot be recognized by the type system of the main project. Incomplete features, troublesome development, and low operating efficiency.
|
可查看我们已知的头部公司中使用HybridCLR并且已经上线的[项目列表](https://www.hybridclr.cn/docs/other/businesscase)。
|
||||||
|
|
||||||
## Supported Versions And Platforms
|
## 支持与联系
|
||||||
|
|
||||||
- Support 2019.4.x, 2020.3.x, 2021.3.x, 2022.3.x full series of LTS versions. The `2023.2.0ax` version is also supported, but not released to the public.
|
- 官方1群(3000人):651188171(满)
|
||||||
- Supports all il2cpp supported platforms
|
- 新手1群(3000人):428404198(满)
|
||||||
|
- 新手2群(2000人):680274677(满)
|
||||||
|
- 新手3群(2000人):**920714552(推荐)**
|
||||||
|
- discord频道 `https://discord.gg/BATfNfJnm2`
|
||||||
|
- 商业合作邮箱: business#code-philosophy.com
|
||||||
|
- [商业化支持](https://www.hybridclr.cn/docs/business/intro)
|
||||||
|
|
||||||
## Stability Status
|
## 关于作者
|
||||||
|
|
||||||
HybridCLR has been widely verified as a very efficient and stable solution for hot update of Unity.
|
**walon** :**Code Philosophy(代码哲学)** 创始人
|
||||||
|
|
||||||
The official versions of **extremely stable** 1.x, 2.x, and 3.x are currently released, which are sufficient to meet the stability requirements of large and medium-sized commercial projects.
|
毕业于清华大学物理系,2006年CMO金牌,奥数国家集训队成员,保送清华基科班。专注于游戏技术,擅长开发架构和基础技术设施。
|
||||||
At present, at least thousands of commercial game projects have been integrated, and hundreds of them have been launched on both ends. The online projects include MMORPG, heavy card, heavy tower defense and other games. **Most top game companies** (such as Tencent and NetEase) are already using HybridCLR.
|
|
||||||
|
|
||||||
You can read the [game projects in top game companies](https://hybridclr.doc.code-philosophy.com/en/docs/other/businesscase) those are using HybridCLR and has been launched.
|
## license
|
||||||
|
|
||||||
## Support And Contact
|
|
||||||
|
|
||||||
- Official 1 group: 651188171 (full)
|
|
||||||
- Novice QQ group 1: 428404198 (full)
|
|
||||||
- Novice QQ group 2: **680274677 (recommended)**
|
|
||||||
- discord channel [https://discord.gg/BATfNfJnm2](https://discord.gg/BATfNfJnm2)
|
|
||||||
- Business cooperation email: business#code-philosophy.com
|
|
||||||
- [Commercial Support](https://hybridclr.doc.code-philosophy.com/en/docs/business/intro)
|
|
||||||
|
|
||||||
## About The Author
|
|
||||||
|
|
||||||
**walon** : Founder of **Code Philosophy (code philosophy)**
|
|
||||||
|
|
||||||
Graduated from the Department of Physics of Tsinghua University, won the CMO gold medal in 2006, a member of the National Mathematical Olympiad Training Team, and was recommended to Tsinghua University for basic courses. Focus on game technology, good at developing architecture and basic technical facilities.
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
HybridCLR is licensed under the [MIT](https://github.com/focus-creative-games/hybridclr/blob/main/LICENSE) license
|
HybridCLR is licensed under the [MIT](https://github.com/focus-creative-games/hybridclr/blob/main/LICENSE) license
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
|
||||||
|
- [README Chinese](./README.md)
|
||||||
|
- [README English](./README_EN.md)
|
||||||
|
|
||||||
|
# HybridCLR
|
||||||
|
|
||||||
|
[](https://github.com/focus-creative-games/hybridclr/blob/main/LICENSE)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
HybridCLR is a **feature-complete, zero-cost, high-performance, low-memory** **near-perfect** Unity cross-platform native C# hot update solution.
|
||||||
|
|
||||||
|
HybridCLR extends the il2cpp runtime code, transforming it from a pure [AOT](https://en.wikipedia.org/wiki/Ahead-of-time_compilation) runtime to an AOT+Interpreter hybrid runtime, thereby natively supporting the dynamic loading of assemblies and fundamentally supporting hot updates from the bottom layer. Games using HybridCLR technology can not only run efficiently on the Android platform but also on all platforms supported by il2cpp, including iOS, Consoles, WebGL, etc.
|
||||||
|
|
||||||
|
Thanks to HybridCLR's good support for the ECMA-335 specification and its high compatibility with the Unity development workflow, Unity projects can almost seamlessly gain the ability to hot update C# code after integrating HybridCLR. Developers do not need to change their daily development habits and requirements. HybridCLR is the first to achieve the engineering difficulty of a full-platform code hot update solution for the Unity platform to almost zero.
|
||||||
|
|
||||||
|
Welcome to embrace modern native C# hot update technology!
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- [Official Documentation](https://www.hybridclr.cn/en/docs/intro)
|
||||||
|
- [Quick Start](https://www.hybridclr.cn/en/docs/beginner/quickstart)
|
||||||
|
- [Business Project Cases](https://www.hybridclr.cn/en/docs/other/businesscase)
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Nearly complete implementation of the [ECMA-335 specification](https://www.ecma-international.org/publications-and-standards/standards/ecma-335/), with only a very small number of [unsupported features](https://www.hybridclr.cn/en/docs/basic/notsupportedfeatures).
|
||||||
|
- Zero learning and usage costs. For most developers, writing code is almost unrestricted. Hot update code works seamlessly with AOT code, allowing for inheritance, **generics**, **reflection**, and other code without additional special code or code generation.
|
||||||
|
- Full support for multithreading, including but not limited to volatile, ThreadStatic, async Task, and related features and characteristics. This is not supported by any other hot update solution.
|
||||||
|
- Almost complete compatibility with Unity's workflow. This includes support for hot updating **MonoBehaviour**, ScriptableObject, **DOTS** technology, and correctly instantiating hot update scripts mounted on resources, which is not supported by any other hot update solution.
|
||||||
|
- Efficient execution. A highly efficient register interpreter has been implemented, with all indicators significantly better than other hot update solutions. [Performance Test Report](https://www.hybridclr.cn/en/docs/basic/performance)
|
||||||
|
- Efficient memory usage. Classes defined in hot update scripts occupy the same memory space as ordinary C# classes, far superior to other hot update solutions. [Memory Usage Report](https://www.hybridclr.cn/en/docs/basic/memory)
|
||||||
|
- Supports MonoPInvokeCallback, enabling good interaction with native code or other languages such as Lua, JavaScript, Python.
|
||||||
|
- Supports some features not supported by il2cpp, such as __makeref, __reftype, __refvalue instructions.
|
||||||
|
- Supports the unique **Differential Hybrid Execution (DHE)** technology, which allows for arbitrary additions, deletions, and modifications to AOT DLLs. It intelligently runs unchanged functions in AOT mode and changed or newly added functions in interpreter mode, bringing the performance of hot-updated game logic close to that of native AOT.
|
||||||
|
- Supports **hot reload** technology, allowing 100% unloading of assemblies.
|
||||||
|
- Supports modern DLL encryption technology to effectively protect code security.
|
||||||
|
|
||||||
|
## Supported Versions and Platforms
|
||||||
|
|
||||||
|
- Supports all LTS versions including 2019.4.x, 2020.3.x, 2021.3.x, 2022.3.x, 2023.2.x, 6000.x.y.
|
||||||
|
- Supports all platforms supported by il2cpp.
|
||||||
|
- Supports Tuanjie(China) Engine and HarmonyOS platform.
|
||||||
|
|
||||||
|
## Working Principle
|
||||||
|
|
||||||
|
HybridCLR draws inspiration from Mono's [mixed mode execution](https://www.mono-project.com/news/2017/11/13/mono-interpreter/) technology, providing an interpreter module for AOT runtimes like Unity's il2cpp, transforming them from pure AOT runtimes to "AOT + Interpreter" hybrid operation modes.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
More specifically, HybridCLR has done the following:
|
||||||
|
|
||||||
|
- Implemented an efficient metadata (dll) parsing library.
|
||||||
|
- Modified the metadata management module to achieve dynamic registration of metadata.
|
||||||
|
- Implemented a compiler that converts IL instructions to a custom register instruction set.
|
||||||
|
- Implemented an efficient register interpreter.
|
||||||
|
- Provided a large number of instinct functions to enhance interpreter performance.
|
||||||
|
|
||||||
|
## Stability Status
|
||||||
|
|
||||||
|
HybridCLR has been widely verified as an efficient and stable Unity hot update solution, meeting the stability and performance requirements of medium and large commercial projects.
|
||||||
|
|
||||||
|
Currently, thousands of commercial game projects have integrated HybridCLR, with over a thousand already launched on the App Store and Google Play. Nearly a hundred of the top 500 free iOS games use HybridCLR, including MMORPGs, heavy card games, and heavy tower defense games. Most of the **Top Game Companies** in China are already using HybridCLR.
|
||||||
|
|
||||||
|
You can view the [list of known top companies using HybridCLR and their launched projects](https://www.hybridclr.cn/en/docs/other/businesscase).
|
||||||
|
|
||||||
|
## Support and Contact
|
||||||
|
|
||||||
|
- Official Group 1: 651188171 (Full)
|
||||||
|
- Beginner Group 1: 428404198 (Full)
|
||||||
|
- Beginner Group 2: 680274677 (Full)
|
||||||
|
- Beginner Group 3: **920714552 (Recommended)**
|
||||||
|
- Discord channel https://discord.gg/BATfNfJnm2
|
||||||
|
- Business cooperation email: business#code-philosophy.com
|
||||||
|
- [Commercial Support](https://www.hybridclr.cn/en/docs/business/intro)
|
||||||
|
|
||||||
|
## About the Author
|
||||||
|
|
||||||
|
**walon**: Founder of **Code Philosophy (代码哲学)**
|
||||||
|
|
||||||
|
Graduated from the Department of Physics at Tsinghua University, gold medalist of the 2006 CMO, member of the National Mathematical Olympiad Training Team, and sent to the Basic Science Class of Tsinghua. Focused on game technology, proficient in development architecture and basic technical infrastructure.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
HybridCLR is licensed under the [MIT](https://github.com/focus-creative-games/hybridclr/blob/main/LICENSE) license.
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 52688f4469ff9454e8c3d8aaf045d9f4
|
guid: c9e34f237251ef44193538977db6b15f
|
||||||
TextScriptImporter:
|
TextScriptImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
userData:
|
userData:
|
||||||
91
README_zh.md
91
README_zh.md
|
|
@ -1,91 +0,0 @@
|
||||||
- [README 中文](./README_zh.md)
|
|
||||||
- [README English](./README.md)
|
|
||||||
|
|
||||||
# HybridCLR
|
|
||||||
|
|
||||||
[](https://github.com/focus-creative-games/hybridclr/blob/main/LICENSE)
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
HybridCLR是一个**特性完整、零成本、高性能、低内存**的**近乎完美**的Unity全平台原生c#热更方案。
|
|
||||||
|
|
||||||
HybridCLR扩充了il2cpp的代码,使它由纯[AOT](https://en.wikipedia.org/wiki/Ahead-of-time_compilation) runtime变成‘AOT+Interpreter’ 混合runtime,进而原生支持动态加载assembly,使得基于il2cpp backend打包的游戏不仅能在Android平台,也能在IOS、Consoles等限制了JIT的平台上高效地以**AOT+interpreter**混合模式执行,从底层彻底支持了热更新。
|
|
||||||
|
|
||||||
HybridCLR不仅支持传统的全解释执行模式,还开创性地实现了 [Differential Hybrid Execution(差分混合执行技术)](https://hybridclr.doc.code-philosophy.com/docs/business/differentialhybridexecution) 差分混合执行技术。即可以对AOT dll任意增删改,会智能地让变化或者新增的类和函数以interpreter模式运行,但未改动的类和函数以AOT方式运行,让热更新的游戏逻辑的运行性能基本达到原生AOT的水平。
|
|
||||||
|
|
||||||
欢迎拥抱现代原生C#热更新技术 !!!
|
|
||||||
|
|
||||||
## 文档
|
|
||||||
|
|
||||||
- [官方文档](https://hybridclr.doc.code-philosophy.com/docs/intro)
|
|
||||||
- [快速上手](https://hybridclr.doc.code-philosophy.com/docs/beginner/quickstart)
|
|
||||||
- [发布日志](./RELEASELOG.md)
|
|
||||||
|
|
||||||
## 特性
|
|
||||||
|
|
||||||
- 特性完整。 近乎完整实现了[ECMA-335规范](https://www.ecma-international.org/publications-and-standards/standards/ecma-335/),只有极少量的[不支持的特性](https://hybridclr.doc.code-philosophy.com/docs/basic/notsupportedfeatures)。
|
|
||||||
- 零学习和使用成本。 HybridCLR将纯AOT runtime增强为完整的runtime,使得热更新代码与AOT代码无缝工作。脚本类与AOT类在同一个运行时内,可以随意写继承、反射、多线程(volatile、ThreadStatic、Task、async)之类的代码。不需要额外写任何特殊代码、没有代码生成,几乎没有限制。
|
|
||||||
- 执行高效。实现了一个极其高效的寄存器解释器,所有指标都大幅优于其他热更新方案。[性能测试报告](https://hybridclr.doc.code-philosophy.com/docs/basic/performance)
|
|
||||||
- 内存高效。 热更新脚本中定义的类跟普通c#类占用一样的内存空间,远优于其他热更新方案。[内存与GC](https://hybridclr.doc.code-philosophy.com/docs/basic/memory)
|
|
||||||
- 由于对泛型的完美支持,使得因为AOT泛型问题跟il2cpp不兼容的库现在能够完美地在il2cpp下运行
|
|
||||||
- 支持一些il2cpp不支持的特性,如__makeref、 __reftype、__refvalue指令
|
|
||||||
- [Differential Hybrid Execution(差分混合执行技术)](https://hybridclr.doc.code-philosophy.com/docs/business/differentialhybridexecution)
|
|
||||||
|
|
||||||
## 工作原理
|
|
||||||
|
|
||||||
HybridCLR从mono的 [mixed mode execution](https://www.mono-project.com/news/2017/11/13/mono-interpreter/) 技术中得到启发,为unity的il2cpp之类的AOT runtime额外提供了interpreter模块,将它们由纯AOT运行时改造为"AOT + Interpreter"混合运行方式。
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
更具体地说,HybridCLR做了以下几点工作:
|
|
||||||
|
|
||||||
- 实现了一个高效的元数据(dll)解析库
|
|
||||||
- 改造了元数据管理模块,实现了元数据的动态注册
|
|
||||||
- 实现了一个IL指令集到自定义的寄存器指令集的compiler
|
|
||||||
- 实现了一个高效的寄存器解释器
|
|
||||||
- 额外提供大量的instinct函数,提升解释器性能
|
|
||||||
|
|
||||||
## 与其他流行的c#热更新方案的区别
|
|
||||||
|
|
||||||
HybridCLR是原生的c#热更新方案。通俗地说,il2cpp相当于mono的aot模块,HybridCLR相当于mono的interpreter模块,两者合一成为完整mono。HybridCLR使得il2cpp变成一个全功能的runtime,原生(即通过System.Reflection.Assembly.Load)支持动态加载dll,从而支持ios平台的热更新。
|
|
||||||
|
|
||||||
正因为HybridCLR是原生runtime级别实现,热更新部分的类型与主工程AOT部分类型是完全等价并且无缝统一的。可以随意调用、继承、反射、多线程,不需要生成代码或者写适配器。
|
|
||||||
|
|
||||||
其他热更新方案则是独立vm,与il2cpp的关系本质上相当于mono中嵌入lua的关系。因此类型系统不统一,为了让热更新类型能够继承AOT部分类型,需要写适配器,并且解释器中的类型不能为主工程的类型系统所识别。特性不完整、开发麻烦、运行效率低下。
|
|
||||||
|
|
||||||
## 支持的版本与平台
|
|
||||||
|
|
||||||
- 支持2019.4.x、2020.3.x、2021.3.x、2022.3.x全系列LTS版本。`2023.2.0ax`版本也已支持,但未对外发布。
|
|
||||||
- 支持所有il2cpp支持的平台
|
|
||||||
|
|
||||||
## 稳定性状况
|
|
||||||
|
|
||||||
HybridCLR已经被广泛验证是非常高效、稳定的Unity热更新解决方案。
|
|
||||||
|
|
||||||
当前发布了**极其稳定**的1.x、2.x、3.x正式版本,足以满足大中型商业项目的稳定性要求。
|
|
||||||
目前至少有上千个商业游戏项目完成接入,其中有几百款已经双端上线,上线的项目中包括MMORPG、重度卡牌、重度塔防之类的游戏。**绝大多数头部游戏公司**(如腾讯、网易)都已经在使用HybridCLR。
|
|
||||||
|
|
||||||
可查看我们已知的头部公司中使用HybridCLR并且已经上线的[项目列表](https://hybridclr.doc.code-philosophy.com/docs/other/businesscase)。
|
|
||||||
|
|
||||||
|
|
||||||
## 支持与联系
|
|
||||||
|
|
||||||
- 官方1群:651188171(满)
|
|
||||||
- 新手1群:428404198(满)
|
|
||||||
- 新手2群:**680274677(推荐)**
|
|
||||||
- discord频道 https://discord.gg/BATfNfJnm2
|
|
||||||
- 商业合作邮箱: business#code-philosophy.com
|
|
||||||
- [商业化支持](https://hybridclr.doc.code-philosophy.com/docs/business/intro)
|
|
||||||
|
|
||||||
## 关于作者
|
|
||||||
|
|
||||||
**walon** :**Code Philosophy(代码哲学)** 创始人
|
|
||||||
|
|
||||||
毕业于清华大学物理系,2006年CMO金牌,奥数国家集训队成员,保送清华基科班。专注于游戏技术,擅长开发架构和基础技术设施。
|
|
||||||
|
|
||||||
## license
|
|
||||||
|
|
||||||
HybridCLR is licensed under the [MIT](https://github.com/focus-creative-games/hybridclr/blob/main/LICENSE) license
|
|
||||||
964
RELEASELOG.md
964
RELEASELOG.md
File diff suppressed because it is too large
Load Diff
|
|
@ -3,8 +3,8 @@ namespace HybridCLR
|
||||||
{
|
{
|
||||||
public enum HomologousImageMode
|
public enum HomologousImageMode
|
||||||
{
|
{
|
||||||
Consistent, // AOT dll需要跟主工程精确一致,即为裁剪后的AO dll
|
Consistent,
|
||||||
SuperSet, // AOT dll不需要跟主工程精确一致,但必须包含裁剪后的AOT dll的所有元数据,即为裁剪后dll的超集。推荐使用原始aot dll
|
SuperSet,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,16 @@ namespace HybridCLR
|
||||||
public enum LoadImageErrorCode
|
public enum LoadImageErrorCode
|
||||||
{
|
{
|
||||||
OK = 0,
|
OK = 0,
|
||||||
BAD_IMAGE, // dll 不合法
|
BAD_IMAGE, // invalid dll file
|
||||||
NOT_IMPLEMENT, // 不支持的元数据特性
|
NOT_IMPLEMENT, // not implement feature
|
||||||
AOT_ASSEMBLY_NOT_FIND, // 对应的AOT assembly未找到
|
AOT_ASSEMBLY_NOT_FIND, // AOT assembly not found
|
||||||
HOMOLOGOUS_ONLY_SUPPORT_AOT_ASSEMBLY, // 不能给解释器assembly补充元数据
|
HOMOLOGOUS_ONLY_SUPPORT_AOT_ASSEMBLY, // can not load supplementary metadata assembly for non-AOT assembly
|
||||||
HOMOLOGOUS_ASSEMBLY_HAS_LOADED, // 已经补充过了,不能再次补充
|
HOMOLOGOUS_ASSEMBLY_HAS_LOADED, // can not load supplementary metadata assembly for the same assembly
|
||||||
INVALID_HOMOLOGOUS_MODE, // 非法HomologousImageMode
|
INVALID_HOMOLOGOUS_MODE, // invalid homologous image mode
|
||||||
};
|
PDB_BAD_FILE, // invalid pdb file
|
||||||
|
UNKNOWN_IMAGE_FORMAT,
|
||||||
|
UNSUPPORT_FORMAT_VERSION,
|
||||||
|
UNMATCH_FORMAT_VARIANT,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,16 @@ using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine.Scripting;
|
||||||
|
|
||||||
namespace HybridCLR
|
namespace HybridCLR
|
||||||
{
|
{
|
||||||
|
[Preserve]
|
||||||
public static class RuntimeApi
|
public static class RuntimeApi
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 加载补充元数据assembly
|
/// load supplementary metadata assembly
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dllBytes"></param>
|
/// <param name="dllBytes"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
|
@ -28,7 +31,38 @@ namespace HybridCLR
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取解释器线程栈的最大StackObject个数(size*8 为最终占用的内存大小)
|
/// prejit method to avoid the jit cost of first time running
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="method"></param>
|
||||||
|
/// <returns>return true if method is jited, return false if method can't be jited </returns>
|
||||||
|
///
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
public static bool PreJitMethod(MethodInfo method)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||||
|
public static extern bool PreJitMethod(MethodInfo method);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// prejit all methods of class to avoid the jit cost of first time running
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type"></param>
|
||||||
|
/// <returns>return true if class is jited, return false if class can't be jited </returns>
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
public static bool PreJitClass(Type type)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||||
|
public static extern bool PreJitClass(Type type);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// get the maximum number of StackObjects in the interpreter thread stack (size*8 represents the final memory size occupied
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static int GetInterpreterThreadObjectStackSize()
|
public static int GetInterpreterThreadObjectStackSize()
|
||||||
|
|
@ -37,7 +71,7 @@ namespace HybridCLR
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设置解释器线程栈的最大StackObject个数(size*8 为最终占用的内存大小)
|
/// set the maximum number of StackObjects for the interpreter thread stack (size*8 represents the final memory size occupied)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="size"></param>
|
/// <param name="size"></param>
|
||||||
public static void SetInterpreterThreadObjectStackSize(int size)
|
public static void SetInterpreterThreadObjectStackSize(int size)
|
||||||
|
|
@ -47,7 +81,7 @@ namespace HybridCLR
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取解释器线程函数帧数量(sizeof(InterpreterFrame)*size 为最终占用的内存大小)
|
/// get the number of interpreter thread function frames (sizeof(InterpreterFrame)*size represents the final memory size occupied)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static int GetInterpreterThreadFrameStackSize()
|
public static int GetInterpreterThreadFrameStackSize()
|
||||||
|
|
@ -56,7 +90,7 @@ namespace HybridCLR
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设置解释器线程函数帧数量(sizeof(InterpreterFrame)*size 为最终占用的内存大小)
|
/// set the number of interpreter thread function frames (sizeof(InterpreterFrame)*size represents the final memory size occupied)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="size"></param>
|
/// <param name="size"></param>
|
||||||
public static void SetInterpreterThreadFrameStackSize(int size)
|
public static void SetInterpreterThreadFrameStackSize(int size)
|
||||||
|
|
@ -69,6 +103,11 @@ namespace HybridCLR
|
||||||
|
|
||||||
private static readonly Dictionary<RuntimeOptionId, int> s_runtimeOptions = new Dictionary<RuntimeOptionId, int>();
|
private static readonly Dictionary<RuntimeOptionId, int> s_runtimeOptions = new Dictionary<RuntimeOptionId, int>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// set runtime option value
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="optionId"></param>
|
||||||
|
/// <param name="value"></param>
|
||||||
public static void SetRuntimeOption(RuntimeOptionId optionId, int value)
|
public static void SetRuntimeOption(RuntimeOptionId optionId, int value)
|
||||||
{
|
{
|
||||||
s_runtimeOptions[optionId] = value;
|
s_runtimeOptions[optionId] = value;
|
||||||
|
|
@ -78,6 +117,11 @@ namespace HybridCLR
|
||||||
public static extern void SetRuntimeOption(RuntimeOptionId optionId, int value);
|
public static extern void SetRuntimeOption(RuntimeOptionId optionId, int value);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// get runtime option value
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="optionId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
public static int GetRuntimeOption(RuntimeOptionId optionId)
|
public static int GetRuntimeOption(RuntimeOptionId optionId)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -5,5 +5,8 @@
|
||||||
InterpreterThreadObjectStackSize = 1,
|
InterpreterThreadObjectStackSize = 1,
|
||||||
InterpreterThreadFrameStackSize = 2,
|
InterpreterThreadFrameStackSize = 2,
|
||||||
ThreadExceptionFlowSize = 3,
|
ThreadExceptionFlowSize = 3,
|
||||||
|
MaxMethodBodyCacheSize = 4,
|
||||||
|
MaxMethodInlineDepth = 5,
|
||||||
|
MaxInlineableMethodBodySize = 6,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
{
|
{
|
||||||
"name": "com.code-philosophy.hybridclr",
|
"name": "com.code-philosophy.hybridclr",
|
||||||
"version": "5.1.0",
|
"version": "8.6.0",
|
||||||
"displayName": "HybridCLR",
|
"displayName": "HybridCLR",
|
||||||
"description": "HybridCLR is a fully featured, zero-cost, high-performance, low-memory solution for Unity's all-platform native c# hotupdate.",
|
"description": "HybridCLR is a fully featured, zero-cost, high-performance, low-memory solution for Unity's all-platform native c# hotupdate.",
|
||||||
"category": "Runtime",
|
"category": "Scripting",
|
||||||
"documentationUrl": "https://hybridclr.doc.code-philosophy.com/#/",
|
"documentationUrl": "https://www.hybridclr.cn/#/",
|
||||||
"changelogUrl": "https://hybridclr.doc.code-philosophy.com/#/other/changelog",
|
"changelogUrl": "https://www.hybridclr.cn/#/other/changelog",
|
||||||
"licensesUrl": "https://github.com/focus-creative-games/hybridclr_unity/blob/main/LICENSE",
|
"licensesUrl": "https://github.com/focus-creative-games/hybridclr_unity/blob/main/LICENSE",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"HybridCLR",
|
"HybridCLR",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue