You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

343 lines
17 KiB

4 years ago
4 years ago
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Reflection;
  5. using System.Reflection.Emit;
  6. #if NET3
  7. using Net3_Proxy;
  8. using Array = Net3_Proxy.Array;
  9. #endif
  10. namespace IPA.Utilities
  11. {
  12. /// <summary>
  13. /// A type containing utilities for accessing non-public fields of objects.
  14. /// </summary>
  15. /// <typeparam name="T">the type that the fields are on</typeparam>
  16. /// <typeparam name="U">the type of the field to access</typeparam>
  17. /// <seealso cref="PropertyAccessor{T, U}"/>
  18. public static class FieldAccessor<T, U>
  19. {
  20. /// <summary>
  21. /// A delegate for a field accessor taking a <typeparamref name="T"/> ref and returning a <typeparamref name="U"/> ref.
  22. /// </summary>
  23. /// <param name="obj">the object to access the field of</param>
  24. /// <returns>a reference to the field's value</returns>
  25. public delegate ref U Accessor(ref T obj);
  26. // field name -> accessor
  27. private static readonly Dictionary<string, Accessor> accessors = new Dictionary<string, Accessor>();
  28. private static Accessor MakeAccessor(string fieldName)
  29. {
  30. var field = typeof(T).GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
  31. if (field == null)
  32. throw new MissingFieldException(typeof(T).Name, fieldName);
  33. if (field.FieldType != typeof(U))
  34. throw new ArgumentException($"Field '{fieldName}' not of type {typeof(U)}");
  35. var dynMethodName = $"<>_accessor__{fieldName}";
  36. // unfortunately DynamicMethod doesn't like having a ByRef return type, so reflection it
  37. var dyn = new DynamicMethod(dynMethodName, typeof(U), new[] { typeof(T).MakeByRefType() }, typeof(FieldAccessor<T, U>), true);
  38. ReflectionUtil.DynamicMethodReturnType.SetValue(dyn, typeof(U).MakeByRefType());
  39. var il = dyn.GetILGenerator();
  40. il.Emit(OpCodes.Ldarg_0);
  41. if (!typeof(T).IsValueType)
  42. il.Emit(OpCodes.Ldind_Ref);
  43. il.Emit(OpCodes.Ldflda, field);
  44. il.Emit(OpCodes.Ret);
  45. return (Accessor)dyn.CreateDelegate(typeof(Accessor));
  46. }
  47. /// <summary>
  48. /// Gets an <see cref="Accessor"/> for the field named <paramref name="name"/> on <typeparamref name="T"/>.
  49. /// </summary>
  50. /// <param name="name">the field name</param>
  51. /// <returns>an accessor for the field</returns>
  52. /// <exception cref="MissingFieldException">if the field does not exist on <typeparamref name="T"/></exception>
  53. public static Accessor GetAccessor(string name)
  54. {
  55. if (!accessors.TryGetValue(name, out var accessor))
  56. accessors.Add(name, accessor = MakeAccessor(name));
  57. return accessor;
  58. }
  59. /// <summary>
  60. /// Accesses a field for an object by name.
  61. /// </summary>
  62. /// <param name="obj">the object to access the field of</param>
  63. /// <param name="name">the name of the field to access</param>
  64. /// <returns>a reference to the object at the field</returns>
  65. /// <exception cref="MissingFieldException">if the field does not exist on <typeparamref name="T"/></exception>
  66. /// <seealso cref="GetAccessor(string)"/>
  67. public static ref U Access(ref T obj, string name) => ref GetAccessor(name)(ref obj);
  68. /// <summary>
  69. /// Gets the value of a field of an object by name.
  70. /// </summary>
  71. /// <remarks>
  72. /// The only good reason to use this over <see cref="Get(T, string)"/> is when you are working with a value type,
  73. /// as it prevents a copy.
  74. /// </remarks>
  75. /// <param name="obj">the object to access the field of</param>
  76. /// <param name="name">the name of the field to access</param>
  77. /// <returns>the value of the field</returns>
  78. /// <exception cref="MissingFieldException">if the field does not exist on <typeparamref name="T"/></exception>
  79. /// <seealso cref="Get(T, string)"/>
  80. /// <seealso cref="Access(ref T, string)"/>
  81. /// <seealso cref="GetAccessor(string)"/>
  82. public static U Get(ref T obj, string name) => Access(ref obj, name);
  83. /// <summary>
  84. /// Gets the value of a field of an object by name.
  85. /// </summary>
  86. /// <param name="obj">the object to access the field of</param>
  87. /// <param name="name">the name of the field to access</param>
  88. /// <returns>the value of the field</returns>
  89. /// <exception cref="MissingFieldException">if the field does not exist on <typeparamref name="T"/></exception>
  90. /// <seealso cref="Get(ref T, string)"/>
  91. /// <seealso cref="Access(ref T, string)"/>
  92. /// <seealso cref="GetAccessor(string)"/>
  93. public static U Get(T obj, string name) => Get(ref obj, name);
  94. /// <summary>
  95. /// Sets the value of a field for an object by name.
  96. /// </summary>
  97. /// <remarks>
  98. /// This overload must be used for value types.
  99. /// </remarks>
  100. /// <param name="obj">the object to set the field of</param>
  101. /// <param name="name">the name of the field</param>
  102. /// <param name="value">the value to set it to</param>
  103. /// <exception cref="MissingFieldException">if the field does not exist on <typeparamref name="T"/></exception>
  104. /// <seealso cref="Set(T, string, U)"/>
  105. /// <seealso cref="Access(ref T, string)"/>
  106. /// <seealso cref="GetAccessor(string)"/>
  107. public static void Set(ref T obj, string name, U value) => Access(ref obj, name) = value;
  108. /// <summary>
  109. /// Sets the value of a field for an object by name.
  110. /// </summary>
  111. /// <remarks>
  112. /// This overload cannot be safely used for value types. Use <see cref="Set(ref T, string, U)"/> instead.
  113. /// </remarks>
  114. /// <param name="obj">the object to set the field of</param>
  115. /// <param name="name">the name of the field</param>
  116. /// <param name="value">the value to set it to</param>
  117. /// <exception cref="MissingFieldException">if the field does not exist on <typeparamref name="T"/></exception>
  118. /// <seealso cref="Set(ref T, string, U)"/>
  119. /// <seealso cref="Access(ref T, string)"/>
  120. /// <seealso cref="GetAccessor(string)"/>
  121. public static void Set(T obj, string name, U value) => Set(ref obj, name, value);
  122. }
  123. /// <summary>
  124. /// A type containing utilities for accessing non-public properties of an object.
  125. /// </summary>
  126. /// <typeparam name="T">the type that the properties are on</typeparam>
  127. /// <typeparam name="U">the type of the property to access</typeparam>
  128. public static class PropertyAccessor<T, U>
  129. {
  130. /// <summary>
  131. /// A getter for a property.
  132. /// </summary>
  133. /// <param name="obj">the object it is a member of</param>
  134. /// <returns>the value of the property</returns>
  135. public delegate U Getter(ref T obj);
  136. /// <summary>
  137. /// A setter for a property.
  138. /// </summary>
  139. /// <param name="obj">the object it is a member of</param>
  140. /// <param name="val">the new property value</param>
  141. public delegate void Setter(ref T obj, U val);
  142. private static readonly Dictionary<string, (Getter get, Setter set)> props = new Dictionary<string, (Getter get, Setter set)>();
  143. private static (Getter, Setter) MakeAccessors(string propName)
  144. {
  145. var prop = typeof(T).GetProperty(propName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
  146. if (prop == null)
  147. throw new MissingMemberException(typeof(T).Name, propName);
  148. if (prop.PropertyType != typeof(U))
  149. throw new ArgumentException($"Property '{propName}' on {typeof(T)} is not of type {typeof(U)}");
  150. var getM = prop.GetGetMethod(true);
  151. var setM = prop.GetSetMethod(true);
  152. Getter getter = null;
  153. Setter setter = null;
  154. if (typeof(T).IsValueType)
  155. {
  156. if (getM != null)
  157. getter = (Getter)Delegate.CreateDelegate(typeof(Getter), getM);
  158. if (setM != null)
  159. setter = (Setter)Delegate.CreateDelegate(typeof(Setter), setM);
  160. }
  161. else
  162. {
  163. if (getM != null)
  164. {
  165. var dyn = new DynamicMethod($"<>_get__{propName}", typeof(U), new[] { typeof(T).MakeByRefType() }, typeof(PropertyAccessor<T, U>), true);
  166. var il = dyn.GetILGenerator();
  167. il.Emit(OpCodes.Ldarg_0);
  168. il.Emit(OpCodes.Ldind_Ref);
  169. il.Emit(OpCodes.Tailcall);
  170. il.Emit(getM.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, getM);
  171. il.Emit(OpCodes.Ret);
  172. getter = (Getter)dyn.CreateDelegate(typeof(Getter));
  173. }
  174. if (setM != null)
  175. {
  176. var dyn = new DynamicMethod($"<>_set__{propName}", typeof(void), new[] { typeof(T).MakeByRefType(), typeof(U) }, typeof(PropertyAccessor<T, U>), true);
  177. var il = dyn.GetILGenerator();
  178. il.Emit(OpCodes.Ldarg_0);
  179. il.Emit(OpCodes.Ldind_Ref);
  180. il.Emit(OpCodes.Ldarg_1);
  181. il.Emit(OpCodes.Tailcall);
  182. il.Emit(setM.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, setM);
  183. il.Emit(OpCodes.Ret);
  184. setter = (Setter)dyn.CreateDelegate(typeof(Setter));
  185. }
  186. }
  187. return (getter, setter);
  188. }
  189. private static (Getter get, Setter set) GetAccessors(string propName)
  190. {
  191. if (!props.TryGetValue(propName, out var access))
  192. props.Add(propName, access = MakeAccessors(propName));
  193. return access;
  194. }
  195. /// <summary>
  196. /// Gets a <see cref="Getter"/> for the property identified by <paramref name="name"/>.
  197. /// </summary>
  198. /// <param name="name">the name of the property</param>
  199. /// <returns>a <see cref="Getter"/> that can access that property</returns>
  200. /// <exception cref="MissingMemberException">if the property does not exist</exception>
  201. public static Getter GetGetter(string name) => GetAccessors(name).get;
  202. /// <summary>
  203. /// Gets a <see cref="Setter"/> for the property identified by <paramref name="name"/>.
  204. /// </summary>
  205. /// <param name="name">the name of the property</param>
  206. /// <returns>a <see cref="Setter"/> that can access that property</returns>
  207. /// <exception cref="MissingMemberException">if the property does not exist</exception>
  208. public static Setter GetSetter(string name) => GetAccessors(name).set;
  209. /// <summary>
  210. /// Gets the value of the property identified by <paramref name="name"/> on <paramref name="obj"/>.
  211. /// </summary>
  212. /// <remarks>
  213. /// The only reason to use this over <see cref="Get(T, string)"/> is if you are using a value type because
  214. /// it avoids a copy.
  215. /// </remarks>
  216. /// <param name="obj">the instance to access</param>
  217. /// <param name="name">the name of the property</param>
  218. /// <returns>the value of the property</returns>
  219. /// <exception cref="MissingMemberException">if the property does not exist</exception>
  220. /// <seealso cref="Get(T, string)"/>
  221. /// <seealso cref="GetGetter(string)"/>
  222. public static U Get(ref T obj, string name) => GetGetter(name)(ref obj);
  223. /// <summary>
  224. /// Gets the value of the property identified by <paramref name="name"/> on <paramref name="obj"/>.
  225. /// </summary>
  226. /// <param name="obj">the instance to access</param>
  227. /// <param name="name">the name of the property</param>
  228. /// <returns>the value of the property</returns>
  229. /// <exception cref="MissingMemberException">if the property does not exist</exception>
  230. /// <seealso cref="Get(ref T, string)"/>
  231. /// <seealso cref="GetGetter(string)"/>
  232. public static U Get(T obj, string name) => GetGetter(name)(ref obj);
  233. /// <summary>
  234. /// Sets the value of the property identified by <paramref name="name"/> on <paramref name="obj"/>.
  235. /// </summary>
  236. /// <remarks>
  237. /// This overload must be used for value types.
  238. /// </remarks>
  239. /// <param name="obj">the instance to access</param>
  240. /// <param name="name">the name of the property</param>
  241. /// <param name="val">the new value of the property</param>
  242. /// <exception cref="MissingMemberException">if the property does not exist</exception>
  243. /// <seealso cref="Set(T, string, U)"/>
  244. /// <seealso cref="GetSetter(string)"/>
  245. public static void Set(ref T obj, string name, U val) => GetSetter(name)(ref obj, val);
  246. /// <summary>
  247. /// Sets the value of the property identified by <paramref name="name"/> on <paramref name="obj"/>.
  248. /// </summary>
  249. /// <remarks>
  250. /// This overload cannot be safely used for value types. Use <see cref="Set(ref T, string, U)"/> instead.
  251. /// </remarks>
  252. /// <param name="obj">the instance to access</param>
  253. /// <param name="name">the name of the property</param>
  254. /// <param name="val">the new value of the property</param>
  255. /// <exception cref="MissingMemberException">if the property does not exist</exception>
  256. /// <seealso cref="Set(ref T, string, U)"/>
  257. /// <seealso cref="GetSetter(string)"/>
  258. public static void Set(T obj, string name, U val) => GetSetter(name)(ref obj, val);
  259. }
  260. internal class AccessorDelegateInfo<TDelegate> where TDelegate : Delegate
  261. {
  262. public static readonly Type Type = typeof(TDelegate);
  263. public static readonly MethodInfo Invoke = Type.GetMethod("Invoke");
  264. public static readonly ParameterInfo[] Parameters = Invoke.GetParameters();
  265. }
  266. /// <summary>
  267. /// A type containing utilities for calling non-public methods on an object.
  268. /// </summary>
  269. /// <typeparam name="T">the type to find the methods on</typeparam>
  270. /// <typeparam name="TDelegate">the delegate type to create, and to use as a signature to search for</typeparam>
  271. public static class MethodAccessor<T, TDelegate> where TDelegate : Delegate
  272. {
  273. private static readonly Dictionary<string, TDelegate> methods = new Dictionary<string, TDelegate>();
  274. static MethodAccessor()
  275. {
  276. // ensure that first argument of delegate type is valid
  277. var firstArg = AccessorDelegateInfo<TDelegate>.Parameters.First();
  278. var firstType = firstArg.ParameterType;
  279. if (typeof(T).IsValueType)
  280. {
  281. if (!firstType.IsByRef)
  282. throw new InvalidOperationException("First parameter of a method accessor to a value type is not byref");
  283. else
  284. firstType = firstType.GetElementType(); // get the non-byref type to check compatability
  285. }
  286. if (!typeof(T).IsAssignableFrom(firstType))
  287. throw new InvalidOperationException("First parameter of a method accessor is not assignable to the method owning type");
  288. }
  289. private static TDelegate MakeDelegate(string name)
  290. {
  291. var delParams = AccessorDelegateInfo<TDelegate>.Parameters;
  292. var method = typeof(T).GetMethod(name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly,
  293. null, delParams.Skip(1).Select(p => p.ParameterType).ToArray(), Array.Empty<ParameterModifier>());
  294. if (method == null)
  295. throw new MissingMethodException(typeof(T).FullName, name);
  296. var retType = AccessorDelegateInfo<TDelegate>.Invoke.ReturnType;
  297. if (!retType.IsAssignableFrom(method.ReturnType))
  298. throw new ArgumentException($"The method found returns a type incompatable with the return type of {typeof(TDelegate)}");
  299. return (TDelegate)Delegate.CreateDelegate(AccessorDelegateInfo<TDelegate>.Type, method, true);
  300. }
  301. /// <summary>
  302. /// Gets a delegate to the named method with the signature specified by <typeparamref name="TDelegate"/>.
  303. /// </summary>
  304. /// <param name="name">the name of the method to get</param>
  305. /// <returns>a delegate that can call the specified method</returns>
  306. /// <exception cref="MissingMethodException">if <paramref name="name"/> does not represent the name of a method with the given signature</exception>
  307. /// <exception cref="ArgumentException">if the method found returns a type incompatable with the return type of <typeparamref name="TDelegate"/></exception>
  308. public static TDelegate GetDelegate(string name)
  309. {
  310. if (!methods.TryGetValue(name, out var del))
  311. methods.Add(name, del = MakeDelegate(name));
  312. return del;
  313. }
  314. }
  315. }