bizinvoker - fix a bug that caused the CLR to reject any [BizInvoke, Compatibility = false] method with a string parameter in any position except the first

According to the rules (which are hard to find), there must be nothing on the evaluation stack when localloc is called (except its own size parameter).  The generated string interop code didn't respect that.  This didn't cause any problems in existing code.
This commit is contained in:
nattthebear 2020-06-30 17:53:26 -04:00
parent ef311576ee
commit 6e938718f5
1 changed files with 123 additions and 66 deletions

View File

@ -287,6 +287,19 @@ namespace BizHawk.BizInvoke
};
}
private class ParameterLoadInfo
{
/// <summary>
/// The native type for this parameter, to pass to calli
/// </summary>
public Type NativeType;
/// <summary>
/// Closure that will actually emit the parameter load to the il stream. The evaluation stack will
/// already have other parameters on it at this time.
/// </summary>
public Action EmitLoad;
}
/// <summary>
/// create a method implementation that uses calli internally
/// </summary>
@ -296,7 +309,7 @@ namespace BizHawk.BizInvoke
{
var paramInfos = baseMethod.GetParameters();
var paramTypes = paramInfos.Select(p => p.ParameterType).ToArray();
var nativeParamTypes = new List<Type>();
var paramLoadInfos = new List<ParameterLoadInfo>();
var returnType = baseMethod.ReturnType;
if (returnType != typeof(void) && !returnType.IsPrimitive && !returnType.IsPointer && !returnType.IsEnum)
{
@ -327,10 +340,16 @@ namespace BizHawk.BizInvoke
exc = il.BeginExceptionBlock();
}
// phase 1: empty eval stack and each parameter load thunk does any prep work it needs to do
for (int i = 0; i < paramTypes.Length; i++)
{
// arg 0 is this, so + 1
nativeParamTypes.Add(EmitParamterLoad(il, i + 1, paramTypes[i], adapterField));
paramLoadInfos.Add(EmitParamterLoad(il, i + 1, paramTypes[i], adapterField));
}
// phase 2: actually load the individual params, leaving each one on the stack
foreach (var pli in paramLoadInfos)
{
pli.EmitLoad();
}
il.Emit(OpCodes.Ldarg_0);
@ -338,7 +357,7 @@ namespace BizHawk.BizInvoke
il.EmitCalli(OpCodes.Calli,
nativeCall,
returnType == typeof(bool) ? typeof(byte) : returnType, // undo winapi style bool garbage
nativeParamTypes.ToArray());
paramLoadInfos.Select(p => p.NativeType).ToArray());
if (monitorField != null) // monitor: finally exit
{
@ -419,9 +438,10 @@ namespace BizHawk.BizInvoke
}
/// <summary>
/// emit a single parameter load with unmanaged conversions
/// emit a single parameter load with unmanaged conversions. The evaluation stack will be empty when the IL generated here runs,
/// and should end as empty.
/// </summary>
private static Type EmitParamterLoad(ILGenerator il, int idx, Type type, FieldInfo adapterField)
private static ParameterLoadInfo EmitParamterLoad(ILGenerator il, int idx, Type type, FieldInfo adapterField)
{
if (type.IsGenericType)
{
@ -435,13 +455,18 @@ namespace BizHawk.BizInvoke
{
throw new NotImplementedException("Only refs of primitive or enum types are supported!");
}
var loc = il.DeclareLocal(type, true);
il.Emit(OpCodes.Ldarg, (short)idx);
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Stloc, loc);
il.Emit(OpCodes.Conv_I);
return typeof(IntPtr);
return new ParameterLoadInfo
{
NativeType = typeof(IntPtr),
EmitLoad = () =>
{
var loc = il.DeclareLocal(type, true);
il.Emit(OpCodes.Ldarg, (short)idx);
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Stloc, loc);
il.Emit(OpCodes.Conv_I);
}
};
}
if (type.IsArray)
@ -463,48 +488,59 @@ namespace BizHawk.BizInvoke
throw new NotImplementedException("Only 0-based 1-dimensional arrays are supported!");
}
var loc = il.DeclareLocal(type, true);
var end = il.DefineLabel();
var isNull = il.DefineLabel();
return new ParameterLoadInfo
{
NativeType = typeof(IntPtr),
EmitLoad = () =>
{
var loc = il.DeclareLocal(type, true);
var end = il.DefineLabel();
var isNull = il.DefineLabel();
il.Emit(OpCodes.Ldarg, (short)idx);
il.Emit(OpCodes.Brfalse, isNull);
il.Emit(OpCodes.Ldarg, (short)idx);
il.Emit(OpCodes.Brfalse, isNull);
il.Emit(OpCodes.Ldarg, (short)idx);
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Stloc, loc);
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Ldelema, et);
il.Emit(OpCodes.Conv_I);
il.Emit(OpCodes.Br, end);
il.Emit(OpCodes.Ldarg, (short)idx);
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Stloc, loc);
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Ldelema, et);
il.Emit(OpCodes.Conv_I);
il.Emit(OpCodes.Br, end);
il.MarkLabel(isNull);
LoadConstant(il, IntPtr.Zero);
il.MarkLabel(end);
return typeof(IntPtr);
il.MarkLabel(isNull);
LoadConstant(il, IntPtr.Zero);
il.MarkLabel(end);
}
};
}
if (typeof(Delegate).IsAssignableFrom(type))
{
// callback -- use the same callingconventionadapter on it that the invoker is being made from
var mi = typeof(ICallingConventionAdapter).GetMethod("GetFunctionPointerForDelegate");
var end = il.DefineLabel();
var isNull = il.DefineLabel();
return new ParameterLoadInfo
{
NativeType = typeof(IntPtr),
EmitLoad = () =>
{
var mi = typeof(ICallingConventionAdapter).GetMethod("GetFunctionPointerForDelegate");
var end = il.DefineLabel();
var isNull = il.DefineLabel();
il.Emit(OpCodes.Ldarg, (short)idx);
il.Emit(OpCodes.Brfalse, isNull);
il.Emit(OpCodes.Ldarg, (short)idx);
il.Emit(OpCodes.Brfalse, isNull);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, adapterField);
il.Emit(OpCodes.Ldarg, (short)idx);
il.EmitCall(OpCodes.Callvirt, mi, Type.EmptyTypes);
il.Emit(OpCodes.Br, end);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, adapterField);
il.Emit(OpCodes.Ldarg, (short)idx);
il.EmitCall(OpCodes.Callvirt, mi, Type.EmptyTypes);
il.Emit(OpCodes.Br, end);
il.MarkLabel(isNull);
LoadConstant(il, IntPtr.Zero);
il.MarkLabel(end);
return typeof(IntPtr);
il.MarkLabel(isNull);
LoadConstant(il, IntPtr.Zero);
il.MarkLabel(end);
}
};
}
if (type == typeof(string))
@ -534,6 +570,8 @@ namespace BizHawk.BizInvoke
il.Emit(OpCodes.Ldc_I4_1);
il.Emit(OpCodes.Add); // +1 for null byte
il.Emit(OpCodes.Conv_U);
// NB: The evaluation stack must be entirely empty, except for the size argument, when calling localloc.
// That's why we have to split every parameter load into two parts, the first of which runs on an empty stack.
il.Emit(OpCodes.Localloc);
il.Emit(OpCodes.Stloc, bytes);
@ -556,46 +594,65 @@ namespace BizHawk.BizInvoke
// unused ret
il.Emit(OpCodes.Pop);
il.Emit(OpCodes.Ldloc, bytes);
il.Emit(OpCodes.Br, end);
il.MarkLabel(isNull);
LoadConstant(il, IntPtr.Zero);
il.Emit(OpCodes.Stloc, bytes);
il.MarkLabel(end);
return typeof(IntPtr);
return new ParameterLoadInfo
{
NativeType = typeof(IntPtr),
EmitLoad = () =>
{
il.Emit(OpCodes.Ldloc, bytes);
}
};
}
if (type.IsClass)
{
// non ref of class can just be passed as pointer
var loc = il.DeclareLocal(type, true);
var end = il.DefineLabel();
var isNull = il.DefineLabel();
return new ParameterLoadInfo
{
NativeType = typeof(IntPtr),
EmitLoad = () =>
{
var loc = il.DeclareLocal(type, true);
var end = il.DefineLabel();
var isNull = il.DefineLabel();
il.Emit(OpCodes.Ldarg, (short)idx);
il.Emit(OpCodes.Brfalse, isNull);
il.Emit(OpCodes.Ldarg, (short)idx);
il.Emit(OpCodes.Brfalse, isNull);
il.Emit(OpCodes.Ldarg, (short)idx);
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Stloc, loc);
il.Emit(OpCodes.Conv_I);
// skip past the methodtable pointer to the first field
il.Emit(OpCodes.Ldc_I4, ClassFieldOffset);
il.Emit(OpCodes.Conv_I);
il.Emit(OpCodes.Add);
il.Emit(OpCodes.Br, end);
il.Emit(OpCodes.Ldarg, (short)idx);
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Stloc, loc);
il.Emit(OpCodes.Conv_I);
// skip past the methodtable pointer to the first field
il.Emit(OpCodes.Ldc_I4, ClassFieldOffset);
il.Emit(OpCodes.Conv_I);
il.Emit(OpCodes.Add);
il.Emit(OpCodes.Br, end);
il.MarkLabel(isNull);
LoadConstant(il, IntPtr.Zero);
il.MarkLabel(end);
return typeof(IntPtr);
il.MarkLabel(isNull);
LoadConstant(il, IntPtr.Zero);
il.MarkLabel(end);
}
};
}
if (type.IsPrimitive || type.IsEnum || type.IsPointer)
{
il.Emit(OpCodes.Ldarg, (short)idx);
return type;
return new ParameterLoadInfo
{
NativeType = type,
EmitLoad = () =>
{
il.Emit(OpCodes.Ldarg, (short)idx);
}
};
}
throw new NotImplementedException("Unrecognized parameter type!");