diff options
author | Shockah <me@shockah.pl> | 2022-02-16 19:08:40 +0100 |
---|---|---|
committer | Shockah <me@shockah.pl> | 2022-02-16 19:08:40 +0100 |
commit | ba42bb97d1f1df07e87b02ee0e03eef0227ea5a7 (patch) | |
tree | 0a1364332a89756c48e0c7368fd63843a3c1b762 /src/SMAPI/Framework/Reflection | |
parent | a54d58d064df5654e845f9b2b4fc9db5b0189db0 (diff) | |
download | SMAPI-ba42bb97d1f1df07e87b02ee0e03eef0227ea5a7.tar.gz SMAPI-ba42bb97d1f1df07e87b02ee0e03eef0227ea5a7.tar.bz2 SMAPI-ba42bb97d1f1df07e87b02ee0e03eef0227ea5a7.zip |
move proxying to a separate NuGet library
Diffstat (limited to 'src/SMAPI/Framework/Reflection')
3 files changed, 0 insertions, 523 deletions
diff --git a/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs b/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs deleted file mode 100644 index 81cfff50..00000000 --- a/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs +++ /dev/null @@ -1,405 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Reflection.Emit; -using System.Runtime.CompilerServices; - -namespace StardewModdingAPI.Framework.Reflection -{ - /// <summary>Generates a proxy class to access a mod API through an arbitrary interface.</summary> - internal class InterfaceProxyBuilder - { - /********* - ** Consts - *********/ - private static readonly string TargetFieldName = "__Target"; - private static readonly string GlueFieldName = "__Glue"; - private static readonly MethodInfo ObtainInstanceForProxyTypeNameMethod = typeof(InterfaceProxyGlue).GetMethod(nameof(InterfaceProxyGlue.ObtainInstanceForProxyTypeName), new Type[] { typeof(string), typeof(object) }); - private static readonly MethodInfo UnproxyOrObtainInstanceForProxyTypeNameMethod = typeof(InterfaceProxyGlue).GetMethod(nameof(InterfaceProxyGlue.UnproxyOrObtainInstanceForProxyTypeName), new Type[] { typeof(string), typeof(string), typeof(object) }); - - /********* - ** Fields - *********/ - /// <summary>The target class type.</summary> - private readonly Type TargetType; - - /// <summary>The interfce type.</summary> - private readonly Type InterfaceType; - - /// <summary>The full name of the generated proxy type.</summary> - private readonly string ProxyTypeName; - - /// <summary>The generated proxy type.</summary> - private Type ProxyType; - - /// <summary>A cache of all proxies generated by this builder.</summary> - private readonly ConditionalWeakTable<object, object> ProxyCache = new(); - - /********* - ** Public methods - *********/ - /// <summary>Construct an instance.</summary> - /// <param name="targetType">The target type.</param> - /// <param name="interfaceType">The interface type to implement.</param> - /// <param name="proxyTypeName">The type name to generate.</param> - public InterfaceProxyBuilder(Type targetType, Type interfaceType, string proxyTypeName) - { - // validate and store - this.TargetType = targetType ?? throw new ArgumentNullException(nameof(targetType)); - this.InterfaceType = interfaceType ?? throw new ArgumentNullException(nameof(interfaceType)); - this.ProxyTypeName = proxyTypeName ?? throw new ArgumentNullException(nameof(proxyTypeName)); - if (!interfaceType.IsInterface) - throw new ArgumentException($"{nameof(interfaceType)} is not an interface."); - } - - - /// <summary>Creates and sets up the proxy type.</summary> - /// <param name="factory">The <see cref="InterfaceProxyFactory"/> that requested to build a proxy.</param> - /// <param name="moduleBuilder">The CLR module in which to create proxy classes.</param> - /// <param name="sourceModID">The unique ID of the mod consuming the API.</param> - /// <param name="targetModID">The unique ID of the mod providing the API.</param> - public void SetupProxyType(InterfaceProxyFactory factory, ModuleBuilder moduleBuilder, string sourceModID, string targetModID) - { - // define proxy type - TypeBuilder proxyBuilder = moduleBuilder.DefineType(this.ProxyTypeName, TypeAttributes.Public | TypeAttributes.Class); - proxyBuilder.AddInterfaceImplementation(this.InterfaceType); - - // create fields to store target instance and proxy factory - FieldBuilder targetField = proxyBuilder.DefineField(TargetFieldName, this.TargetType, FieldAttributes.Private); - FieldBuilder glueField = proxyBuilder.DefineField(GlueFieldName, typeof(InterfaceProxyGlue), FieldAttributes.Private); - - // create constructor which accepts target instance + factory, and sets fields - { - ConstructorBuilder constructor = proxyBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard | CallingConventions.HasThis, new[] { this.TargetType, typeof(InterfaceProxyGlue) }); - ILGenerator il = constructor.GetILGenerator(); - - il.Emit(OpCodes.Ldarg_0); // this - // ReSharper disable once AssignNullToNotNullAttribute -- never null - il.Emit(OpCodes.Call, typeof(object).GetConstructor(Array.Empty<Type>())); // call base constructor - il.Emit(OpCodes.Ldarg_0); // this - il.Emit(OpCodes.Ldarg_1); // load argument - il.Emit(OpCodes.Stfld, targetField); // set field to loaded argument - il.Emit(OpCodes.Ldarg_0); // this - il.Emit(OpCodes.Ldarg_2); // load argument - il.Emit(OpCodes.Stfld, glueField); // set field to loaded argument - il.Emit(OpCodes.Ret); - } - - var allTargetMethods = this.TargetType.GetMethods().ToList(); - foreach (Type targetInterface in this.TargetType.GetInterfaces()) - { - foreach (MethodInfo targetMethod in targetInterface.GetMethods()) - { - if (!targetMethod.IsAbstract) - allTargetMethods.Add(targetMethod); - } - } - - MatchingTypesResult AreTypesMatching(Type targetType, Type proxyType, MethodTypeMatchingPart part) - { - var typeA = part == MethodTypeMatchingPart.Parameter ? targetType : proxyType; - var typeB = part == MethodTypeMatchingPart.Parameter ? proxyType : targetType; - - if (typeA.IsGenericMethodParameter != typeB.IsGenericMethodParameter) - return MatchingTypesResult.False; - // TODO: decide if "assignable" checking is desired (instead of just 1:1 type equality) - if (typeA.IsGenericMethodParameter ? typeA.GenericParameterPosition == typeB.GenericParameterPosition : typeA.IsAssignableFrom(typeB)) - return MatchingTypesResult.True; - - if (!proxyType.IsGenericMethodParameter) - { - if (proxyType.GetNonRefType().IsInterface) - return MatchingTypesResult.IfProxied; - if (targetType.GetNonRefType().IsInterface) - return MatchingTypesResult.IfProxied; - } - - return MatchingTypesResult.False; - } - - // proxy methods - foreach (MethodInfo proxyMethod in this.InterfaceType.GetMethods()) - { - var proxyMethodParameters = proxyMethod.GetParameters(); - var proxyMethodGenericArguments = proxyMethod.GetGenericArguments(); - - foreach (MethodInfo targetMethod in allTargetMethods) - { - // checking if `targetMethod` matches `proxyMethod` - - if (targetMethod.Name != proxyMethod.Name) - continue; - if (targetMethod.GetGenericArguments().Length != proxyMethodGenericArguments.Length) - continue; - var positionsToProxy = new HashSet<int?>(); // null = return type; anything else = parameter position - - switch (AreTypesMatching(targetMethod.ReturnType, proxyMethod.ReturnType, MethodTypeMatchingPart.ReturnType)) - { - case MatchingTypesResult.False: - continue; - case MatchingTypesResult.True: - break; - case MatchingTypesResult.IfProxied: - positionsToProxy.Add(null); - break; - } - - var mParameters = targetMethod.GetParameters(); - if (mParameters.Length != proxyMethodParameters.Length) - continue; - for (int i = 0; i < mParameters.Length; i++) - { - switch (AreTypesMatching(mParameters[i].ParameterType, proxyMethodParameters[i].ParameterType, MethodTypeMatchingPart.Parameter)) - { - case MatchingTypesResult.False: - goto targetMethodLoopContinue; - case MatchingTypesResult.True: - break; - case MatchingTypesResult.IfProxied: - positionsToProxy.Add(i); - break; - } - } - - // method matched; proxying - - this.ProxyMethod(factory, proxyBuilder, proxyMethod, targetMethod, targetField, glueField, positionsToProxy, sourceModID, targetModID); - goto proxyMethodLoopContinue; - targetMethodLoopContinue:; - } - - throw new InvalidOperationException($"The {this.InterfaceType.FullName} interface defines method {proxyMethod.Name} which doesn't exist in the API."); - proxyMethodLoopContinue:; - } - - // save info - this.ProxyType = proxyBuilder.CreateType(); - } - - /// <summary>Get an existing or create a new instance of the proxy for a target instance.</summary> - /// <param name="targetInstance">The target instance.</param> - /// <param name="factory">The <see cref="InterfaceProxyFactory"/> that requested to build a proxy.</param> - public object ObtainInstance(object targetInstance, InterfaceProxyFactory factory) - { - if (this.ProxyCache.TryGetValue(targetInstance, out object proxyInstance)) - return proxyInstance; - - ConstructorInfo constructor = this.ProxyType.GetConstructor(new[] { this.TargetType, typeof(InterfaceProxyGlue) }); - if (constructor == null) - throw new InvalidOperationException($"Couldn't find the constructor for generated proxy type '{this.ProxyType.Name}'."); // should never happen - proxyInstance = constructor.Invoke(new[] { targetInstance, new InterfaceProxyGlue(factory) }); - this.ProxyCache.Add(targetInstance, proxyInstance); - return proxyInstance; - } - - - /********* - ** Private methods - *********/ - /// <summary>Define a method which proxies access to a method on the target.</summary> - /// <param name="factory">The <see cref="InterfaceProxyFactory"/> that requested to build a proxy.</param> - /// <param name="proxyBuilder">The proxy type being generated.</param> - /// <param name="proxy">The proxy method.</param> - /// <param name="target">The target method.</param> - /// <param name="instanceField">The proxy field containing the API instance.</param> - /// <param name="glueField">The proxy field containing an <see cref="InterfaceProxyGlue"/>.</param> - /// <param name="positionsToProxy">Parameter type positions (or null for the return type) for which types should also be proxied.</param> - /// <param name="sourceModID">The unique ID of the mod consuming the API.</param> - /// <param name="targetModID">The unique ID of the mod providing the API.</param> - private void ProxyMethod(InterfaceProxyFactory factory, TypeBuilder proxyBuilder, MethodInfo proxy, MethodInfo target, FieldBuilder instanceField, FieldBuilder glueField, ISet<int?> positionsToProxy, string sourceModID, string targetModID) - { - MethodBuilder methodBuilder = proxyBuilder.DefineMethod(proxy.Name, MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual); - - // set up generic arguments - Type[] proxyGenericArguments = proxy.GetGenericArguments(); - string[] genericArgNames = proxyGenericArguments.Select(a => a.Name).ToArray(); - GenericTypeParameterBuilder[] genericTypeParameterBuilders = proxyGenericArguments.Length == 0 ? null : methodBuilder.DefineGenericParameters(genericArgNames); - for (int i = 0; i < proxyGenericArguments.Length; i++) - genericTypeParameterBuilders[i].SetGenericParameterAttributes(proxyGenericArguments[i].GenericParameterAttributes); - - // set up return type - Type returnType = proxy.ReturnType.IsGenericMethodParameter ? genericTypeParameterBuilders[proxy.ReturnType.GenericParameterPosition] : proxy.ReturnType; - methodBuilder.SetReturnType(returnType); - - // set up parameters - var targetParameters = target.GetParameters(); - Type[] argTypes = proxy.GetParameters() - .Select(a => a.ParameterType) - .Select(t => t.IsGenericMethodParameter ? genericTypeParameterBuilders[t.GenericParameterPosition] : t) - .ToArray(); - - // proxy additional types - string returnValueProxyTypeName = null; - string[] parameterTargetToArgProxyTypeNames = new string[argTypes.Length]; - string[] parameterArgToTargetUnproxyTypeNames = new string[argTypes.Length]; - if (positionsToProxy.Count > 0) - { - foreach (int? position in positionsToProxy) - { - // we don't check for generics here, because earlier code does and generic positions won't end up here - if (position == null) // it's the return type - { - var builder = factory.ObtainBuilder(target.ReturnType, proxy.ReturnType, sourceModID, targetModID); - returnType = proxy.ReturnType; - returnValueProxyTypeName = builder.ProxyTypeName; - } - else // it's one of the parameters - { - bool isByRef = argTypes[position.Value].IsByRef; - var targetType = targetParameters[position.Value].ParameterType; - var argType = argTypes[position.Value]; - - var builder = factory.ObtainBuilder(targetType.GetNonRefType(), argType.GetNonRefType(), sourceModID, targetModID); - argTypes[position.Value] = argType; - parameterTargetToArgProxyTypeNames[position.Value] = builder.ProxyTypeName; - - if (!targetParameters[position.Value].IsOut) - { - var argToTargetBuilder = factory.ObtainBuilder(argType.GetNonRefType(), targetType.GetNonRefType(), sourceModID, targetModID); - parameterArgToTargetUnproxyTypeNames[position.Value] = argToTargetBuilder.ProxyTypeName; - } - } - } - - methodBuilder.SetReturnType(returnType); - } - - methodBuilder.SetParameters(argTypes); - for (int i = 0; i < argTypes.Length; i++) - methodBuilder.DefineParameter(i, targetParameters[i].Attributes, targetParameters[i].Name); - - // create method body - { - ILGenerator il = methodBuilder.GetILGenerator(); - LocalBuilder[] inputLocals = new LocalBuilder[argTypes.Length]; - LocalBuilder[] outputLocals = new LocalBuilder[argTypes.Length]; - - void ProxyIfNeededAndStore(LocalBuilder inputLocal, LocalBuilder outputLocal, string proxyTypeName, string unproxyTypeName) - { - if (proxyTypeName == null) - { - il.Emit(OpCodes.Ldloc, inputLocal); - il.Emit(OpCodes.Stloc, outputLocal); - return; - } - - var isNullLabel = il.DefineLabel(); - il.Emit(OpCodes.Ldloc, inputLocal); - il.Emit(OpCodes.Brfalse, isNullLabel); - - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldfld, glueField); - if (unproxyTypeName == null) - { - il.Emit(OpCodes.Ldstr, proxyTypeName); - il.Emit(OpCodes.Ldloc, inputLocal); - il.Emit(OpCodes.Call, ObtainInstanceForProxyTypeNameMethod); - } - else - { - il.Emit(OpCodes.Ldstr, proxyTypeName); - il.Emit(OpCodes.Ldstr, unproxyTypeName); - il.Emit(OpCodes.Ldloc, inputLocal); - il.Emit(OpCodes.Call, UnproxyOrObtainInstanceForProxyTypeNameMethod); - } - il.Emit(OpCodes.Castclass, outputLocal.LocalType); - il.Emit(OpCodes.Stloc, outputLocal); - - il.MarkLabel(isNullLabel); - } - - // calling the proxied method - LocalBuilder resultInputLocal = target.ReturnType == typeof(void) ? null : il.DeclareLocal(target.ReturnType); - LocalBuilder resultOutputLocal = returnType == typeof(void) ? null : il.DeclareLocal(returnType); - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldfld, instanceField); - for (int i = 0; i < argTypes.Length; i++) - { - if (targetParameters[i].IsOut && parameterTargetToArgProxyTypeNames[i] != null) // out parameter, proxy on the way back - { - inputLocals[i] = il.DeclareLocal(targetParameters[i].ParameterType.GetNonRefType()); - outputLocals[i] = il.DeclareLocal(argTypes[i].GetNonRefType()); - il.Emit(OpCodes.Ldloca, inputLocals[i]); - } - else if (parameterArgToTargetUnproxyTypeNames[i] != null) // normal parameter, proxy on the way in - { - inputLocals[i] = il.DeclareLocal(argTypes[i].GetNonRefType()); - outputLocals[i] = il.DeclareLocal(targetParameters[i].ParameterType.GetNonRefType()); - il.Emit(OpCodes.Ldarg, i + 1); - il.Emit(OpCodes.Stloc, inputLocals[i]); - ProxyIfNeededAndStore(inputLocals[i], outputLocals[i], parameterArgToTargetUnproxyTypeNames[i], parameterTargetToArgProxyTypeNames[i]); - il.Emit(OpCodes.Ldloc, outputLocals[i]); - } - else // normal parameter, no proxying - { - il.Emit(OpCodes.Ldarg, i + 1); - } - } - il.Emit(target.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, target); - if (target.ReturnType != typeof(void)) - il.Emit(OpCodes.Stloc, resultInputLocal); - - // proxying `out` parameters - for (int i = 0; i < argTypes.Length; i++) - { - if (parameterTargetToArgProxyTypeNames[i] == null) - continue; - if (!targetParameters[i].IsOut) - continue; - - ProxyIfNeededAndStore(inputLocals[i], outputLocals[i], parameterTargetToArgProxyTypeNames[i], null); - il.Emit(OpCodes.Ldarg, i + 1); - il.Emit(OpCodes.Ldloc, outputLocals[i]); - il.Emit(OpCodes.Stind_Ref); - } - - // proxying return value - if (target.ReturnType != typeof(void)) - ProxyIfNeededAndStore(resultInputLocal, resultOutputLocal, returnValueProxyTypeName, null); - - // return result - if (target.ReturnType != typeof(void)) - il.Emit(OpCodes.Ldloc, resultOutputLocal); - il.Emit(OpCodes.Ret); - } - } - - /// <summary>Try to get a target instance for a given proxy instance.</summary> - /// <param name="potentialProxyInstance">The proxy instance to look for.</param> - /// <param name="targetInstance">The reference to store the found target instance in.</param> - public bool TryUnproxy(object potentialProxyInstance, out object targetInstance) - { - foreach ((object cachedTargetInstance, object cachedProxyInstance) in this.ProxyCache) - { - if (object.ReferenceEquals(potentialProxyInstance, cachedProxyInstance)) - { - targetInstance = cachedTargetInstance; - return true; - } - } - targetInstance = null; - return false; - } - - /// <summary>The part of a method that is being matched.</summary> - private enum MethodTypeMatchingPart - { - ReturnType, Parameter - } - - /// <summary>The result of matching a target and a proxy type.</summary> - private enum MatchingTypesResult - { - False, IfProxied, True - } - } - - internal static class TypeExtensions - { - internal static Type GetNonRefType(this Type type) - { - return type.IsByRef ? type.GetElementType() : type; - } - } -} diff --git a/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs b/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs deleted file mode 100644 index a6f38c3a..00000000 --- a/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Reflection.Emit; - -namespace StardewModdingAPI.Framework.Reflection -{ - /// <summary>Generates proxy classes to access mod APIs through an arbitrary interface.</summary> - internal class InterfaceProxyFactory - { - /********* - ** Fields - *********/ - /// <summary>The CLR module in which to create proxy classes.</summary> - private readonly ModuleBuilder ModuleBuilder; - - /// <summary>The generated proxy types.</summary> - private readonly IDictionary<string, InterfaceProxyBuilder> Builders = new Dictionary<string, InterfaceProxyBuilder>(); - - - /********* - ** Public methods - *********/ - /// <summary>Construct an instance.</summary> - public InterfaceProxyFactory() - { - AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName($"StardewModdingAPI.Proxies, Version={this.GetType().Assembly.GetName().Version}, Culture=neutral"), AssemblyBuilderAccess.Run); - this.ModuleBuilder = assemblyBuilder.DefineDynamicModule("StardewModdingAPI.Proxies"); - } - - /// <summary>Create an API proxy.</summary> - /// <typeparam name="TInterface">The interface through which to access the API.</typeparam> - /// <param name="instance">The API instance to access.</param> - /// <param name="sourceModID">The unique ID of the mod consuming the API.</param> - /// <param name="targetModID">The unique ID of the mod providing the API.</param> - public TInterface CreateProxy<TInterface>(object instance, string sourceModID, string targetModID) - where TInterface : class - { - // validate - if (instance == null) - throw new InvalidOperationException("Can't proxy access to a null API."); - - // create instance - InterfaceProxyBuilder builder = this.ObtainBuilder(instance.GetType(), typeof(TInterface), sourceModID, targetModID); - return (TInterface)builder.ObtainInstance(instance, this); - } - - internal InterfaceProxyBuilder ObtainBuilder(Type targetType, Type interfaceType, string sourceModID, string targetModID) - { - lock (this.Builders) - { - // validate - if (!interfaceType.IsInterface) - throw new InvalidOperationException("The proxy type must be an interface, not a class."); - - // get proxy type - string proxyTypeName = $"StardewModdingAPI.Proxies.From<{sourceModID}_{interfaceType.FullName}>_To<{targetModID}_{targetType.FullName}>"; - if (!this.Builders.TryGetValue(proxyTypeName, out InterfaceProxyBuilder builder)) - { - builder = new InterfaceProxyBuilder(targetType, interfaceType, proxyTypeName); - this.Builders[proxyTypeName] = builder; - try - { - builder.SetupProxyType(this, this.ModuleBuilder, sourceModID, targetModID); - } - catch - { - this.Builders.Remove(proxyTypeName); - throw; - } - } - return builder; - } - } - - internal InterfaceProxyBuilder GetBuilderByProxyTypeName(string proxyTypeName) - { - lock (this.Builders) - { - return this.Builders.TryGetValue(proxyTypeName, out InterfaceProxyBuilder builder) ? builder : null; - } - } - } -} diff --git a/src/SMAPI/Framework/Reflection/InterfaceProxyGlue.cs b/src/SMAPI/Framework/Reflection/InterfaceProxyGlue.cs deleted file mode 100644 index 38569efa..00000000 --- a/src/SMAPI/Framework/Reflection/InterfaceProxyGlue.cs +++ /dev/null @@ -1,34 +0,0 @@ -namespace StardewModdingAPI.Framework.Reflection -{ - /// <summary>Provides an interface for proxied types to create other proxied types.</summary> - public sealed class InterfaceProxyGlue - { - private readonly InterfaceProxyFactory Factory; - - internal InterfaceProxyGlue(InterfaceProxyFactory factory) - { - this.Factory = factory; - } - - /// <summary>Get an existing or create a new proxied instance by its type name.</summary> - /// <param name="proxyTypeName">The full name of the proxy type.</param> - /// <param name="toProxy">The target instance to proxy.</param> - public object ObtainInstanceForProxyTypeName(string proxyTypeName, object toProxy) - { - var builder = this.Factory.GetBuilderByProxyTypeName(proxyTypeName); - return builder.ObtainInstance(toProxy, this.Factory); - } - - /// <summary>Try to unproxy, or get an existing, or create a new proxied instance by its type name.</summary> - /// <param name="proxyTypeName">The full name of the proxy type.</param> - /// <param name="unproxyTypeName">The full name of the reverse proxy type.</param> - /// <param name="toProxy">The target instance to proxy.</param> - public object UnproxyOrObtainInstanceForProxyTypeName(string proxyTypeName, string unproxyTypeName, object toProxy) - { - var unproxyBuilder = this.Factory.GetBuilderByProxyTypeName(unproxyTypeName); - if (unproxyBuilder.TryUnproxy(toProxy, out object targetInstance)) - return targetInstance; - return this.ObtainInstanceForProxyTypeName(proxyTypeName, toProxy); - } - } -} |