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))
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;
}
}
}