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
{
/// <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)
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)
throw new InvalidOperationException("Can't proxy a sealed type");
if (!baseType.IsPublic)
@ -49,38 +104,32 @@ namespace BizHawk.Emulation.Common.BizInvoke
}
// 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 assy = AppDomain.CurrentDomain.DefineDynamicAssembly(aname, AssemblyBuilderAccess.Run);
var module = assy.DefineDynamicModule("BizInvoker");
var type = module.DefineType("Bizhawk.BizInvokeProxy", TypeAttributes.Class | TypeAttributes.Public, baseType);
var type = ImplModuleBilder.DefineType("Bizhawk.BizInvokeProxy", TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.Sealed, baseType);
foreach (var mi in baseMethods)
{
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)
{
ImplementMethodCalli(type, mi.Info, entryPtr, mi.Attr.CallingConvention);
}
else
{
var hook = ImplementMethodDelegate(type, mi.Info, entryPtr, mi.Attr.CallingConvention);
postCreateHooks.Add(hook);
}
var hook = mi.Attr.Compatibility
? ImplementMethodDelegate(type, mi.Info, mi.Attr.CallingConvention, entryPointName)
: ImplementMethodCalli(type, mi.Info, mi.Attr.CallingConvention, entryPointName);
postCreateHooks.Add(hook);
}
var ret = Activator.CreateInstance(type.CreateType());
foreach (var hook in postCreateHooks)
hook(ret);
return (T)ret;
return new InvokerImpl
{
Hooks = postCreateHooks,
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 paramTypes = paramInfos.Select(p => p.ParameterType).ToArray();
@ -117,24 +166,33 @@ namespace BizHawk.Emulation.Common.BizInvoke
type.DefineMethodOverride(method, baseMethod);
return (o) =>
return (o, dll) =>
{
var entryPtr = dll.SafeResolve(entryPointName);
var interopDelegate = Marshal.GetDelegateForFunctionPointer(entryPtr, delegateType.CreateType());
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 paramTypes = paramInfos.Select(p => p.ParameterType).ToArray();
var nativeParamTypes = new List<Type>();
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,
CallingConventions.HasThis, returnType, paramTypes);
if (returnType != typeof(void) && !returnType.IsPrimitive)
throw new InvalidOperationException("Only primitive return types are supported");
var il = method.GetILGenerator();
for (int i = 0; i < paramTypes.Length; i++)
@ -142,7 +200,8 @@ namespace BizHawk.Emulation.Common.BizInvoke
// arg 0 is this, so + 1
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());
// 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);
type.DefineMethodOverride(method, baseMethod);
return (o, dll) =>
{
var entryPtr = dll.SafeResolve(entryPointName);
o.GetType().GetField(field.Name).SetValue(o, entryPtr);
};
}
/// <summary>
@ -180,6 +245,9 @@ namespace BizHawk.Emulation.Common.BizInvoke
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)
{
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)]
public class BizImportAttribute : Attribute
{
@ -268,8 +339,20 @@ namespace BizHawk.Emulation.Common.BizInvoke
}
private readonly CallingConvention _callingConvention;
/// <summary>
/// name of entry point; if not given, the method's name is used
/// </summary>
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)
{
_callingConvention = c;

View File

@ -9,4 +9,15 @@ namespace BizHawk.Emulation.Common.BizInvoke
{
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;
}
}
}