using IPA.Utilities.Async; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Threading; #if NET3 using Net3_Proxy; using Array = Net3_Proxy.Array; #endif namespace IPA.Utilities { /// /// A type containing utilities for accessing non-public fields of objects. /// /// the type that the fields are on /// the type of the field to access /// public static class FieldAccessor { /// /// A delegate for a field accessor taking a ref and returning a ref. /// /// the object to access the field of /// a reference to the field's value public delegate ref U Accessor(ref T obj); private static Accessor MakeAccessor(string fieldName) { var field = typeof(T).GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly); if (field == null) throw new MissingFieldException(typeof(T).Name, fieldName); if (field.FieldType != typeof(U)) throw new ArgumentException($"Field '{fieldName}' not of type {typeof(U)}"); var dynMethodName = $"<>_accessor__{fieldName}"; // unfortunately DynamicMethod doesn't like having a ByRef return type, so reflection it var dyn = new DynamicMethod(dynMethodName, typeof(U), new[] { typeof(T).MakeByRefType() }, typeof(FieldAccessor), true); ReflectionUtil.DynamicMethodReturnType.SetValue(dyn, typeof(U).MakeByRefType()); var il = dyn.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); if (!typeof(T).IsValueType) il.Emit(OpCodes.Ldind_Ref); il.Emit(OpCodes.Ldflda, field); il.Emit(OpCodes.Ret); return (Accessor)dyn.CreateDelegate(typeof(Accessor)); } // field name -> accessor private static readonly SingleCreationValueCache accessors = new SingleCreationValueCache(); /// /// Gets an for the field named on . /// /// the field name /// an accessor for the field /// if the field does not exist on public static Accessor GetAccessor(string name) => accessors.GetOrAdd(name, MakeAccessor); /// /// Accesses a field for an object by name. /// /// the object to access the field of /// the name of the field to access /// a reference to the object at the field /// if the field does not exist on /// public static ref U Access(ref T obj, string name) => ref GetAccessor(name)(ref obj); /// /// Gets the value of a field of an object by name. /// /// /// The only good reason to use this over is when you are working with a value type, /// as it prevents a copy. /// /// the object to access the field of /// the name of the field to access /// the value of the field /// if the field does not exist on /// /// /// public static U Get(ref T obj, string name) => Access(ref obj, name); /// /// Gets the value of a field of an object by name. /// /// the object to access the field of /// the name of the field to access /// the value of the field /// if the field does not exist on /// /// /// public static U Get(T obj, string name) => Get(ref obj, name); /// /// Sets the value of a field for an object by name. /// /// /// This overload must be used for value types. /// /// the object to set the field of /// the name of the field /// the value to set it to /// if the field does not exist on /// /// /// public static void Set(ref T obj, string name, U value) => Access(ref obj, name) = value; /// /// Sets the value of a field for an object by name. /// /// /// This overload cannot be safely used for value types. Use instead. /// /// the object to set the field of /// the name of the field /// the value to set it to /// if the field does not exist on /// /// /// public static void Set(T obj, string name, U value) => Set(ref obj, name, value); } /// /// A type containing utilities for accessing non-public properties of an object. /// /// the type that the properties are on /// the type of the property to access public static class PropertyAccessor { /// /// A getter for a property. /// /// the object it is a member of /// the value of the property public delegate U Getter(ref T obj); /// /// A setter for a property. /// /// the object it is a member of /// the new property value public delegate void Setter(ref T obj, U val); private static (Getter, Setter) MakeAccessors(string propName) { var prop = typeof(T).GetProperty(propName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly); if (prop == null) throw new MissingMemberException(typeof(T).Name, propName); if (prop.PropertyType != typeof(U)) throw new ArgumentException($"Property '{propName}' on {typeof(T)} is not of type {typeof(U)}"); var getM = prop.GetGetMethod(true); var setM = prop.GetSetMethod(true); Getter getter = null; Setter setter = null; if (typeof(T).IsValueType) { if (getM != null) getter = (Getter)Delegate.CreateDelegate(typeof(Getter), getM); if (setM != null) setter = (Setter)Delegate.CreateDelegate(typeof(Setter), setM); } else { if (getM != null) { var dyn = new DynamicMethod($"<>_get__{propName}", typeof(U), new[] { typeof(T).MakeByRefType() }, typeof(PropertyAccessor), true); var il = dyn.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldind_Ref); il.Emit(OpCodes.Tailcall); il.Emit(getM.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, getM); il.Emit(OpCodes.Ret); getter = (Getter)dyn.CreateDelegate(typeof(Getter)); } if (setM != null) { var dyn = new DynamicMethod($"<>_set__{propName}", typeof(void), new[] { typeof(T).MakeByRefType(), typeof(U) }, typeof(PropertyAccessor), true); var il = dyn.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldind_Ref); il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Tailcall); il.Emit(setM.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, setM); il.Emit(OpCodes.Ret); setter = (Setter)dyn.CreateDelegate(typeof(Setter)); } } return (getter, setter); } private static readonly SingleCreationValueCache props = new SingleCreationValueCache(); private static (Getter get, Setter set) GetAccessors(string propName) => props.GetOrAdd(propName, MakeAccessors); /// /// Gets a for the property identified by . /// /// the name of the property /// a that can access that property /// if the property does not exist public static Getter GetGetter(string name) => GetAccessors(name).get; /// /// Gets a for the property identified by . /// /// the name of the property /// a that can access that property /// if the property does not exist public static Setter GetSetter(string name) => GetAccessors(name).set; /// /// Gets the value of the property identified by on . /// /// /// The only reason to use this over is if you are using a value type because /// it avoids a copy. /// /// the instance to access /// the name of the property /// the value of the property /// if the property does not exist /// /// public static U Get(ref T obj, string name) => GetGetter(name)(ref obj); /// /// Gets the value of the property identified by on . /// /// the instance to access /// the name of the property /// the value of the property /// if the property does not exist /// /// public static U Get(T obj, string name) => GetGetter(name)(ref obj); /// /// Sets the value of the property identified by on . /// /// /// This overload must be used for value types. /// /// the instance to access /// the name of the property /// the new value of the property /// if the property does not exist /// /// public static void Set(ref T obj, string name, U val) => GetSetter(name)(ref obj, val); /// /// Sets the value of the property identified by on . /// /// /// This overload cannot be safely used for value types. Use instead. /// /// the instance to access /// the name of the property /// the new value of the property /// if the property does not exist /// /// public static void Set(T obj, string name, U val) => GetSetter(name)(ref obj, val); } internal class AccessorDelegateInfo where TDelegate : Delegate { public static readonly Type Type = typeof(TDelegate); public static readonly MethodInfo Invoke = Type.GetMethod("Invoke"); public static readonly ParameterInfo[] Parameters = Invoke.GetParameters(); } /// /// A type containing utilities for calling non-public methods on an object. /// /// the type to find the methods on /// the delegate type to create, and to use as a signature to search for public static class MethodAccessor where TDelegate : Delegate { static MethodAccessor() { // ensure that first argument of delegate type is valid var firstArg = AccessorDelegateInfo.Parameters.First(); var firstType = firstArg.ParameterType; if (typeof(T).IsValueType) { if (!firstType.IsByRef) throw new InvalidOperationException("First parameter of a method accessor to a value type is not byref"); else firstType = firstType.GetElementType(); // get the non-byref type to check compatability } if (!typeof(T).IsAssignableFrom(firstType)) throw new InvalidOperationException("First parameter of a method accessor is not assignable to the method owning type"); } private static TDelegate MakeDelegate(string name) { var delParams = AccessorDelegateInfo.Parameters; var method = typeof(T).GetMethod(name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly, null, delParams.Skip(1).Select(p => p.ParameterType).ToArray(), Array.Empty()); if (method == null) throw new MissingMethodException(typeof(T).FullName, name); var retType = AccessorDelegateInfo.Invoke.ReturnType; if (!retType.IsAssignableFrom(method.ReturnType)) throw new ArgumentException($"The method found returns a type incompatable with the return type of {typeof(TDelegate)}"); return (TDelegate)Delegate.CreateDelegate(AccessorDelegateInfo.Type, method, true); } private static readonly SingleCreationValueCache methods = new SingleCreationValueCache(); /// /// Gets a delegate to the named method with the signature specified by . /// /// the name of the method to get /// a delegate that can call the specified method /// if does not represent the name of a method with the given signature /// if the method found returns a type incompatable with the return type of public static TDelegate GetDelegate(string name) => methods.GetOrAdd(name, MakeDelegate); } }