Skip to content

Commit

Permalink
Improve Cast and PointerToValueGeneric performance
Browse files Browse the repository at this point in the history
  • Loading branch information
js6pak committed Oct 15, 2022
1 parent 89b9c12 commit 880b9d5
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 33 deletions.
9 changes: 4 additions & 5 deletions Il2CppInterop.Runtime/IL2CPP.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ public static unsafe class IL2CPP
{
private static readonly Dictionary<string, IntPtr> ourImagesMap = new();

private static readonly MethodInfo CastMethod = typeof(Il2CppObjectBase).GetMethod(nameof(Il2CppObjectBase.Cast));

static IL2CPP()
{
var domain = il2cpp_domain_get();
Expand Down Expand Up @@ -299,10 +297,11 @@ private static T GenerateDelegateForMissingICall<T>(string signature) where T :
if (objectPointer == IntPtr.Zero)
return default;

var nativeObject = new Il2CppObjectBase(objectPointer);
if (typeof(T).IsValueType)
return nativeObject.UnboxUnsafe<T>();
return (T)CastMethod.MakeGenericMethod(typeof(T)).Invoke(nativeObject, new object[0]);
return Il2CppObjectBase.UnboxUnsafe<T>(objectPointer);

var il2CppObjectBase = Il2CppObjectBase.CreateUnsafe<T>(objectPointer);
return Unsafe.As<Il2CppObjectBase, T>(ref il2CppObjectBase);
}

public static string RenderTypeName<T>(bool addRefMarker = false)
Expand Down
117 changes: 89 additions & 28 deletions Il2CppInterop.Runtime/InteropTypes/Il2CppObjectBase.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
Expand Down Expand Up @@ -60,32 +61,108 @@ public T Cast<T>() where T : Il2CppObjectBase
$"Can't cast object of type {Marshal.PtrToStringAnsi(IL2CPP.il2cpp_class_get_name(IL2CPP.il2cpp_object_get_class(Pointer)))} to type {typeof(T)}");
}

internal unsafe T UnboxUnsafe<T>()
internal static unsafe T UnboxUnsafe<T>(IntPtr pointer)
{
var nestedTypeClassPointer = Il2CppClassPointerStore<T>.NativeClassPtr;
if (nestedTypeClassPointer == IntPtr.Zero)
throw new ArgumentException($"{typeof(T)} is not an Il2Cpp reference type");

var ownClass = IL2CPP.il2cpp_object_get_class(Pointer);
var ownClass = IL2CPP.il2cpp_object_get_class(pointer);
if (!IL2CPP.il2cpp_class_is_assignable_from(nestedTypeClassPointer, ownClass))
throw new InvalidCastException(
$"Can't cast object of type {Marshal.PtrToStringAnsi(IL2CPP.il2cpp_class_get_name(IL2CPP.il2cpp_object_get_class(Pointer)))} to type {typeof(T)}");
$"Can't cast object of type {Marshal.PtrToStringAnsi(IL2CPP.il2cpp_class_get_name(ownClass))} to type {typeof(T)}");

return Unsafe.AsRef<T>(IL2CPP.il2cpp_object_unbox(pointer).ToPointer());
}

public T Unbox<T>() where T : unmanaged
{
return UnboxUnsafe<T>(Pointer);
}

private static readonly Type[] _intPtrTypeArray = { typeof(IntPtr) };
private static readonly MethodInfo _getUninitializedObject = typeof(RuntimeHelpers).GetMethod(nameof(RuntimeHelpers.GetUninitializedObject))!;
private static readonly MethodInfo _getTypeFromHandle = typeof(Type).GetMethod(nameof(Type.GetTypeFromHandle))!;
private static readonly MethodInfo _createGCHandle = typeof(Il2CppObjectBase).GetMethod(nameof(CreateGCHandle))!;
private static readonly FieldInfo _isWrapped = typeof(Il2CppObjectBase).GetField(nameof(isWrapped))!;

private static class InitializerStore<T>
{
private static Func<IntPtr, T>? _initializer;

private static Func<IntPtr, T> Create()
{
var type = Il2CppClassPointerStore<T>.CreatedTypeRedirect ?? typeof(T);

var dynamicMethod = new DynamicMethod($"Initializer<{typeof(T).AssemblyQualifiedName}>", type, _intPtrTypeArray);
dynamicMethod.DefineParameter(0, ParameterAttributes.None, "pointer");

var il = dynamicMethod.GetILGenerator();

if (type.GetConstructor(new[] { typeof(IntPtr) }) is { } pointerConstructor)
{
// Base case: Il2Cpp constructor => call it directly
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Newobj, pointerConstructor);
}
else
{
// Special case: We have a parameterless constructor
// However, it could be be user-made or implicit
// In that case we set the GCHandle and then call the ctor and let GC destroy any objects created by DerivedConstructorPointer

// var obj = (T)FormatterServices.GetUninitializedObject(type);
il.Emit(OpCodes.Ldtoken, type);
il.Emit(OpCodes.Call, _getTypeFromHandle);
il.Emit(OpCodes.Call, _getUninitializedObject);
il.Emit(OpCodes.Castclass, type);

// obj.CreateGCHandle(pointer);
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Callvirt, _createGCHandle);

// obj.isWrapped = true;
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Ldc_I4_1);
il.Emit(OpCodes.Stsfld, _isWrapped);

var parameterlessConstructor = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, Type.EmptyTypes);
if (parameterlessConstructor != null)
{
// obj..ctor();
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Callvirt, parameterlessConstructor);
}
}

il.Emit(OpCodes.Ret);

return dynamicMethod.CreateDelegate<Func<IntPtr, T>>();
}

return Unsafe.AsRef<T>(IL2CPP.il2cpp_object_unbox(Pointer).ToPointer());
public static Func<IntPtr, T> Initializer => _initializer ??= Create();
}

public unsafe T Unbox<T>() where T : unmanaged
internal static Il2CppObjectBase CreateUnsafe<T>(IntPtr pointer)
{
var nestedTypeClassPointer = Il2CppClassPointerStore<T>.NativeClassPtr;
if (nestedTypeClassPointer == IntPtr.Zero)
throw new ArgumentException($"{typeof(T)} is not an Il2Cpp reference type");

var ownClass = IL2CPP.il2cpp_object_get_class(Pointer);
var ownClass = IL2CPP.il2cpp_object_get_class(pointer);
if (!IL2CPP.il2cpp_class_is_assignable_from(nestedTypeClassPointer, ownClass))
throw new InvalidCastException(
$"Can't cast object of type {Marshal.PtrToStringAnsi(IL2CPP.il2cpp_class_get_name(IL2CPP.il2cpp_object_get_class(Pointer)))} to type {typeof(T)}");
var unboxedPtr = IL2CPP.il2cpp_object_unbox(Pointer);
return Unsafe.AsRef<T>(unboxedPtr.ToPointer());
return null;

if (RuntimeSpecificsStore.IsInjected(ownClass))
{
var monoObject = ClassInjectorBase.GetMonoObjectFromIl2CppPointer(pointer);
if (monoObject is T) return (Il2CppObjectBase)monoObject;
}

var il2CppObjectBase = InitializerStore<T>.Initializer(pointer);
return Unsafe.As<T, Il2CppObjectBase>(ref il2CppObjectBase);
}

public T? TryCast<T>() where T : Il2CppObjectBase
Expand All @@ -100,26 +177,10 @@ public unsafe T Unbox<T>() where T : unmanaged

if (RuntimeSpecificsStore.IsInjected(ownClass))
{
var monoObject = ClassInjectorBase.GetMonoObjectFromIl2CppPointer(Pointer) as T;
if (monoObject != null) return monoObject;
if (ClassInjectorBase.GetMonoObjectFromIl2CppPointer(Pointer) is T monoObject) return monoObject;
}

var type = Il2CppClassPointerStore<T>.CreatedTypeRedirect ?? typeof(T);
// Base case: Il2Cpp constructor => call it directly
if (type.GetConstructor(new[] { typeof(IntPtr) }) != null)
return (T)Activator.CreateInstance(type, Pointer);

// Special case: We have a parameterless constructor
// However, it could be be user-made or implicit
// In that case we set the GCHandle and then call the ctor and let GC destroy any objects created by DerivedConstructorPointer
var obj = (T)FormatterServices.GetUninitializedObject(type);
obj.CreateGCHandle(Pointer);
obj.isWrapped = true;
var ctor = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null,
Type.EmptyTypes, Array.Empty<ParameterModifier>());
if (ctor != null)
ctor.Invoke(obj, null);
return obj;
return InitializerStore<T>.Initializer(Pointer);
}

~Il2CppObjectBase()
Expand Down

0 comments on commit 880b9d5

Please sign in to comment.