add WIP proxying of methods with `out` parameters

This commit is contained in:
Shockah 2022-02-09 23:26:26 +01:00
parent d9599a3a0a
commit f920ed59d6
1 changed files with 56 additions and 10 deletions

View File

@ -98,7 +98,7 @@ namespace StardewModdingAPI.Framework.Reflection
if (typeA.IsGenericMethodParameter ? typeA.GenericParameterPosition == typeB.GenericParameterPosition : typeA.IsAssignableFrom(typeB)) if (typeA.IsGenericMethodParameter ? typeA.GenericParameterPosition == typeB.GenericParameterPosition : typeA.IsAssignableFrom(typeB))
return MatchingTypesResult.True; 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.IfProxied;
return MatchingTypesResult.False; return MatchingTypesResult.False;
} }
@ -233,8 +233,15 @@ namespace StardewModdingAPI.Framework.Reflection
} }
else // it's one of the parameters else // it's one of the parameters
{ {
var builder = factory.ObtainBuilder(targetParameters[position.Value].ParameterType, argTypes[position.Value], sourceModID, targetModID); bool isByRef = argTypes[position.Value].IsByRef;
argTypes[position.Value] = proxy.ReturnType; 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; parameterProxyTypeNames[position.Value] = builder.ProxyTypeName;
} }
} }
@ -246,32 +253,63 @@ namespace StardewModdingAPI.Framework.Reflection
// create method body // create method body
{ {
ILGenerator il = methodBuilder.GetILGenerator(); 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` var resultLocal = il.DeclareLocal(typeof(object)); // we store both unmodified and modified in here, hence `object`
il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, instanceField); il.Emit(OpCodes.Ldfld, instanceField);
for (int i = 0; i < argTypes.Length; i++) for (int i = 0; i < argTypes.Length; i++)
{
if (parameterProxyTypeNames[i] == null)
{
il.Emit(OpCodes.Ldarg, i + 1); 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.Callvirt, target);
il.Emit(OpCodes.Stloc, resultLocal); 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(); var isNullLabel = il.DefineLabel();
il.Emit(OpCodes.Ldloc, resultLocal); il.Emit(OpCodes.Ldloc, local);
il.Emit(OpCodes.Brfalse, isNullLabel); il.Emit(OpCodes.Brfalse, isNullLabel);
il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, glueField); il.Emit(OpCodes.Ldfld, glueField);
il.Emit(OpCodes.Ldstr, returnValueProxyTypeName); il.Emit(OpCodes.Ldstr, proxyTypeName);
il.Emit(OpCodes.Ldloc, resultLocal); il.Emit(OpCodes.Ldloc, local);
il.Emit(OpCodes.Call, CreateInstanceForProxyTypeNameMethod); il.Emit(OpCodes.Call, CreateInstanceForProxyTypeNameMethod);
il.Emit(OpCodes.Stloc, resultLocal); il.Emit(OpCodes.Stloc, local);
il.MarkLabel(isNullLabel); 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 // return result
il.Emit(OpCodes.Ldloc, resultLocal); il.Emit(OpCodes.Ldloc, resultLocal);
il.Emit(OpCodes.Ret); il.Emit(OpCodes.Ret);
@ -290,4 +328,12 @@ namespace StardewModdingAPI.Framework.Reflection
False, IfProxied, True False, IfProxied, True
} }
} }
internal static class TypeExtensions
{
internal static Type GetNonRefType(this Type type)
{
return type.IsByRef ? type.GetElementType() : type;
}
}
} }