Clean up and work on BizInvoker some more

This commit is contained in:
nattthebear 2016-02-20 11:30:05 -05:00
parent f59038fb90
commit 787711389a
2 changed files with 122 additions and 28 deletions

View File

@ -10,10 +10,65 @@ namespace BizHawk.Emulation.Common.BizInvoke
{ {
public static class BizInvoker public static class BizInvoker
{ {
/// <summary>
/// holds information about a proxy implementation, including type and setup hooks
/// </summary>
private class InvokerImpl
{
public Type ImplType;
public List<Action<object, IImportResolver>> Hooks;
public object Create(IImportResolver dll)
{
var ret = Activator.CreateInstance(ImplType);
foreach (var f in Hooks)
f(ret, dll);
return ret;
}
}
/// <summary>
/// dictionary of all generated proxy implementations and their basetypes
/// </summary>
private static IDictionary<Type, InvokerImpl> Impls = new Dictionary<Type, InvokerImpl>();
/// <summary>
/// the assembly that all proxies are placed in
/// </summary>
private static readonly AssemblyBuilder ImplAssemblyBuilder;
/// <summary>
/// the module that all proxies are placed in
/// </summary>
private static readonly ModuleBuilder ImplModuleBilder;
static BizInvoker()
{
var aname = new AssemblyName("BizInvokeProxyAssembly");
ImplAssemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(aname, AssemblyBuilderAccess.Run);
ImplModuleBilder = ImplAssemblyBuilder.DefineDynamicModule("BizInvokerModule");
}
/// <summary>
/// get an implementation proxy for an interop class
/// </summary>
public static T GetInvoker<T>(IImportResolver dll) public static T GetInvoker<T>(IImportResolver dll)
where T : class where T : class
{ {
var baseType = typeof(T); InvokerImpl impl;
lock (Impls)
{
var baseType = typeof(T);
if (!Impls.TryGetValue(baseType, out impl))
{
impl = CreateProxy(baseType);
Impls.Add(baseType, impl);
}
}
return (T)impl.Create(dll);
}
private static InvokerImpl CreateProxy(Type baseType)
{
if (baseType.IsSealed) if (baseType.IsSealed)
throw new InvalidOperationException("Can't proxy a sealed type"); throw new InvalidOperationException("Can't proxy a sealed type");
if (!baseType.IsPublic) if (!baseType.IsPublic)
@ -49,38 +104,32 @@ namespace BizHawk.Emulation.Common.BizInvoke
} }
// hooks that will be run on the created proxy object // hooks that will be run on the created proxy object
var postCreateHooks = new List<Action<object>>(); var postCreateHooks = new List<Action<object, IImportResolver>>();
var aname = new AssemblyName(baseType.Name + Guid.NewGuid().ToString("N")); var type = ImplModuleBilder.DefineType("Bizhawk.BizInvokeProxy", TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.Sealed, baseType);
var assy = AppDomain.CurrentDomain.DefineDynamicAssembly(aname, AssemblyBuilderAccess.Run);
var module = assy.DefineDynamicModule("BizInvoker");
var type = module.DefineType("Bizhawk.BizInvokeProxy", TypeAttributes.Class | TypeAttributes.Public, baseType);
foreach (var mi in baseMethods) foreach (var mi in baseMethods)
{ {
var entryPointName = mi.Attr.EntryPoint ?? mi.Info.Name; var entryPointName = mi.Attr.EntryPoint ?? mi.Info.Name;
var entryPtr = dll.Resolve(entryPointName);
if (entryPtr == IntPtr.Zero)
throw new InvalidOperationException("Resolver returned NULL for entry point " + entryPointName);
if (false) var hook = mi.Attr.Compatibility
{ ? ImplementMethodDelegate(type, mi.Info, mi.Attr.CallingConvention, entryPointName)
ImplementMethodCalli(type, mi.Info, entryPtr, mi.Attr.CallingConvention); : ImplementMethodCalli(type, mi.Info, mi.Attr.CallingConvention, entryPointName);
}
else postCreateHooks.Add(hook);
{
var hook = ImplementMethodDelegate(type, mi.Info, entryPtr, mi.Attr.CallingConvention);
postCreateHooks.Add(hook);
}
} }
var ret = Activator.CreateInstance(type.CreateType()); return new InvokerImpl
foreach (var hook in postCreateHooks) {
hook(ret); Hooks = postCreateHooks,
return (T)ret; ImplType = type.CreateType()
};
} }
private static Action<object> ImplementMethodDelegate(TypeBuilder type, MethodInfo baseMethod, IntPtr entryPtr, CallingConvention nativeCall) /// <summary>
/// create a method implementation that uses GetDelegateForFunctionPointer internally
/// </summary>
private static Action<object, IImportResolver> ImplementMethodDelegate(TypeBuilder type, MethodInfo baseMethod, CallingConvention nativeCall, string entryPointName)
{ {
var paramInfos = baseMethod.GetParameters(); var paramInfos = baseMethod.GetParameters();
var paramTypes = paramInfos.Select(p => p.ParameterType).ToArray(); var paramTypes = paramInfos.Select(p => p.ParameterType).ToArray();
@ -117,24 +166,33 @@ namespace BizHawk.Emulation.Common.BizInvoke
type.DefineMethodOverride(method, baseMethod); type.DefineMethodOverride(method, baseMethod);
return (o) => return (o, dll) =>
{ {
var entryPtr = dll.SafeResolve(entryPointName);
var interopDelegate = Marshal.GetDelegateForFunctionPointer(entryPtr, delegateType.CreateType()); var interopDelegate = Marshal.GetDelegateForFunctionPointer(entryPtr, delegateType.CreateType());
o.GetType().GetField(field.Name).SetValue(o, interopDelegate); o.GetType().GetField(field.Name).SetValue(o, interopDelegate);
}; };
} }
private static void ImplementMethodCalli(TypeBuilder type, MethodInfo baseMethod, IntPtr entryPtr, CallingConvention nativeCall) /// <summary>
/// create a method implementation that uses calli internally
/// </summary>
private static Action<object, IImportResolver> ImplementMethodCalli(TypeBuilder type, MethodInfo baseMethod, CallingConvention nativeCall, string entryPointName)
{ {
var paramInfos = baseMethod.GetParameters(); var paramInfos = baseMethod.GetParameters();
var paramTypes = paramInfos.Select(p => p.ParameterType).ToArray(); var paramTypes = paramInfos.Select(p => p.ParameterType).ToArray();
var nativeParamTypes = new List<Type>(); var nativeParamTypes = new List<Type>();
var returnType = baseMethod.ReturnType; var returnType = baseMethod.ReturnType;
if (returnType != typeof(void) && !returnType.IsPrimitive)
throw new InvalidOperationException("Only primitive return types are supported");
// define a field on the type to hold the entry pointer
var field = type.DefineField("EntryPtrField" + baseMethod.Name, typeof(IntPtr),
FieldAttributes.Public);
var method = type.DefineMethod(baseMethod.Name, MethodAttributes.Virtual | MethodAttributes.Public, var method = type.DefineMethod(baseMethod.Name, MethodAttributes.Virtual | MethodAttributes.Public,
CallingConventions.HasThis, returnType, paramTypes); CallingConventions.HasThis, returnType, paramTypes);
if (returnType != typeof(void) && !returnType.IsPrimitive)
throw new InvalidOperationException("Only primitive return types are supported");
var il = method.GetILGenerator(); var il = method.GetILGenerator();
for (int i = 0; i < paramTypes.Length; i++) for (int i = 0; i < paramTypes.Length; i++)
@ -142,7 +200,8 @@ namespace BizHawk.Emulation.Common.BizInvoke
// arg 0 is this, so + 1 // arg 0 is this, so + 1
nativeParamTypes.Add(EmitParamterLoad(il, i + 1, paramTypes[i])); nativeParamTypes.Add(EmitParamterLoad(il, i + 1, paramTypes[i]));
} }
LoadConstant(il, entryPtr); il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, field);
il.EmitCalli(OpCodes.Calli, nativeCall, returnType, nativeParamTypes.ToArray()); il.EmitCalli(OpCodes.Calli, nativeCall, returnType, nativeParamTypes.ToArray());
// either there's a primitive on the stack and we're expected to return that primitive, // either there's a primitive on the stack and we're expected to return that primitive,
@ -150,6 +209,12 @@ namespace BizHawk.Emulation.Common.BizInvoke
il.Emit(OpCodes.Ret); il.Emit(OpCodes.Ret);
type.DefineMethodOverride(method, baseMethod); type.DefineMethodOverride(method, baseMethod);
return (o, dll) =>
{
var entryPtr = dll.SafeResolve(entryPointName);
o.GetType().GetField(field.Name).SetValue(o, entryPtr);
};
} }
/// <summary> /// <summary>
@ -180,6 +245,9 @@ namespace BizHawk.Emulation.Common.BizInvoke
il.Emit(OpCodes.Conv_U); il.Emit(OpCodes.Conv_U);
} }
/// <summary>
/// emit a single parameter load with unmanaged conversions
/// </summary>
private static Type EmitParamterLoad(ILGenerator il, int idx, Type type) private static Type EmitParamterLoad(ILGenerator il, int idx, Type type)
{ {
if (type.IsGenericType) if (type.IsGenericType)
@ -259,6 +327,9 @@ namespace BizHawk.Emulation.Common.BizInvoke
} }
} }
/// <summary>
/// mark an abstract method to be proxied by BizInvoker
/// </summary>
[AttributeUsage(AttributeTargets.Method)] [AttributeUsage(AttributeTargets.Method)]
public class BizImportAttribute : Attribute public class BizImportAttribute : Attribute
{ {
@ -268,8 +339,20 @@ namespace BizHawk.Emulation.Common.BizInvoke
} }
private readonly CallingConvention _callingConvention; private readonly CallingConvention _callingConvention;
/// <summary>
/// name of entry point; if not given, the method's name is used
/// </summary>
public string EntryPoint { get; set; } public string EntryPoint { get; set; }
/// <summary>
/// Use a slower interop that supports more argument types
/// </summary>
public bool Compatibility { get; set; }
/// <summary>
///
/// </summary>
/// <param name="c">unmanaged calling convention</param>
public BizImportAttribute(CallingConvention c) public BizImportAttribute(CallingConvention c)
{ {
_callingConvention = c; _callingConvention = c;

View File

@ -9,4 +9,15 @@ namespace BizHawk.Emulation.Common.BizInvoke
{ {
IntPtr Resolve(string entryPoint); IntPtr Resolve(string entryPoint);
} }
public static class ImportResolverExtensions
{
public static IntPtr SafeResolve(this IImportResolver dll, string entryPoint)
{
var ret = dll.Resolve(entryPoint);
if (ret == IntPtr.Zero)
throw new NullReferenceException(string.Format("Couldn't resolve entry point \"{0}\"", entryPoint));
return ret;
}
}
} }