From 0ff82c38e7a5b630256d2cd23a63ac1088d13e39 Mon Sep 17 00:00:00 2001 From: Shockah Date: Tue, 8 Feb 2022 20:02:13 +0100 Subject: allow default interface method implementations in API proxies --- .../Framework/Reflection/InterfaceProxyBuilder.cs | 31 +++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs b/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs index 70ef81f8..164cac0b 100644 --- a/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs +++ b/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs @@ -55,10 +55,39 @@ namespace StardewModdingAPI.Framework.Reflection il.Emit(OpCodes.Ret); } + var allTargetMethods = targetType.GetMethods().ToList(); + foreach (Type targetInterface in targetType.GetInterfaces()) + { + foreach (MethodInfo targetMethod in targetInterface.GetMethods()) + { + if (!targetMethod.IsAbstract) + allTargetMethods.Add(targetMethod); + } + } + // proxy methods foreach (MethodInfo proxyMethod in interfaceType.GetMethods()) { - var targetMethod = targetType.GetMethod(proxyMethod.Name, proxyMethod.GetParameters().Select(a => a.ParameterType).ToArray()); + var proxyMethodParameters = proxyMethod.GetParameters(); + var targetMethod = allTargetMethods.Where(m => + { + if (m.Name != proxyMethod.Name) + return false; + if (m.ReturnType != proxyMethod.ReturnType) + return false; + + var mParameters = m.GetParameters(); + if (m.GetParameters().Length != proxyMethodParameters.Length) + return false; + for (int i = 0; i < mParameters.Length; i++) + { + // TODO: decide if "assignable" checking is desired (instead of just 1:1 type equality) + // TODO: test if this actually works + if (!mParameters[i].ParameterType.IsAssignableFrom(proxyMethodParameters[i].ParameterType)) + return false; + } + return true; + }).FirstOrDefault(); if (targetMethod == null) throw new InvalidOperationException($"The {interfaceType.FullName} interface defines method {proxyMethod.Name} which doesn't exist in the API."); -- cgit From 3135925982b01d8f316adc171c7c1235ea41c1d3 Mon Sep 17 00:00:00 2001 From: Shockah Date: Tue, 8 Feb 2022 21:54:53 +0100 Subject: allow generic methods and any assignable types in API proxies --- .../Framework/Reflection/InterfaceProxyBuilder.cs | 53 ++++++++++++++++++---- 1 file changed, 43 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs b/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs index 164cac0b..35faa852 100644 --- a/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs +++ b/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs @@ -65,15 +65,30 @@ namespace StardewModdingAPI.Framework.Reflection } } + bool 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 false; + // TODO: decide if "assignable" checking is desired (instead of just 1:1 type equality) + return typeA.IsGenericMethodParameter ? typeA.GenericParameterPosition == typeB.GenericParameterPosition : typeA.IsAssignableFrom(typeB); + } + // proxy methods foreach (MethodInfo proxyMethod in interfaceType.GetMethods()) { var proxyMethodParameters = proxyMethod.GetParameters(); + var proxyMethodGenericArguments = proxyMethod.GetGenericArguments(); var targetMethod = allTargetMethods.Where(m => { if (m.Name != proxyMethod.Name) return false; - if (m.ReturnType != proxyMethod.ReturnType) + + if (m.GetGenericArguments().Length != proxyMethodGenericArguments.Length) + return false; + if (!AreTypesMatching(m.ReturnType, proxyMethod.ReturnType, MethodTypeMatchingPart.ReturnType)) return false; var mParameters = m.GetParameters(); @@ -81,9 +96,7 @@ namespace StardewModdingAPI.Framework.Reflection return false; for (int i = 0; i < mParameters.Length; i++) { - // TODO: decide if "assignable" checking is desired (instead of just 1:1 type equality) - // TODO: test if this actually works - if (!mParameters[i].ParameterType.IsAssignableFrom(proxyMethodParameters[i].ParameterType)) + if (!AreTypesMatching(mParameters[i].ParameterType, proxyMethodParameters[i].ParameterType, MethodTypeMatchingPart.Parameter)) return false; } return true; @@ -91,7 +104,7 @@ namespace StardewModdingAPI.Framework.Reflection if (targetMethod == null) throw new InvalidOperationException($"The {interfaceType.FullName} interface defines method {proxyMethod.Name} which doesn't exist in the API."); - this.ProxyMethod(proxyBuilder, targetMethod, targetField); + this.ProxyMethod(proxyBuilder, proxyMethod, targetMethod, targetField); } // save info @@ -115,16 +128,30 @@ namespace StardewModdingAPI.Framework.Reflection *********/ /// Define a method which proxies access to a method on the target. /// The proxy type being generated. + /// The proxy method. /// The target method. /// The proxy field containing the API instance. - private void ProxyMethod(TypeBuilder proxyBuilder, MethodInfo target, FieldBuilder instanceField) + private void ProxyMethod(TypeBuilder proxyBuilder, MethodInfo proxy, MethodInfo target, FieldBuilder instanceField) { - Type[] argTypes = target.GetParameters().Select(a => a.ParameterType).ToArray(); - - // create method MethodBuilder methodBuilder = proxyBuilder.DefineMethod(target.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 + // TODO: keep if it's decided to use isAssignableFrom + methodBuilder.SetReturnType(proxy.ReturnType.IsGenericMethodParameter ? genericTypeParameterBuilders[proxy.ReturnType.GenericParameterPosition] : proxy.ReturnType); + + // set up parameters + Type[] argTypes = proxy.GetParameters() + .Select(a => a.ParameterType) + .Select(t => t.IsGenericMethodParameter ? genericTypeParameterBuilders[t.GenericParameterPosition] : t) + .ToArray(); methodBuilder.SetParameters(argTypes); - methodBuilder.SetReturnType(target.ReturnType); // create method body { @@ -143,5 +170,11 @@ namespace StardewModdingAPI.Framework.Reflection il.Emit(OpCodes.Ret); } } + + /// The part of a method that is being matched. + private enum MethodTypeMatchingPart + { + ReturnType, Parameter + } } } -- cgit From 5b5304403b79ce09bd06d1c4b52b67427069b772 Mon Sep 17 00:00:00 2001 From: Shockah Date: Tue, 8 Feb 2022 22:19:16 +0100 Subject: oops old code --- src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs | 1 - 1 file changed, 1 deletion(-) (limited to 'src') diff --git a/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs b/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs index 35faa852..4c154679 100644 --- a/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs +++ b/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs @@ -143,7 +143,6 @@ namespace StardewModdingAPI.Framework.Reflection genericTypeParameterBuilders[i].SetGenericParameterAttributes(proxyGenericArguments[i].GenericParameterAttributes); // set up return type - // TODO: keep if it's decided to use isAssignableFrom methodBuilder.SetReturnType(proxy.ReturnType.IsGenericMethodParameter ? genericTypeParameterBuilders[proxy.ReturnType.GenericParameterPosition] : proxy.ReturnType); // set up parameters -- cgit From 5a92b0cd357776eebb88e001384f9ca1ccdb7d5c Mon Sep 17 00:00:00 2001 From: Shockah Date: Tue, 8 Feb 2022 22:36:34 +0100 Subject: uses `proxy.Name` instead of `target.Name` (which makes more sense in this context) --- src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs b/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs index 4c154679..5ae96dff 100644 --- a/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs +++ b/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs @@ -133,7 +133,7 @@ namespace StardewModdingAPI.Framework.Reflection /// The proxy field containing the API instance. private void ProxyMethod(TypeBuilder proxyBuilder, MethodInfo proxy, MethodInfo target, FieldBuilder instanceField) { - MethodBuilder methodBuilder = proxyBuilder.DefineMethod(target.Name, MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual); + MethodBuilder methodBuilder = proxyBuilder.DefineMethod(proxy.Name, MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual); // set up generic arguments Type[] proxyGenericArguments = proxy.GetGenericArguments(); -- cgit From 43ad219b75740ef71ad9bad496b00c076182619b Mon Sep 17 00:00:00 2001 From: Shockah Date: Wed, 9 Feb 2022 20:07:01 +0100 Subject: support proxying return values in API proxies --- .../Framework/Reflection/InterfaceProxyBuilder.cs | 183 ++++++++++++++++----- .../Framework/Reflection/InterfaceProxyFactory.cs | 30 +++- .../Framework/Reflection/InterfaceProxyGlue.cs | 18 ++ 3 files changed, 186 insertions(+), 45 deletions(-) create mode 100644 src/SMAPI/Framework/Reflection/InterfaceProxyGlue.cs (limited to 'src') diff --git a/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs b/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs index 5ae96dff..d8b066bd 100644 --- a/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs +++ b/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs @@ -1,13 +1,22 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Reflection.Emit; +using HarmonyLib; namespace StardewModdingAPI.Framework.Reflection { /// Generates a proxy class to access a mod API through an arbitrary interface. internal class InterfaceProxyBuilder { + /********* + ** Consts + *********/ + private static readonly string TargetFieldName = "__Target"; + private static readonly string GlueFieldName = "__Glue"; + private static readonly MethodInfo CreateInstanceForProxyTypeNameMethod = typeof(InterfaceProxyGlue).GetMethod(nameof(InterfaceProxyGlue.CreateInstanceForProxyTypeName), new Type[] { typeof(string), typeof(object) }); + /********* ** Fields *********/ @@ -22,11 +31,14 @@ namespace StardewModdingAPI.Framework.Reflection ** Public methods *********/ /// Construct an instance. + /// The that requested to build a proxy. /// The type name to generate. /// The CLR module in which to create proxy classes. /// The interface type to implement. /// The target type. - public InterfaceProxyBuilder(string name, ModuleBuilder moduleBuilder, Type interfaceType, Type targetType) + /// The unique ID of the mod consuming the API. + /// The unique ID of the mod providing the API. + public InterfaceProxyBuilder(InterfaceProxyFactory factory, string name, ModuleBuilder moduleBuilder, Type interfaceType, Type targetType, string sourceModID, string targetModID) { // validate if (name == null) @@ -38,12 +50,13 @@ namespace StardewModdingAPI.Framework.Reflection TypeBuilder proxyBuilder = moduleBuilder.DefineType(name, TypeAttributes.Public | TypeAttributes.Class); proxyBuilder.AddInterfaceImplementation(interfaceType); - // create field to store target instance - FieldBuilder targetField = proxyBuilder.DefineField("__Target", targetType, FieldAttributes.Private); + // create fields to store target instance and proxy factory + FieldBuilder targetField = proxyBuilder.DefineField(TargetFieldName, targetType, FieldAttributes.Private); + FieldBuilder glueField = proxyBuilder.DefineField(GlueFieldName, typeof(InterfaceProxyGlue), FieldAttributes.Private); - // create constructor which accepts target instance and sets field + // create constructor which accepts target instance + factory, and sets fields { - ConstructorBuilder constructor = proxyBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard | CallingConventions.HasThis, new[] { targetType }); + ConstructorBuilder constructor = proxyBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard | CallingConventions.HasThis, new[] { targetType, typeof(InterfaceProxyGlue) }); ILGenerator il = constructor.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); // this @@ -52,6 +65,9 @@ namespace StardewModdingAPI.Framework.Reflection 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); } @@ -65,15 +81,20 @@ namespace StardewModdingAPI.Framework.Reflection } } - bool AreTypesMatching(Type targetType, Type proxyType, MethodTypeMatchingPart part) + 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 false; + return MatchingTypesResult.False; // TODO: decide if "assignable" checking is desired (instead of just 1:1 type equality) - return typeA.IsGenericMethodParameter ? typeA.GenericParameterPosition == typeB.GenericParameterPosition : typeA.IsAssignableFrom(typeB); + if (typeA.IsGenericMethodParameter ? typeA.GenericParameterPosition == typeB.GenericParameterPosition : typeA.IsAssignableFrom(typeB)) + return MatchingTypesResult.True; + + if (!proxyType.IsGenericMethodParameter && proxyType.IsInterface && proxyType.Assembly == interfaceType.Assembly) + return MatchingTypesResult.IfProxied; + return MatchingTypesResult.False; } // proxy methods @@ -81,30 +102,61 @@ namespace StardewModdingAPI.Framework.Reflection { var proxyMethodParameters = proxyMethod.GetParameters(); var proxyMethodGenericArguments = proxyMethod.GetGenericArguments(); - var targetMethod = allTargetMethods.Where(m => + + foreach (MethodInfo targetMethod in allTargetMethods) { - if (m.Name != proxyMethod.Name) - return false; + // checking if `targetMethod` matches `proxyMethod` - if (m.GetGenericArguments().Length != proxyMethodGenericArguments.Length) - return false; - if (!AreTypesMatching(m.ReturnType, proxyMethod.ReturnType, MethodTypeMatchingPart.ReturnType)) - return false; + if (targetMethod.Name != proxyMethod.Name) + continue; + if (targetMethod.GetGenericArguments().Length != proxyMethodGenericArguments.Length) + continue; + var positionsToProxy = new HashSet(); // 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 = m.GetParameters(); - if (m.GetParameters().Length != proxyMethodParameters.Length) - return false; + var mParameters = targetMethod.GetParameters(); + if (mParameters.Length != proxyMethodParameters.Length) + continue; for (int i = 0; i < mParameters.Length; i++) { - if (!AreTypesMatching(mParameters[i].ParameterType, proxyMethodParameters[i].ParameterType, MethodTypeMatchingPart.Parameter)) - return false; + switch (AreTypesMatching(mParameters[i].ParameterType, proxyMethodParameters[i].ParameterType, MethodTypeMatchingPart.Parameter)) + { + case MatchingTypesResult.False: + goto targetMethodLoopContinue; + case MatchingTypesResult.True: + break; + case MatchingTypesResult.IfProxied: + if (proxyMethodParameters[i].IsOut) + { + positionsToProxy.Add(i); + break; + } + else + { + goto targetMethodLoopContinue; + } + } } - return true; - }).FirstOrDefault(); - if (targetMethod == null) - throw new InvalidOperationException($"The {interfaceType.FullName} interface defines method {proxyMethod.Name} which doesn't exist in the API."); - this.ProxyMethod(proxyBuilder, proxyMethod, targetMethod, targetField); + // method matched; proxying + + this.ProxyMethod(factory, proxyBuilder, proxyMethod, targetMethod, targetField, glueField, positionsToProxy, sourceModID, targetModID); + goto proxyMethodLoopContinue; + targetMethodLoopContinue:; + } + + throw new InvalidOperationException($"The {interfaceType.FullName} interface defines method {proxyMethod.Name} which doesn't exist in the API."); + proxyMethodLoopContinue:; } // save info @@ -114,12 +166,13 @@ namespace StardewModdingAPI.Framework.Reflection /// Create an instance of the proxy for a target instance. /// The target instance. - public object CreateInstance(object targetInstance) + /// The that requested to build a proxy. + public object CreateInstance(object targetInstance, InterfaceProxyFactory factory) { - ConstructorInfo constructor = this.ProxyType.GetConstructor(new[] { this.TargetType }); + 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 - return constructor.Invoke(new[] { targetInstance }); + return constructor.Invoke(new[] { targetInstance, new InterfaceProxyGlue(factory) }); } @@ -127,11 +180,16 @@ namespace StardewModdingAPI.Framework.Reflection ** Private methods *********/ /// Define a method which proxies access to a method on the target. + /// The that requested to build a proxy. /// The proxy type being generated. /// The proxy method. /// The target method. /// The proxy field containing the API instance. - private void ProxyMethod(TypeBuilder proxyBuilder, MethodInfo proxy, MethodInfo target, FieldBuilder instanceField) + /// The proxy field containing an . + /// Parameter type positions (or null for the return type) for which types should also be proxied. + /// The unique ID of the mod consuming the API. + /// The unique ID of the mod providing the API. + private void ProxyMethod(InterfaceProxyFactory factory, TypeBuilder proxyBuilder, MethodInfo proxy, MethodInfo target, FieldBuilder instanceField, FieldBuilder glueField, ISet positionsToProxy, string sourceModID, string targetModID) { MethodBuilder methodBuilder = proxyBuilder.DefineMethod(proxy.Name, MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual); @@ -143,7 +201,8 @@ namespace StardewModdingAPI.Framework.Reflection genericTypeParameterBuilders[i].SetGenericParameterAttributes(proxyGenericArguments[i].GenericParameterAttributes); // set up return type - methodBuilder.SetReturnType(proxy.ReturnType.IsGenericMethodParameter ? genericTypeParameterBuilders[proxy.ReturnType.GenericParameterPosition] : proxy.ReturnType); + Type returnType = proxy.ReturnType.IsGenericMethodParameter ? genericTypeParameterBuilders[proxy.ReturnType.GenericParameterPosition] : proxy.ReturnType; + methodBuilder.SetReturnType(returnType); // set up parameters Type[] argTypes = proxy.GetParameters() @@ -152,18 +211,62 @@ namespace StardewModdingAPI.Framework.Reflection .ToArray(); methodBuilder.SetParameters(argTypes); + // proxy additional types + string returnValueProxyTypeName = null; + string[] parameterProxyTypeNames = new string[argTypes.Length]; + if (positionsToProxy.Count > 0) + { + var targetParameters = target.GetParameters(); + 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.ProxyType.FullName; + } + else // it's one of the parameters + { + var builder = factory.ObtainBuilder(targetParameters[position.Value].ParameterType, argTypes[position.Value], sourceModID, targetModID); + argTypes[position.Value] = proxy.ReturnType; + parameterProxyTypeNames[position.Value] = builder.ProxyType.FullName; + } + } + + methodBuilder.SetReturnType(returnType); + methodBuilder.SetParameters(argTypes); + } + // create method body { ILGenerator il = methodBuilder.GetILGenerator(); - // load target instance - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldfld, instanceField); + void EmitCallInstance() + { + // load target instance + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldfld, instanceField); + + // invoke target method on instance + for (int i = 0; i < argTypes.Length; i++) + il.Emit(OpCodes.Ldarg, i + 1); + il.Emit(OpCodes.Callvirt, target); + } - // invoke target method on instance - for (int i = 0; i < argTypes.Length; i++) - il.Emit(OpCodes.Ldarg, i + 1); - il.Emit(OpCodes.Call, target); + if (returnValueProxyTypeName == null) + { + EmitCallInstance(); + } + else + { + // this.Glue.CreateInstanceForProxyTypeName(proxyTypeName, this.Instance.Call(args)) + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldfld, glueField); + il.Emit(OpCodes.Ldstr, returnValueProxyTypeName); + EmitCallInstance(); + il.Emit(OpCodes.Call, CreateInstanceForProxyTypeNameMethod); + } // return result il.Emit(OpCodes.Ret); @@ -175,5 +278,11 @@ namespace StardewModdingAPI.Framework.Reflection { ReturnType, Parameter } + + /// The result of matching a target and a proxy type. + private enum MatchingTypesResult + { + False, IfProxied, True + } } } diff --git a/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs b/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs index 5acba569..8ce187bf 100644 --- a/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs +++ b/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs @@ -35,26 +35,40 @@ namespace StardewModdingAPI.Framework.Reflection /// The unique ID of the mod providing the API. public TInterface CreateProxy(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.CreateInstance(instance, this); + } + + internal InterfaceProxyBuilder ObtainBuilder(Type targetType, Type interfaceType, string sourceModID, string targetModID) { lock (this.Builders) { // validate - if (instance == null) - throw new InvalidOperationException("Can't proxy access to a null API."); - if (!typeof(TInterface).IsInterface) + if (!interfaceType.IsInterface) throw new InvalidOperationException("The proxy type must be an interface, not a class."); // get proxy type - Type targetType = instance.GetType(); - string proxyTypeName = $"StardewModdingAPI.Proxies.From<{sourceModID}_{typeof(TInterface).FullName}>_To<{targetModID}_{targetType.FullName}>"; + string proxyTypeName = $"StardewModdingAPI.Proxies.From<{sourceModID}_{interfaceType.FullName}>_To<{targetModID}_{targetType.FullName}>"; if (!this.Builders.TryGetValue(proxyTypeName, out InterfaceProxyBuilder builder)) { - builder = new InterfaceProxyBuilder(proxyTypeName, this.ModuleBuilder, typeof(TInterface), targetType); + builder = new InterfaceProxyBuilder(this, proxyTypeName, this.ModuleBuilder, interfaceType, targetType, sourceModID, targetModID); this.Builders[proxyTypeName] = builder; } + return builder; + } + } - // create instance - return (TInterface)builder.CreateInstance(instance); + 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 new file mode 100644 index 00000000..4e027252 --- /dev/null +++ b/src/SMAPI/Framework/Reflection/InterfaceProxyGlue.cs @@ -0,0 +1,18 @@ +namespace StardewModdingAPI.Framework.Reflection +{ + public sealed class InterfaceProxyGlue + { + private readonly InterfaceProxyFactory Factory; + + internal InterfaceProxyGlue(InterfaceProxyFactory factory) + { + this.Factory = factory; + } + + public object CreateInstanceForProxyTypeName(string proxyTypeName, object toProxy) + { + var builder = this.Factory.GetBuilderByProxyTypeName(proxyTypeName); + return builder.CreateInstance(toProxy, this.Factory); + } + } +} -- cgit From ee78ab3c3710639ec7eecb3d2edc7f26ff998407 Mon Sep 17 00:00:00 2001 From: Shockah Date: Wed, 9 Feb 2022 20:38:14 +0100 Subject: fix stack overflow for proxied types referencing each other --- .../Framework/Reflection/InterfaceProxyBuilder.cs | 41 ++++++++++++---------- .../Framework/Reflection/InterfaceProxyFactory.cs | 11 +++++- 2 files changed, 33 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs b/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs index d8b066bd..99aea75c 100644 --- a/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs +++ b/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs @@ -23,40 +23,46 @@ namespace StardewModdingAPI.Framework.Reflection /// The target class type. private readonly Type TargetType; + /// The full name of the generated proxy type. + private readonly string ProxyTypeName; + /// The generated proxy type. - private readonly Type ProxyType; + private Type ProxyType; /********* ** Public methods *********/ /// Construct an instance. + /// The target type. + /// The type name to generate. + public InterfaceProxyBuilder(Type targetType, string proxyTypeName) + { + // validate + this.TargetType = targetType ?? throw new ArgumentNullException(nameof(targetType)); + this.ProxyTypeName = proxyTypeName ?? throw new ArgumentNullException(nameof(proxyTypeName)); + } + + + /// Creates and sets up the proxy type. /// The that requested to build a proxy. - /// The type name to generate. /// The CLR module in which to create proxy classes. /// The interface type to implement. - /// The target type. /// The unique ID of the mod consuming the API. /// The unique ID of the mod providing the API. - public InterfaceProxyBuilder(InterfaceProxyFactory factory, string name, ModuleBuilder moduleBuilder, Type interfaceType, Type targetType, string sourceModID, string targetModID) + public void SetupProxyType(InterfaceProxyFactory factory, ModuleBuilder moduleBuilder, Type interfaceType, string sourceModID, string targetModID) { - // validate - if (name == null) - throw new ArgumentNullException(nameof(name)); - if (targetType == null) - throw new ArgumentNullException(nameof(targetType)); - // define proxy type - TypeBuilder proxyBuilder = moduleBuilder.DefineType(name, TypeAttributes.Public | TypeAttributes.Class); + TypeBuilder proxyBuilder = moduleBuilder.DefineType(this.ProxyTypeName, TypeAttributes.Public | TypeAttributes.Class); proxyBuilder.AddInterfaceImplementation(interfaceType); // create fields to store target instance and proxy factory - FieldBuilder targetField = proxyBuilder.DefineField(TargetFieldName, targetType, FieldAttributes.Private); + 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[] { targetType, typeof(InterfaceProxyGlue) }); + 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 @@ -71,8 +77,8 @@ namespace StardewModdingAPI.Framework.Reflection il.Emit(OpCodes.Ret); } - var allTargetMethods = targetType.GetMethods().ToList(); - foreach (Type targetInterface in targetType.GetInterfaces()) + var allTargetMethods = this.TargetType.GetMethods().ToList(); + foreach (Type targetInterface in this.TargetType.GetInterfaces()) { foreach (MethodInfo targetMethod in targetInterface.GetMethods()) { @@ -160,7 +166,6 @@ namespace StardewModdingAPI.Framework.Reflection } // save info - this.TargetType = targetType; this.ProxyType = proxyBuilder.CreateType(); } @@ -224,13 +229,13 @@ namespace StardewModdingAPI.Framework.Reflection { var builder = factory.ObtainBuilder(target.ReturnType, proxy.ReturnType, sourceModID, targetModID); returnType = proxy.ReturnType; - returnValueProxyTypeName = builder.ProxyType.FullName; + returnValueProxyTypeName = builder.ProxyTypeName; } else // it's one of the parameters { var builder = factory.ObtainBuilder(targetParameters[position.Value].ParameterType, argTypes[position.Value], sourceModID, targetModID); argTypes[position.Value] = proxy.ReturnType; - parameterProxyTypeNames[position.Value] = builder.ProxyType.FullName; + parameterProxyTypeNames[position.Value] = builder.ProxyTypeName; } } diff --git a/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs b/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs index 8ce187bf..72b4254c 100644 --- a/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs +++ b/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs @@ -57,8 +57,17 @@ namespace StardewModdingAPI.Framework.Reflection string proxyTypeName = $"StardewModdingAPI.Proxies.From<{sourceModID}_{interfaceType.FullName}>_To<{targetModID}_{targetType.FullName}>"; if (!this.Builders.TryGetValue(proxyTypeName, out InterfaceProxyBuilder builder)) { - builder = new InterfaceProxyBuilder(this, proxyTypeName, this.ModuleBuilder, interfaceType, targetType, sourceModID, targetModID); + builder = new InterfaceProxyBuilder(targetType, proxyTypeName); this.Builders[proxyTypeName] = builder; + try + { + builder.SetupProxyType(this, this.ModuleBuilder, interfaceType, sourceModID, targetModID); + } + catch + { + this.Builders.Remove(proxyTypeName); + throw; + } } return builder; } -- cgit From 688fccc0246be6756a07933696a235dca8f1a395 Mon Sep 17 00:00:00 2001 From: Shockah Date: Wed, 9 Feb 2022 20:40:54 +0100 Subject: add missing documentation --- src/SMAPI/Framework/Reflection/InterfaceProxyGlue.cs | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src') diff --git a/src/SMAPI/Framework/Reflection/InterfaceProxyGlue.cs b/src/SMAPI/Framework/Reflection/InterfaceProxyGlue.cs index 4e027252..8d0d74a7 100644 --- a/src/SMAPI/Framework/Reflection/InterfaceProxyGlue.cs +++ b/src/SMAPI/Framework/Reflection/InterfaceProxyGlue.cs @@ -1,5 +1,6 @@ namespace StardewModdingAPI.Framework.Reflection { + /// Provides an interface for proxied types to create other proxied types. public sealed class InterfaceProxyGlue { private readonly InterfaceProxyFactory Factory; @@ -9,6 +10,9 @@ namespace StardewModdingAPI.Framework.Reflection this.Factory = factory; } + /// Creates a new proxied instance by its type name. + /// The full name of the proxy type. + /// The target instance to proxy. public object CreateInstanceForProxyTypeName(string proxyTypeName, object toProxy) { var builder = this.Factory.GetBuilderByProxyTypeName(proxyTypeName); -- cgit From 354527bb810d06f69f2a8c45d1a23f294d228caf Mon Sep 17 00:00:00 2001 From: Shockah Date: Wed, 9 Feb 2022 21:02:41 +0100 Subject: stop proxying nulls --- src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs b/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs index 99aea75c..1b6bf1d6 100644 --- a/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs +++ b/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs @@ -265,12 +265,24 @@ namespace StardewModdingAPI.Framework.Reflection } else { - // this.Glue.CreateInstanceForProxyTypeName(proxyTypeName, this.Instance.Call(args)) + var resultLocal = il.DeclareLocal(typeof(object)); // we store both unmodified and modified in here + EmitCallInstance(); + il.Emit(OpCodes.Stloc, resultLocal); + + // if (unmodifiedResultLocal == null) jump + var isNullLabel = il.DefineLabel(); + il.Emit(OpCodes.Ldloc, resultLocal); + il.Emit(OpCodes.Brfalse, isNullLabel); + il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldfld, glueField); il.Emit(OpCodes.Ldstr, returnValueProxyTypeName); - EmitCallInstance(); + il.Emit(OpCodes.Ldloc, resultLocal); il.Emit(OpCodes.Call, CreateInstanceForProxyTypeNameMethod); + il.Emit(OpCodes.Stloc, resultLocal); + + il.MarkLabel(isNullLabel); + il.Emit(OpCodes.Ldloc, resultLocal); } // return result -- cgit From d9599a3a0af500438fa71addb1a25d4608aefda5 Mon Sep 17 00:00:00 2001 From: Shockah Date: Wed, 9 Feb 2022 21:10:20 +0100 Subject: simplifies proxy method IL a bit --- .../Framework/Reflection/InterfaceProxyBuilder.cs | 32 +++++++--------------- 1 file changed, 10 insertions(+), 22 deletions(-) (limited to 'src') diff --git a/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs b/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs index 1b6bf1d6..5e0dd838 100644 --- a/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs +++ b/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs @@ -247,28 +247,16 @@ namespace StardewModdingAPI.Framework.Reflection { ILGenerator il = methodBuilder.GetILGenerator(); - void EmitCallInstance() + var resultLocal = il.DeclareLocal(typeof(object)); // we store both unmodified and modified in here, hence `object` + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldfld, instanceField); + for (int i = 0; i < argTypes.Length; i++) + il.Emit(OpCodes.Ldarg, i + 1); + il.Emit(OpCodes.Callvirt, target); + il.Emit(OpCodes.Stloc, resultLocal); + + if (returnValueProxyTypeName != null) { - // load target instance - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldfld, instanceField); - - // invoke target method on instance - for (int i = 0; i < argTypes.Length; i++) - il.Emit(OpCodes.Ldarg, i + 1); - il.Emit(OpCodes.Callvirt, target); - } - - if (returnValueProxyTypeName == null) - { - EmitCallInstance(); - } - else - { - var resultLocal = il.DeclareLocal(typeof(object)); // we store both unmodified and modified in here - EmitCallInstance(); - il.Emit(OpCodes.Stloc, resultLocal); - // if (unmodifiedResultLocal == null) jump var isNullLabel = il.DefineLabel(); il.Emit(OpCodes.Ldloc, resultLocal); @@ -282,10 +270,10 @@ namespace StardewModdingAPI.Framework.Reflection il.Emit(OpCodes.Stloc, resultLocal); il.MarkLabel(isNullLabel); - il.Emit(OpCodes.Ldloc, resultLocal); } // return result + il.Emit(OpCodes.Ldloc, resultLocal); il.Emit(OpCodes.Ret); } } -- cgit From f920ed59d6234df9d33b0d72ea0398a3fa8e9b8b Mon Sep 17 00:00:00 2001 From: Shockah Date: Wed, 9 Feb 2022 23:26:26 +0100 Subject: add WIP proxying of methods with `out` parameters --- .../Framework/Reflection/InterfaceProxyBuilder.cs | 66 ++++++++++++++++++---- 1 file changed, 56 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs b/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs index 5e0dd838..b1b3c451 100644 --- a/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs +++ b/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs @@ -98,7 +98,7 @@ namespace StardewModdingAPI.Framework.Reflection if (typeA.IsGenericMethodParameter ? typeA.GenericParameterPosition == typeB.GenericParameterPosition : typeA.IsAssignableFrom(typeB)) return MatchingTypesResult.True; - if (!proxyType.IsGenericMethodParameter && proxyType.IsInterface && proxyType.Assembly == interfaceType.Assembly) + if (!proxyType.IsGenericMethodParameter && proxyType.GetNonRefType().IsInterface && proxyType.Assembly == interfaceType.Assembly) return MatchingTypesResult.IfProxied; return MatchingTypesResult.False; } @@ -233,8 +233,15 @@ namespace StardewModdingAPI.Framework.Reflection } else // it's one of the parameters { - var builder = factory.ObtainBuilder(targetParameters[position.Value].ParameterType, argTypes[position.Value], sourceModID, targetModID); - argTypes[position.Value] = proxy.ReturnType; + bool isByRef = argTypes[position.Value].IsByRef; + var targetType = targetParameters[position.Value].ParameterType.GetNonRefType(); + var argType = argTypes[position.Value].GetNonRefType(); + + var builder = factory.ObtainBuilder(targetType, argType, sourceModID, targetModID); + if (isByRef) + argType = argType.MakeByRefType(); + + argTypes[position.Value] = argType; parameterProxyTypeNames[position.Value] = builder.ProxyTypeName; } } @@ -246,32 +253,63 @@ namespace StardewModdingAPI.Framework.Reflection // create method body { ILGenerator il = methodBuilder.GetILGenerator(); + LocalBuilder[] outLocals = new LocalBuilder[argTypes.Length]; + // calling the proxied method var resultLocal = il.DeclareLocal(typeof(object)); // we store both unmodified and modified in here, hence `object` il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldfld, instanceField); for (int i = 0; i < argTypes.Length; i++) - il.Emit(OpCodes.Ldarg, i + 1); + { + if (parameterProxyTypeNames[i] == null) + { + il.Emit(OpCodes.Ldarg, i + 1); + } + else + { + // previous code already checks if the parameters are specifically `out` + outLocals[i] = il.DeclareLocal(typeof(object)); // we store both unmodified and modified in here, hence `object` + il.Emit(OpCodes.Ldloca_S, outLocals[i]); + } + } il.Emit(OpCodes.Callvirt, target); il.Emit(OpCodes.Stloc, resultLocal); - if (returnValueProxyTypeName != null) + void ProxyNonNullIfNeeded(LocalBuilder local, string proxyTypeName) { - // if (unmodifiedResultLocal == null) jump + if (proxyTypeName == null) + return; + var isNullLabel = il.DefineLabel(); - il.Emit(OpCodes.Ldloc, resultLocal); + il.Emit(OpCodes.Ldloc, local); il.Emit(OpCodes.Brfalse, isNullLabel); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldfld, glueField); - il.Emit(OpCodes.Ldstr, returnValueProxyTypeName); - il.Emit(OpCodes.Ldloc, resultLocal); + il.Emit(OpCodes.Ldstr, proxyTypeName); + il.Emit(OpCodes.Ldloc, local); il.Emit(OpCodes.Call, CreateInstanceForProxyTypeNameMethod); - il.Emit(OpCodes.Stloc, resultLocal); + il.Emit(OpCodes.Stloc, local); il.MarkLabel(isNullLabel); } + // proxying `out` parameters + for (int i = 0; i < argTypes.Length; i++) + { + if (parameterProxyTypeNames[i] == null) + continue; + // previous code already checks if the parameters are specifically `out` + + ProxyNonNullIfNeeded(outLocals[i], parameterProxyTypeNames[i]); + il.Emit(OpCodes.Ldarg, i + 1); + il.Emit(OpCodes.Ldloc, outLocals[i]); + il.Emit(OpCodes.Stind_Ref); + } + + // proxying return value + ProxyNonNullIfNeeded(resultLocal, returnValueProxyTypeName); + // return result il.Emit(OpCodes.Ldloc, resultLocal); il.Emit(OpCodes.Ret); @@ -290,4 +328,12 @@ namespace StardewModdingAPI.Framework.Reflection False, IfProxied, True } } + + internal static class TypeExtensions + { + internal static Type GetNonRefType(this Type type) + { + return type.IsByRef ? type.GetElementType() : type; + } + } } -- cgit From 55723f91d270fa2623c25c0ca1decfa7e566b636 Mon Sep 17 00:00:00 2001 From: Shockah Date: Thu, 10 Feb 2022 10:08:14 +0100 Subject: implement `out` parameter proxying --- .../Framework/Reflection/InterfaceProxyBuilder.cs | 46 ++++++++++++++-------- 1 file changed, 29 insertions(+), 17 deletions(-) (limited to 'src') diff --git a/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs b/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs index b1b3c451..3c6d7c61 100644 --- a/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs +++ b/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Reflection.Emit; -using HarmonyLib; namespace StardewModdingAPI.Framework.Reflection { @@ -210,18 +209,17 @@ namespace StardewModdingAPI.Framework.Reflection 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(); - methodBuilder.SetParameters(argTypes); // proxy additional types string returnValueProxyTypeName = null; string[] parameterProxyTypeNames = new string[argTypes.Length]; if (positionsToProxy.Count > 0) { - var targetParameters = target.GetParameters(); foreach (int? position in positionsToProxy) { // we don't check for generics here, because earlier code does and generic positions won't end up here @@ -247,16 +245,21 @@ namespace StardewModdingAPI.Framework.Reflection } methodBuilder.SetReturnType(returnType); - methodBuilder.SetParameters(argTypes); } + 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[] outLocals = new LocalBuilder[argTypes.Length]; + LocalBuilder[] outInputLocals = new LocalBuilder[argTypes.Length]; + LocalBuilder[] outOutputLocals = new LocalBuilder[argTypes.Length]; // calling the proxied method - var resultLocal = il.DeclareLocal(typeof(object)); // we store both unmodified and modified in here, hence `object` + 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++) @@ -268,28 +271,35 @@ namespace StardewModdingAPI.Framework.Reflection else { // previous code already checks if the parameters are specifically `out` - outLocals[i] = il.DeclareLocal(typeof(object)); // we store both unmodified and modified in here, hence `object` - il.Emit(OpCodes.Ldloca_S, outLocals[i]); + outInputLocals[i] = il.DeclareLocal(targetParameters[i].ParameterType.GetNonRefType()); + outOutputLocals[i] = il.DeclareLocal(argTypes[i].GetNonRefType()); + il.Emit(OpCodes.Ldloca, outInputLocals[i]); } } il.Emit(OpCodes.Callvirt, target); - il.Emit(OpCodes.Stloc, resultLocal); + if (target.ReturnType != typeof(void)) + il.Emit(OpCodes.Stloc, resultInputLocal); - void ProxyNonNullIfNeeded(LocalBuilder local, string proxyTypeName) + void ProxyIfNeededAndStore(LocalBuilder inputLocal, LocalBuilder outputLocal, string proxyTypeName) { if (proxyTypeName == null) + { + il.Emit(OpCodes.Ldloc, inputLocal); + il.Emit(OpCodes.Stloc, outputLocal); return; + } var isNullLabel = il.DefineLabel(); - il.Emit(OpCodes.Ldloc, local); + il.Emit(OpCodes.Ldloc, inputLocal); il.Emit(OpCodes.Brfalse, isNullLabel); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldfld, glueField); il.Emit(OpCodes.Ldstr, proxyTypeName); - il.Emit(OpCodes.Ldloc, local); + il.Emit(OpCodes.Ldloc, inputLocal); il.Emit(OpCodes.Call, CreateInstanceForProxyTypeNameMethod); - il.Emit(OpCodes.Stloc, local); + il.Emit(OpCodes.Castclass, outputLocal.LocalType); + il.Emit(OpCodes.Stloc, outputLocal); il.MarkLabel(isNullLabel); } @@ -301,17 +311,19 @@ namespace StardewModdingAPI.Framework.Reflection continue; // previous code already checks if the parameters are specifically `out` - ProxyNonNullIfNeeded(outLocals[i], parameterProxyTypeNames[i]); + ProxyIfNeededAndStore(outInputLocals[i], outOutputLocals[i], parameterProxyTypeNames[i]); il.Emit(OpCodes.Ldarg, i + 1); - il.Emit(OpCodes.Ldloc, outLocals[i]); + il.Emit(OpCodes.Ldloc, outOutputLocals[i]); il.Emit(OpCodes.Stind_Ref); } // proxying return value - ProxyNonNullIfNeeded(resultLocal, returnValueProxyTypeName); + if (target.ReturnType != typeof(void)) + ProxyIfNeededAndStore(resultInputLocal, resultOutputLocal, returnValueProxyTypeName); // return result - il.Emit(OpCodes.Ldloc, resultLocal); + if (target.ReturnType != typeof(void)) + il.Emit(OpCodes.Ldloc, resultOutputLocal); il.Emit(OpCodes.Ret); } } -- cgit From 955790842518d68ca5df21efe1beccc6069a35fe Mon Sep 17 00:00:00 2001 From: Shockah Date: Thu, 10 Feb 2022 10:18:31 +0100 Subject: fix code style warning --- src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs b/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs index 3c6d7c61..9573c791 100644 --- a/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs +++ b/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs @@ -66,7 +66,7 @@ namespace StardewModdingAPI.Framework.Reflection il.Emit(OpCodes.Ldarg_0); // this // ReSharper disable once AssignNullToNotNullAttribute -- never null - il.Emit(OpCodes.Call, typeof(object).GetConstructor(new Type[0])); // call base constructor + il.Emit(OpCodes.Call, typeof(object).GetConstructor(Array.Empty())); // 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 -- cgit From 61415e41eb8f5f61e8b241255162257191c0a766 Mon Sep 17 00:00:00 2001 From: Shockah Date: Thu, 10 Feb 2022 11:21:41 +0100 Subject: use Call/Callvirt depending on target --- src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs b/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs index 9573c791..49cc6bca 100644 --- a/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs +++ b/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs @@ -276,7 +276,7 @@ namespace StardewModdingAPI.Framework.Reflection il.Emit(OpCodes.Ldloca, outInputLocals[i]); } } - il.Emit(OpCodes.Callvirt, target); + il.Emit(target.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, target); if (target.ReturnType != typeof(void)) il.Emit(OpCodes.Stloc, resultInputLocal); -- cgit From 07259452170a253c44d5c2be68fc2342a88d2504 Mon Sep 17 00:00:00 2001 From: Shockah Date: Thu, 10 Feb 2022 11:43:35 +0100 Subject: add proxy instance caching --- .../Framework/Reflection/InterfaceProxyBuilder.cs | 18 +++++++++++++----- .../Framework/Reflection/InterfaceProxyFactory.cs | 2 +- src/SMAPI/Framework/Reflection/InterfaceProxyGlue.cs | 6 +++--- 3 files changed, 17 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs b/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs index 49cc6bca..63a594fa 100644 --- a/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs +++ b/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Reflection.Emit; +using System.Runtime.CompilerServices; namespace StardewModdingAPI.Framework.Reflection { @@ -14,7 +15,7 @@ namespace StardewModdingAPI.Framework.Reflection *********/ private static readonly string TargetFieldName = "__Target"; private static readonly string GlueFieldName = "__Glue"; - private static readonly MethodInfo CreateInstanceForProxyTypeNameMethod = typeof(InterfaceProxyGlue).GetMethod(nameof(InterfaceProxyGlue.CreateInstanceForProxyTypeName), new Type[] { typeof(string), typeof(object) }); + private static readonly MethodInfo ObtainInstanceForProxyTypeNameMethod = typeof(InterfaceProxyGlue).GetMethod(nameof(InterfaceProxyGlue.ObtainInstanceForProxyTypeName), new Type[] { typeof(string), typeof(object) }); /********* ** Fields @@ -28,6 +29,8 @@ namespace StardewModdingAPI.Framework.Reflection /// The generated proxy type. private Type ProxyType; + /// A cache of all proxies generated by this builder. + private readonly ConditionalWeakTable ProxyCache = new(); /********* ** Public methods @@ -168,15 +171,20 @@ namespace StardewModdingAPI.Framework.Reflection this.ProxyType = proxyBuilder.CreateType(); } - /// Create an instance of the proxy for a target instance. + /// Get an existing or create a new instance of the proxy for a target instance. /// The target instance. /// The that requested to build a proxy. - public object CreateInstance(object targetInstance, InterfaceProxyFactory factory) + 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 - return constructor.Invoke(new[] { targetInstance, new InterfaceProxyGlue(factory) }); + proxyInstance = constructor.Invoke(new[] { targetInstance, new InterfaceProxyGlue(factory) }); + this.ProxyCache.Add(targetInstance, proxyInstance); + return proxyInstance; } @@ -297,7 +305,7 @@ namespace StardewModdingAPI.Framework.Reflection il.Emit(OpCodes.Ldfld, glueField); il.Emit(OpCodes.Ldstr, proxyTypeName); il.Emit(OpCodes.Ldloc, inputLocal); - il.Emit(OpCodes.Call, CreateInstanceForProxyTypeNameMethod); + il.Emit(OpCodes.Call, ObtainInstanceForProxyTypeNameMethod); il.Emit(OpCodes.Castclass, outputLocal.LocalType); il.Emit(OpCodes.Stloc, outputLocal); diff --git a/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs b/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs index 72b4254c..daeac2ad 100644 --- a/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs +++ b/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs @@ -42,7 +42,7 @@ namespace StardewModdingAPI.Framework.Reflection // create instance InterfaceProxyBuilder builder = this.ObtainBuilder(instance.GetType(), typeof(TInterface), sourceModID, targetModID); - return (TInterface)builder.CreateInstance(instance, this); + return (TInterface)builder.ObtainInstance(instance, this); } internal InterfaceProxyBuilder ObtainBuilder(Type targetType, Type interfaceType, string sourceModID, string targetModID) diff --git a/src/SMAPI/Framework/Reflection/InterfaceProxyGlue.cs b/src/SMAPI/Framework/Reflection/InterfaceProxyGlue.cs index 8d0d74a7..f98b54a2 100644 --- a/src/SMAPI/Framework/Reflection/InterfaceProxyGlue.cs +++ b/src/SMAPI/Framework/Reflection/InterfaceProxyGlue.cs @@ -10,13 +10,13 @@ namespace StardewModdingAPI.Framework.Reflection this.Factory = factory; } - /// Creates a new proxied instance by its type name. + /// Get an existing or create a new proxied instance by its type name. /// The full name of the proxy type. /// The target instance to proxy. - public object CreateInstanceForProxyTypeName(string proxyTypeName, object toProxy) + public object ObtainInstanceForProxyTypeName(string proxyTypeName, object toProxy) { var builder = this.Factory.GetBuilderByProxyTypeName(proxyTypeName); - return builder.CreateInstance(toProxy, this.Factory); + return builder.ObtainInstance(toProxy, this.Factory); } } } -- cgit From 467375a7a37f5ed3012d1786d3149a0dddd28871 Mon Sep 17 00:00:00 2001 From: Shockah Date: Thu, 10 Feb 2022 14:15:06 +0100 Subject: add reverse API proxying (and unproxying) --- .../Framework/Reflection/InterfaceProxyBuilder.cs | 166 +++++++++++++-------- .../Framework/Reflection/InterfaceProxyFactory.cs | 4 +- .../Framework/Reflection/InterfaceProxyGlue.cs | 12 ++ 3 files changed, 120 insertions(+), 62 deletions(-) (limited to 'src') diff --git a/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs b/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs index 63a594fa..81cfff50 100644 --- a/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs +++ b/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs @@ -16,6 +16,7 @@ namespace StardewModdingAPI.Framework.Reflection 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 @@ -23,6 +24,9 @@ namespace StardewModdingAPI.Framework.Reflection /// The target class type. private readonly Type TargetType; + /// The interfce type. + private readonly Type InterfaceType; + /// The full name of the generated proxy type. private readonly string ProxyTypeName; @@ -37,26 +41,29 @@ namespace StardewModdingAPI.Framework.Reflection *********/ /// Construct an instance. /// The target type. + /// The interface type to implement. /// The type name to generate. - public InterfaceProxyBuilder(Type targetType, string proxyTypeName) + public InterfaceProxyBuilder(Type targetType, Type interfaceType, string proxyTypeName) { - // validate + // 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."); } /// Creates and sets up the proxy type. /// The that requested to build a proxy. /// The CLR module in which to create proxy classes. - /// The interface type to implement. /// The unique ID of the mod consuming the API. /// The unique ID of the mod providing the API. - public void SetupProxyType(InterfaceProxyFactory factory, ModuleBuilder moduleBuilder, Type interfaceType, string sourceModID, string targetModID) + 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(interfaceType); + proxyBuilder.AddInterfaceImplementation(this.InterfaceType); // create fields to store target instance and proxy factory FieldBuilder targetField = proxyBuilder.DefineField(TargetFieldName, this.TargetType, FieldAttributes.Private); @@ -100,13 +107,19 @@ namespace StardewModdingAPI.Framework.Reflection if (typeA.IsGenericMethodParameter ? typeA.GenericParameterPosition == typeB.GenericParameterPosition : typeA.IsAssignableFrom(typeB)) return MatchingTypesResult.True; - if (!proxyType.IsGenericMethodParameter && proxyType.GetNonRefType().IsInterface && proxyType.Assembly == interfaceType.Assembly) - return MatchingTypesResult.IfProxied; + 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 interfaceType.GetMethods()) + foreach (MethodInfo proxyMethod in this.InterfaceType.GetMethods()) { var proxyMethodParameters = proxyMethod.GetParameters(); var proxyMethodGenericArguments = proxyMethod.GetGenericArguments(); @@ -144,15 +157,8 @@ namespace StardewModdingAPI.Framework.Reflection case MatchingTypesResult.True: break; case MatchingTypesResult.IfProxied: - if (proxyMethodParameters[i].IsOut) - { - positionsToProxy.Add(i); - break; - } - else - { - goto targetMethodLoopContinue; - } + positionsToProxy.Add(i); + break; } } @@ -163,7 +169,7 @@ namespace StardewModdingAPI.Framework.Reflection targetMethodLoopContinue:; } - throw new InvalidOperationException($"The {interfaceType.FullName} interface defines method {proxyMethod.Name} which doesn't exist in the API."); + throw new InvalidOperationException($"The {this.InterfaceType.FullName} interface defines method {proxyMethod.Name} which doesn't exist in the API."); proxyMethodLoopContinue:; } @@ -225,7 +231,8 @@ namespace StardewModdingAPI.Framework.Reflection // proxy additional types string returnValueProxyTypeName = null; - string[] parameterProxyTypeNames = new string[argTypes.Length]; + string[] parameterTargetToArgProxyTypeNames = new string[argTypes.Length]; + string[] parameterArgToTargetUnproxyTypeNames = new string[argTypes.Length]; if (positionsToProxy.Count > 0) { foreach (int? position in positionsToProxy) @@ -240,15 +247,18 @@ namespace StardewModdingAPI.Framework.Reflection else // it's one of the parameters { bool isByRef = argTypes[position.Value].IsByRef; - var targetType = targetParameters[position.Value].ParameterType.GetNonRefType(); - var argType = argTypes[position.Value].GetNonRefType(); - - var builder = factory.ObtainBuilder(targetType, argType, sourceModID, targetModID); - if (isByRef) - argType = argType.MakeByRefType(); + 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; - parameterProxyTypeNames[position.Value] = builder.ProxyTypeName; + 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; + } } } @@ -262,33 +272,10 @@ namespace StardewModdingAPI.Framework.Reflection // create method body { ILGenerator il = methodBuilder.GetILGenerator(); - LocalBuilder[] outInputLocals = new LocalBuilder[argTypes.Length]; - LocalBuilder[] outOutputLocals = new LocalBuilder[argTypes.Length]; + LocalBuilder[] inputLocals = new LocalBuilder[argTypes.Length]; + LocalBuilder[] outputLocals = new LocalBuilder[argTypes.Length]; - // 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 (parameterProxyTypeNames[i] == null) - { - il.Emit(OpCodes.Ldarg, i + 1); - } - else - { - // previous code already checks if the parameters are specifically `out` - outInputLocals[i] = il.DeclareLocal(targetParameters[i].ParameterType.GetNonRefType()); - outOutputLocals[i] = il.DeclareLocal(argTypes[i].GetNonRefType()); - il.Emit(OpCodes.Ldloca, outInputLocals[i]); - } - } - il.Emit(target.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, target); - if (target.ReturnType != typeof(void)) - il.Emit(OpCodes.Stloc, resultInputLocal); - - void ProxyIfNeededAndStore(LocalBuilder inputLocal, LocalBuilder outputLocal, string proxyTypeName) + void ProxyIfNeededAndStore(LocalBuilder inputLocal, LocalBuilder outputLocal, string proxyTypeName, string unproxyTypeName) { if (proxyTypeName == null) { @@ -303,31 +290,73 @@ namespace StardewModdingAPI.Framework.Reflection il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldfld, glueField); - il.Emit(OpCodes.Ldstr, proxyTypeName); - il.Emit(OpCodes.Ldloc, inputLocal); - il.Emit(OpCodes.Call, ObtainInstanceForProxyTypeNameMethod); + 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 (parameterProxyTypeNames[i] == null) + if (parameterTargetToArgProxyTypeNames[i] == null) + continue; + if (!targetParameters[i].IsOut) continue; - // previous code already checks if the parameters are specifically `out` - ProxyIfNeededAndStore(outInputLocals[i], outOutputLocals[i], parameterProxyTypeNames[i]); + ProxyIfNeededAndStore(inputLocals[i], outputLocals[i], parameterTargetToArgProxyTypeNames[i], null); il.Emit(OpCodes.Ldarg, i + 1); - il.Emit(OpCodes.Ldloc, outOutputLocals[i]); + il.Emit(OpCodes.Ldloc, outputLocals[i]); il.Emit(OpCodes.Stind_Ref); } // proxying return value if (target.ReturnType != typeof(void)) - ProxyIfNeededAndStore(resultInputLocal, resultOutputLocal, returnValueProxyTypeName); + ProxyIfNeededAndStore(resultInputLocal, resultOutputLocal, returnValueProxyTypeName, null); // return result if (target.ReturnType != typeof(void)) @@ -336,6 +365,23 @@ namespace StardewModdingAPI.Framework.Reflection } } + /// Try to get a target instance for a given proxy instance. + /// The proxy instance to look for. + /// The reference to store the found target instance in. + 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; + } + /// The part of a method that is being matched. private enum MethodTypeMatchingPart { diff --git a/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs b/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs index daeac2ad..a6f38c3a 100644 --- a/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs +++ b/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs @@ -57,11 +57,11 @@ namespace StardewModdingAPI.Framework.Reflection string proxyTypeName = $"StardewModdingAPI.Proxies.From<{sourceModID}_{interfaceType.FullName}>_To<{targetModID}_{targetType.FullName}>"; if (!this.Builders.TryGetValue(proxyTypeName, out InterfaceProxyBuilder builder)) { - builder = new InterfaceProxyBuilder(targetType, proxyTypeName); + builder = new InterfaceProxyBuilder(targetType, interfaceType, proxyTypeName); this.Builders[proxyTypeName] = builder; try { - builder.SetupProxyType(this, this.ModuleBuilder, interfaceType, sourceModID, targetModID); + builder.SetupProxyType(this, this.ModuleBuilder, sourceModID, targetModID); } catch { diff --git a/src/SMAPI/Framework/Reflection/InterfaceProxyGlue.cs b/src/SMAPI/Framework/Reflection/InterfaceProxyGlue.cs index f98b54a2..38569efa 100644 --- a/src/SMAPI/Framework/Reflection/InterfaceProxyGlue.cs +++ b/src/SMAPI/Framework/Reflection/InterfaceProxyGlue.cs @@ -18,5 +18,17 @@ namespace StardewModdingAPI.Framework.Reflection var builder = this.Factory.GetBuilderByProxyTypeName(proxyTypeName); return builder.ObtainInstance(toProxy, this.Factory); } + + /// Try to unproxy, or get an existing, or create a new proxied instance by its type name. + /// The full name of the proxy type. + /// The full name of the reverse proxy type. + /// The target instance to proxy. + 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); + } } } -- cgit From a54d58d064df5654e845f9b2b4fc9db5b0189db0 Mon Sep 17 00:00:00 2001 From: Shockah Date: Thu, 10 Feb 2022 16:26:43 +0100 Subject: add TryProxy for any objects --- .../Framework/ModHelpers/ModRegistryHelper.cs | 26 ++++++++++++++++++++++ src/SMAPI/IModRegistry.cs | 7 ++++++ 2 files changed, 33 insertions(+) (limited to 'src') diff --git a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs index ef1ad30c..92c52b00 100644 --- a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs @@ -98,5 +98,31 @@ namespace StardewModdingAPI.Framework.ModHelpers return castApi; return this.ProxyFactory.CreateProxy(api, this.ModID, uniqueID); } + + /// + public bool TryProxy(string uniqueID, object toProxy, out TInterface proxy) where TInterface : class + { + try + { + foreach (var toProxyInterface in toProxy.GetType().GetInterfaces()) + { + var unproxyBuilder = this.ProxyFactory.ObtainBuilder(typeof(TInterface), toProxyInterface, this.ModID, uniqueID); + if (unproxyBuilder.TryUnproxy(toProxy, out object targetInstance)) + { + proxy = (TInterface)targetInstance; + return true; + } + } + + var proxyBuilder = this.ProxyFactory.ObtainBuilder(toProxy.GetType(), typeof(TInterface), this.ModID, uniqueID); + proxy = (TInterface)proxyBuilder.ObtainInstance(toProxy, this.ProxyFactory); + return true; + } + catch + { + proxy = null; + return false; + } + } } } diff --git a/src/SMAPI/IModRegistry.cs b/src/SMAPI/IModRegistry.cs index 10b3121e..9b99e459 100644 --- a/src/SMAPI/IModRegistry.cs +++ b/src/SMAPI/IModRegistry.cs @@ -25,5 +25,12 @@ namespace StardewModdingAPI /// The interface which matches the properties and methods you intend to access. /// The mod's unique ID. TInterface GetApi(string uniqueID) where TInterface : class; + + /// Try to proxy (or unproxy back) the given object to a given interface provided by a mod. + /// The interface type to proxy (or unproxy) to. + /// The mod's unique ID. + /// The object to try to proxy (or unproxy back). + /// The reference to store the proxied (or unproxied) object back. + bool TryProxy(string uniqueID, object toProxy, out TInterface proxy) where TInterface : class; } } -- cgit From ba42bb97d1f1df07e87b02ee0e03eef0227ea5a7 Mon Sep 17 00:00:00 2001 From: Shockah Date: Wed, 16 Feb 2022 19:08:40 +0100 Subject: move proxying to a separate NuGet library --- build/common.targets | 1 + src/SMAPI.Tests/SMAPI.Tests.csproj | 1 + .../Framework/ModHelpers/ModRegistryHelper.cs | 37 +- .../Framework/Reflection/InterfaceProxyBuilder.cs | 405 --------------------- .../Framework/Reflection/InterfaceProxyFactory.cs | 84 ----- .../Framework/Reflection/InterfaceProxyGlue.cs | 34 -- src/SMAPI/Framework/SCore.cs | 16 +- src/SMAPI/IModRegistry.cs | 7 - src/SMAPI/SMAPI.csproj | 1 + 9 files changed, 20 insertions(+), 566 deletions(-) delete mode 100644 src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs delete mode 100644 src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs delete mode 100644 src/SMAPI/Framework/Reflection/InterfaceProxyGlue.cs (limited to 'src') diff --git a/build/common.targets b/build/common.targets index 86624b62..bcb0e9e1 100644 --- a/build/common.targets +++ b/build/common.targets @@ -53,6 +53,7 @@ + diff --git a/src/SMAPI.Tests/SMAPI.Tests.csproj b/src/SMAPI.Tests/SMAPI.Tests.csproj index 8329b2e1..e27dbecc 100644 --- a/src/SMAPI.Tests/SMAPI.Tests.csproj +++ b/src/SMAPI.Tests/SMAPI.Tests.csproj @@ -20,6 +20,7 @@ + diff --git a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs index 92c52b00..93ea6028 100644 --- a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Nanoray.Pintail; using StardewModdingAPI.Framework.Reflection; namespace StardewModdingAPI.Framework.ModHelpers @@ -19,7 +20,7 @@ namespace StardewModdingAPI.Framework.ModHelpers private readonly HashSet AccessedModApis = new HashSet(); /// Generates proxy classes to access mod APIs through an arbitrary interface. - private readonly InterfaceProxyFactory ProxyFactory; + private readonly IProxyManager ProxyManager; /********* @@ -28,13 +29,13 @@ namespace StardewModdingAPI.Framework.ModHelpers /// Construct an instance. /// The unique ID of the relevant mod. /// The underlying mod registry. - /// Generates proxy classes to access mod APIs through an arbitrary interface. + /// Generates proxy classes to access mod APIs through an arbitrary interface. /// Encapsulates monitoring and logging for the mod. - public ModRegistryHelper(string modID, ModRegistry registry, InterfaceProxyFactory proxyFactory, IMonitor monitor) + public ModRegistryHelper(string modID, ModRegistry registry, IProxyManager proxyManager, IMonitor monitor) : base(modID) { this.Registry = registry; - this.ProxyFactory = proxyFactory; + this.ProxyManager = proxyManager; this.Monitor = monitor; } @@ -96,33 +97,7 @@ namespace StardewModdingAPI.Framework.ModHelpers // get API of type if (api is TInterface castApi) return castApi; - return this.ProxyFactory.CreateProxy(api, this.ModID, uniqueID); - } - - /// - public bool TryProxy(string uniqueID, object toProxy, out TInterface proxy) where TInterface : class - { - try - { - foreach (var toProxyInterface in toProxy.GetType().GetInterfaces()) - { - var unproxyBuilder = this.ProxyFactory.ObtainBuilder(typeof(TInterface), toProxyInterface, this.ModID, uniqueID); - if (unproxyBuilder.TryUnproxy(toProxy, out object targetInstance)) - { - proxy = (TInterface)targetInstance; - return true; - } - } - - var proxyBuilder = this.ProxyFactory.ObtainBuilder(toProxy.GetType(), typeof(TInterface), this.ModID, uniqueID); - proxy = (TInterface)proxyBuilder.ObtainInstance(toProxy, this.ProxyFactory); - return true; - } - catch - { - proxy = null; - return false; - } + return this.ProxyManager.ObtainProxy(api, this.ModID, uniqueID); } } } 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 -{ - /// Generates a proxy class to access a mod API through an arbitrary interface. - 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 - *********/ - /// The target class type. - private readonly Type TargetType; - - /// The interfce type. - private readonly Type InterfaceType; - - /// The full name of the generated proxy type. - private readonly string ProxyTypeName; - - /// The generated proxy type. - private Type ProxyType; - - /// A cache of all proxies generated by this builder. - private readonly ConditionalWeakTable ProxyCache = new(); - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The target type. - /// The interface type to implement. - /// The type name to generate. - 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."); - } - - - /// Creates and sets up the proxy type. - /// The that requested to build a proxy. - /// The CLR module in which to create proxy classes. - /// The unique ID of the mod consuming the API. - /// The unique ID of the mod providing the API. - 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())); // 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(); // 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(); - } - - /// Get an existing or create a new instance of the proxy for a target instance. - /// The target instance. - /// The that requested to build a proxy. - 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 - *********/ - /// Define a method which proxies access to a method on the target. - /// The that requested to build a proxy. - /// The proxy type being generated. - /// The proxy method. - /// The target method. - /// The proxy field containing the API instance. - /// The proxy field containing an . - /// Parameter type positions (or null for the return type) for which types should also be proxied. - /// The unique ID of the mod consuming the API. - /// The unique ID of the mod providing the API. - private void ProxyMethod(InterfaceProxyFactory factory, TypeBuilder proxyBuilder, MethodInfo proxy, MethodInfo target, FieldBuilder instanceField, FieldBuilder glueField, ISet 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); - } - } - - /// Try to get a target instance for a given proxy instance. - /// The proxy instance to look for. - /// The reference to store the found target instance in. - 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; - } - - /// The part of a method that is being matched. - private enum MethodTypeMatchingPart - { - ReturnType, Parameter - } - - /// The result of matching a target and a proxy type. - 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 -{ - /// Generates proxy classes to access mod APIs through an arbitrary interface. - internal class InterfaceProxyFactory - { - /********* - ** Fields - *********/ - /// The CLR module in which to create proxy classes. - private readonly ModuleBuilder ModuleBuilder; - - /// The generated proxy types. - private readonly IDictionary Builders = new Dictionary(); - - - /********* - ** Public methods - *********/ - /// Construct an instance. - 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"); - } - - /// Create an API proxy. - /// The interface through which to access the API. - /// The API instance to access. - /// The unique ID of the mod consuming the API. - /// The unique ID of the mod providing the API. - public TInterface CreateProxy(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 -{ - /// Provides an interface for proxied types to create other proxied types. - public sealed class InterfaceProxyGlue - { - private readonly InterfaceProxyFactory Factory; - - internal InterfaceProxyGlue(InterfaceProxyFactory factory) - { - this.Factory = factory; - } - - /// Get an existing or create a new proxied instance by its type name. - /// The full name of the proxy type. - /// The target instance to proxy. - public object ObtainInstanceForProxyTypeName(string proxyTypeName, object toProxy) - { - var builder = this.Factory.GetBuilderByProxyTypeName(proxyTypeName); - return builder.ObtainInstance(toProxy, this.Factory); - } - - /// Try to unproxy, or get an existing, or create a new proxied instance by its type name. - /// The full name of the proxy type. - /// The full name of the reverse proxy type. - /// The target instance to proxy. - 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); - } - } -} diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 55a7f083..67c13ad0 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -47,6 +47,8 @@ using xTile.Display; using MiniMonoModHotfix = MonoMod.Utils.MiniMonoModHotfix; using PathUtilities = StardewModdingAPI.Toolkit.Utilities.PathUtilities; using SObject = StardewValley.Object; +using Nanoray.Pintail; +using System.Reflection.Emit; namespace StardewModdingAPI.Framework { @@ -1477,12 +1479,16 @@ namespace StardewModdingAPI.Framework { // init HashSet suppressUpdateChecks = new HashSet(this.Settings.SuppressUpdateChecks, StringComparer.OrdinalIgnoreCase); - InterfaceProxyFactory proxyFactory = new InterfaceProxyFactory(); + AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName($"StardewModdingAPI.Proxies, Version={this.GetType().Assembly.GetName().Version}, Culture=neutral"), AssemblyBuilderAccess.Run); + ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("StardewModdingAPI.Proxies"); + IProxyManager proxyManager = new DefaultProxyManager(moduleBuilder, new DefaultProxyManagerConfiguration( + proxyObjectInterfaceMarking: ProxyObjectInterfaceMarking.Disabled + )); // load mods foreach (IModMetadata mod in mods) { - if (!this.TryLoadMod(mod, mods, modAssemblyLoader, proxyFactory, jsonHelper, contentCore, modDatabase, suppressUpdateChecks, out ModFailReason? failReason, out string errorPhrase, out string errorDetails)) + if (!this.TryLoadMod(mod, mods, modAssemblyLoader, proxyManager, jsonHelper, contentCore, modDatabase, suppressUpdateChecks, out ModFailReason? failReason, out string errorPhrase, out string errorDetails)) { failReason ??= ModFailReason.LoadFailed; mod.SetStatus(ModMetadataStatus.Failed, failReason.Value, errorPhrase, errorDetails); @@ -1585,7 +1591,7 @@ namespace StardewModdingAPI.Framework /// The mod to load. /// The mods being loaded. /// Preprocesses and loads mod assemblies. - /// Generates proxy classes to access mod APIs through an arbitrary interface. + /// Generates proxy classes to access mod APIs through an arbitrary interface. /// The JSON helper with which to read mods' JSON files. /// The content manager to use for mod content. /// Handles access to SMAPI's internal mod metadata list. @@ -1594,7 +1600,7 @@ namespace StardewModdingAPI.Framework /// The user-facing reason phrase explaining why the mod couldn't be loaded (if applicable). /// More detailed details about the error intended for developers (if any). /// Returns whether the mod was successfully loaded. - private bool TryLoadMod(IModMetadata mod, IModMetadata[] mods, AssemblyLoader assemblyLoader, InterfaceProxyFactory proxyFactory, JsonHelper jsonHelper, ContentCoordinator contentCore, ModDatabase modDatabase, HashSet suppressUpdateChecks, out ModFailReason? failReason, out string errorReasonPhrase, out string errorDetails) + private bool TryLoadMod(IModMetadata mod, IModMetadata[] mods, AssemblyLoader assemblyLoader, IProxyManager proxyManager, JsonHelper jsonHelper, ContentCoordinator contentCore, ModDatabase modDatabase, HashSet suppressUpdateChecks, out ModFailReason? failReason, out string errorReasonPhrase, out string errorDetails) { errorDetails = null; @@ -1737,7 +1743,7 @@ namespace StardewModdingAPI.Framework IContentPackHelper contentPackHelper = new ContentPackHelper(manifest.UniqueID, new Lazy(GetContentPacks), CreateFakeContentPack); IDataHelper dataHelper = new DataHelper(manifest.UniqueID, mod.DirectoryPath, jsonHelper); IReflectionHelper reflectionHelper = new ReflectionHelper(manifest.UniqueID, mod.DisplayName, this.Reflection); - IModRegistry modRegistryHelper = new ModRegistryHelper(manifest.UniqueID, this.ModRegistry, proxyFactory, monitor); + IModRegistry modRegistryHelper = new ModRegistryHelper(manifest.UniqueID, this.ModRegistry, proxyManager, monitor); IMultiplayerHelper multiplayerHelper = new MultiplayerHelper(manifest.UniqueID, this.Multiplayer); modHelper = new ModHelper(manifest.UniqueID, mod.DirectoryPath, () => this.GetCurrentGameInstance().Input, events, contentHelper, contentPackHelper, commandHelper, dataHelper, modRegistryHelper, reflectionHelper, multiplayerHelper, translationHelper); diff --git a/src/SMAPI/IModRegistry.cs b/src/SMAPI/IModRegistry.cs index 9b99e459..10b3121e 100644 --- a/src/SMAPI/IModRegistry.cs +++ b/src/SMAPI/IModRegistry.cs @@ -25,12 +25,5 @@ namespace StardewModdingAPI /// The interface which matches the properties and methods you intend to access. /// The mod's unique ID. TInterface GetApi(string uniqueID) where TInterface : class; - - /// Try to proxy (or unproxy back) the given object to a given interface provided by a mod. - /// The interface type to proxy (or unproxy) to. - /// The mod's unique ID. - /// The object to try to proxy (or unproxy back). - /// The reference to store the proxied (or unproxied) object back. - bool TryProxy(string uniqueID, object toProxy, out TInterface proxy) where TInterface : class; } } diff --git a/src/SMAPI/SMAPI.csproj b/src/SMAPI/SMAPI.csproj index f07ede87..1ea5ea78 100644 --- a/src/SMAPI/SMAPI.csproj +++ b/src/SMAPI/SMAPI.csproj @@ -28,6 +28,7 @@ + -- cgit From 8ecca56816f03c76e3cf669ca1a552932b01253e Mon Sep 17 00:00:00 2001 From: Shockah Date: Wed, 16 Feb 2022 21:10:23 +0100 Subject: update Pintail --- src/SMAPI.Tests/SMAPI.Tests.csproj | 1 - src/SMAPI/SMAPI.csproj | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) (limited to 'src') diff --git a/src/SMAPI.Tests/SMAPI.Tests.csproj b/src/SMAPI.Tests/SMAPI.Tests.csproj index e27dbecc..8329b2e1 100644 --- a/src/SMAPI.Tests/SMAPI.Tests.csproj +++ b/src/SMAPI.Tests/SMAPI.Tests.csproj @@ -20,7 +20,6 @@ - diff --git a/src/SMAPI/SMAPI.csproj b/src/SMAPI/SMAPI.csproj index 1ea5ea78..90cbf9b3 100644 --- a/src/SMAPI/SMAPI.csproj +++ b/src/SMAPI/SMAPI.csproj @@ -25,10 +25,10 @@ + - -- cgit From e41dbd49cacc69e87ed11bbea0d67a3b33af5d25 Mon Sep 17 00:00:00 2001 From: Shockah Date: Thu, 17 Feb 2022 19:59:36 +0100 Subject: update Pintail --- src/SMAPI/SMAPI.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/SMAPI/SMAPI.csproj b/src/SMAPI/SMAPI.csproj index 90cbf9b3..b5e538be 100644 --- a/src/SMAPI/SMAPI.csproj +++ b/src/SMAPI/SMAPI.csproj @@ -25,7 +25,7 @@ - + -- cgit From 5706d91dd9a919ad33e889f14287e77c33ff528e Mon Sep 17 00:00:00 2001 From: Shockah Date: Fri, 18 Feb 2022 12:16:02 +0100 Subject: update Pintail --- src/SMAPI/SMAPI.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/SMAPI/SMAPI.csproj b/src/SMAPI/SMAPI.csproj index b5e538be..e4a4e6b6 100644 --- a/src/SMAPI/SMAPI.csproj +++ b/src/SMAPI/SMAPI.csproj @@ -25,7 +25,7 @@ - + -- cgit From e24c0db7b1291137296363aa745ce5de6316ae59 Mon Sep 17 00:00:00 2001 From: Shockah Date: Sat, 19 Feb 2022 21:27:13 +0100 Subject: update Pintail --- src/SMAPI/SMAPI.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/SMAPI/SMAPI.csproj b/src/SMAPI/SMAPI.csproj index e4a4e6b6..2204ad70 100644 --- a/src/SMAPI/SMAPI.csproj +++ b/src/SMAPI/SMAPI.csproj @@ -25,7 +25,7 @@ - + -- cgit From e9321ba6f90d30bb7132478df40786414b0f1304 Mon Sep 17 00:00:00 2001 From: Shockah Date: Sat, 19 Feb 2022 22:08:05 +0100 Subject: change proxy prepare behavior --- src/SMAPI/Framework/SCore.cs | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 67c13ad0..81e56247 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1482,6 +1482,7 @@ namespace StardewModdingAPI.Framework AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName($"StardewModdingAPI.Proxies, Version={this.GetType().Assembly.GetName().Version}, Culture=neutral"), AssemblyBuilderAccess.Run); ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("StardewModdingAPI.Proxies"); IProxyManager proxyManager = new DefaultProxyManager(moduleBuilder, new DefaultProxyManagerConfiguration( + proxyPrepareBehavior: DefaultProxyManagerProxyPrepareBehavior.Eager, proxyObjectInterfaceMarking: ProxyObjectInterfaceMarking.Disabled )); -- cgit From 28f78d1a2942a4bf2d528d3c8f15c2188e0c9cb6 Mon Sep 17 00:00:00 2001 From: Shockah Date: Sun, 20 Feb 2022 00:38:42 +0100 Subject: update Pintail --- src/SMAPI/SMAPI.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/SMAPI/SMAPI.csproj b/src/SMAPI/SMAPI.csproj index 2204ad70..3fc76299 100644 --- a/src/SMAPI/SMAPI.csproj +++ b/src/SMAPI/SMAPI.csproj @@ -25,7 +25,7 @@ - + -- cgit From 5e3237d000aba8a4d3c7e40d5791a476fb5d6a65 Mon Sep 17 00:00:00 2001 From: Shockah Date: Thu, 24 Feb 2022 12:58:16 +0100 Subject: update Pintail --- src/SMAPI/Framework/SCore.cs | 4 ++-- src/SMAPI/SMAPI.csproj | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 81e56247..58537031 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1481,8 +1481,8 @@ namespace StardewModdingAPI.Framework HashSet suppressUpdateChecks = new HashSet(this.Settings.SuppressUpdateChecks, StringComparer.OrdinalIgnoreCase); AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName($"StardewModdingAPI.Proxies, Version={this.GetType().Assembly.GetName().Version}, Culture=neutral"), AssemblyBuilderAccess.Run); ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("StardewModdingAPI.Proxies"); - IProxyManager proxyManager = new DefaultProxyManager(moduleBuilder, new DefaultProxyManagerConfiguration( - proxyPrepareBehavior: DefaultProxyManagerProxyPrepareBehavior.Eager, + IProxyManager proxyManager = new ProxyManager(moduleBuilder, new ProxyManagerConfiguration( + proxyPrepareBehavior: ProxyManagerProxyPrepareBehavior.Eager, proxyObjectInterfaceMarking: ProxyObjectInterfaceMarking.Disabled )); diff --git a/src/SMAPI/SMAPI.csproj b/src/SMAPI/SMAPI.csproj index 3fc76299..1540ee42 100644 --- a/src/SMAPI/SMAPI.csproj +++ b/src/SMAPI/SMAPI.csproj @@ -25,7 +25,7 @@ - + -- cgit