diff --git a/BizHawk.Emulation.Common/BizInvoke/BizInvoker.cs b/BizHawk.Emulation.Common/BizInvoke/BizInvoker.cs index cb3b3b36b1..475b5c40fb 100644 --- a/BizHawk.Emulation.Common/BizInvoke/BizInvoker.cs +++ b/BizHawk.Emulation.Common/BizInvoke/BizInvoker.cs @@ -10,10 +10,65 @@ namespace BizHawk.Emulation.Common.BizInvoke { public static class BizInvoker { + /// + /// holds information about a proxy implementation, including type and setup hooks + /// + private class InvokerImpl + { + public Type ImplType; + public List> Hooks; + + public object Create(IImportResolver dll) + { + var ret = Activator.CreateInstance(ImplType); + foreach (var f in Hooks) + f(ret, dll); + return ret; + } + } + + /// + /// dictionary of all generated proxy implementations and their basetypes + /// + private static IDictionary Impls = new Dictionary(); + + /// + /// the assembly that all proxies are placed in + /// + private static readonly AssemblyBuilder ImplAssemblyBuilder; + /// + /// the module that all proxies are placed in + /// + private static readonly ModuleBuilder ImplModuleBilder; + + static BizInvoker() + { + var aname = new AssemblyName("BizInvokeProxyAssembly"); + ImplAssemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(aname, AssemblyBuilderAccess.Run); + ImplModuleBilder = ImplAssemblyBuilder.DefineDynamicModule("BizInvokerModule"); + } + + /// + /// get an implementation proxy for an interop class + /// public static T GetInvoker(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>(); + var postCreateHooks = new List>(); - 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 ImplementMethodDelegate(TypeBuilder type, MethodInfo baseMethod, IntPtr entryPtr, CallingConvention nativeCall) + /// + /// create a method implementation that uses GetDelegateForFunctionPointer internally + /// + private static Action 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) + /// + /// create a method implementation that uses calli internally + /// + private static Action 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(); 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); + }; } /// @@ -180,6 +245,9 @@ namespace BizHawk.Emulation.Common.BizInvoke il.Emit(OpCodes.Conv_U); } + /// + /// emit a single parameter load with unmanaged conversions + /// private static Type EmitParamterLoad(ILGenerator il, int idx, Type type) { if (type.IsGenericType) @@ -259,6 +327,9 @@ namespace BizHawk.Emulation.Common.BizInvoke } } + /// + /// mark an abstract method to be proxied by BizInvoker + /// [AttributeUsage(AttributeTargets.Method)] public class BizImportAttribute : Attribute { @@ -268,8 +339,20 @@ namespace BizHawk.Emulation.Common.BizInvoke } private readonly CallingConvention _callingConvention; + /// + /// name of entry point; if not given, the method's name is used + /// public string EntryPoint { get; set; } + /// + /// Use a slower interop that supports more argument types + /// + public bool Compatibility { get; set; } + + /// + /// + /// + /// unmanaged calling convention public BizImportAttribute(CallingConvention c) { _callingConvention = c; diff --git a/BizHawk.Emulation.Common/BizInvoke/IImportResolver.cs b/BizHawk.Emulation.Common/BizInvoke/IImportResolver.cs index bb33bd06cf..8a023c380f 100644 --- a/BizHawk.Emulation.Common/BizInvoke/IImportResolver.cs +++ b/BizHawk.Emulation.Common/BizInvoke/IImportResolver.cs @@ -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; + } + } }