using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
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
{
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)
// the proxy type will be in a new assembly, so public is required here
throw new InvalidOperationException("Type must be public");
var baseConstructor = baseType.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null);
if (baseConstructor == null)
throw new InvalidOperationException("Base type must have a zero arg constructor");
var baseMethods = baseType.GetMethods(BindingFlags.Instance | BindingFlags.Public)
.Select(m => new
{
Info = m,
Attr = m.GetCustomAttributes(true).OfType().FirstOrDefault()
})
.Where(a => a.Attr != null)
.ToList();
if (baseMethods.Count == 0)
throw new InvalidOperationException("Couldn't find any [BizImport] methods to proxy");
{
var uo = baseMethods.FirstOrDefault(a => !a.Info.IsVirtual || a.Info.IsFinal);
if (uo != null)
throw new InvalidOperationException("Method " + uo.Info.Name + " cannot be overriden!");
// there's no technical reason to disallow this, but we wouldn't be doing anything
// with the base implementation, so it's probably a user error
var na = baseMethods.FirstOrDefault(a => !a.Info.IsAbstract);
if (na != null)
throw new InvalidOperationException("Method " + na.Info.Name + " is not abstract!");
}
// hooks that will be run on the created proxy object
var postCreateHooks = new List>();
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 hook = mi.Attr.Compatibility
? ImplementMethodDelegate(type, mi.Info, mi.Attr.CallingConvention, entryPointName)
: ImplementMethodCalli(type, mi.Info, mi.Attr.CallingConvention, entryPointName);
postCreateHooks.Add(hook);
}
return new InvokerImpl
{
Hooks = postCreateHooks,
ImplType = type.CreateType()
};
}
///
/// create a method implementation that uses GetDelegateForFunctionPointer internally
///
private static Action