using System; using System.Security; using System.Threading; using System.Text; using System.Reflection; using System.Reflection.Emit; using System.Runtime.InteropServices; using System.Linq; using System.Diagnostics; using System.Globalization; using System.IO; using System.Collections.Generic; namespace BizHawk { /// /// universal interface to a shared library /// public interface ILibAccessor : IDisposable { IntPtr GetProcAddress(string name); bool IsOpen { get; } } /// /// universal access to an external emulator core /// public interface IExternalCoreAccessor : IDisposable { IntPtr Signal(string type, IntPtr obj, string param, IntPtr value); bool IsOpen { get; } void RegisterCore(ExternalCore core, bool register); } public class CoreAccessor : IExternalCoreAccessor { ILibAccessor mLibAccessor; public CoreAccessor(ILibAccessor accessor) { mLibAccessor = accessor; if (accessor.IsOpen) { mSignal = (SignalCallbackDelegate)Marshal.GetDelegateForFunctionPointer(accessor.GetProcAddress("Core_signal"), typeof(SignalCallbackDelegate)); IsOpen = true; } } public void RegisterCore(ExternalCore core, bool register) { if (core is StaticCoreCommon) return; //defer initialization until the core is needed, to avoid pointless costs for cores the user isnt using if (!IsInitialized) { IsInitialized = true; scc = new StaticCoreCommon(this); scc.RegisterClientSignal(new SignalCallbackDelegate(ClientSignal)); scc.Initialize(); } if (register) { mCoreRegistry[core.ManagedOpaque] = core; } else { mCoreRegistry.Remove(core.ManagedOpaque); } } StaticCoreCommon scc; Dictionary mCoreRegistry = new Dictionary(); public void Dispose() { if (mLibAccessor == null) return; scc.Dispose(); mLibAccessor.Dispose(); mLibAccessor = null; IsOpen = false; } public bool IsOpen { get; private set; } bool IsInitialized; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate IntPtr SignalCallbackDelegate(string type, IntPtr obj, string param, IntPtr value); SignalCallbackDelegate mSignal; //external cores call into the client from here public IntPtr ClientSignal(string type, IntPtr obj, string param, IntPtr value) { //static calls if (obj == IntPtr.Zero) { return scc.ClientSignal(type, obj, param, value); } else { return mCoreRegistry[obj].ClientSignal(type, obj, param, value); } } public IntPtr Signal(string type, IntPtr obj, string param, IntPtr value) { if (!IsOpen) throw new InvalidOperationException("core accessor is open"); return mSignal(type, obj, param, value); } } //todo - make abstract public class ExternalCore : IDisposable { //rename to managed and unmanaged public IntPtr ManagedOpaque; public IntPtr UnmanagedOpaque; static int _ManagedOpaque_Counter = 1; private static object oLock = new object(); protected IExternalCoreAccessor mAccessor; public ExternalCore(IExternalCoreAccessor accessor) { mAccessor = accessor; lock (oLock) { ManagedOpaque = new IntPtr(_ManagedOpaque_Counter); _ManagedOpaque_Counter++; } mAccessor.RegisterCore(this, true); } public virtual void Dispose() { mAccessor.RegisterCore(this, false); //universal delete mechanism? //probably not. } /// /// cores call into the client from here. this system is not fully baked yet, though /// public virtual IntPtr ClientSignal(string type, IntPtr obj, string param, IntPtr value) { //if (type == "FACTORY") //{ // return new DiscInterface(mAccessor).UnmanagedOpaque; //} //else return IntPtr.Zero; return IntPtr.Zero; } /// /// merely emits an integer of the current system int size to an ILGenerator /// static void EmitIntPtr(ILGenerator gen, IntPtr val) { if (IntPtr.Size == 4) gen.Emit(OpCodes.Ldc_I4, val.ToInt32()); else gen.Emit(OpCodes.Ldc_I8, val.ToInt64()); } /// /// retrieves a function pointer from the core and returns it as the specified delegate type /// protected void QueryCoreCall(out T del, string name) { del = QueryCoreCall(name); } /// /// retrieves a function pointer from the core and returns it as the specified delegate type /// protected T QueryCoreCall(string name) { MethodInfo mi = typeof(T).GetMethod("Invoke"); ParameterInfo[] pis = mi.GetParameters(); Type[] unmanagedParamTypes = new Type[pis.Length + 1]; Type[] managedParamTypes = new Type[pis.Length]; unmanagedParamTypes[0] = typeof(int); for (int i = 0; i < pis.Length; i++) { unmanagedParamTypes[i + 1] = pis[i].ParameterType; managedParamTypes[i] = pis[i].ParameterType; } IntPtr fptr = mAccessor.Signal("QUERY_FUNCTION", IntPtr.Zero, name, IntPtr.Zero); if (fptr == IntPtr.Zero) throw new InvalidOperationException("external core was missing requested function: " + name); DynamicMethod dm = new DynamicMethod("", mi.ReturnType, managedParamTypes, GetType().Module); ILGenerator gen = dm.GetILGenerator(); EmitIntPtr(gen, UnmanagedOpaque); for (int i = 0; i < pis.Length; i++) gen.Emit(OpCodes.Ldarg, i); EmitIntPtr(gen, fptr); gen.EmitCalli(OpCodes.Calli, CallingConvention.ThisCall, mi.ReturnType, unmanagedParamTypes); gen.Emit(OpCodes.Ret); Delegate d = dm.CreateDelegate(typeof(T)); return (T)(object)d; } /// /// exports a delegate as an IntPtr for use in unmanaged code and manages its life cycle to keep it from getting freed /// protected IntPtr ExportDelegate(Delegate d) { IntPtr retPtr = Marshal.GetFunctionPointerForDelegate(d); listLiveDelegates.Add(d); return retPtr; } //need to hold on to these callbacks to make sure they dont get GCed while unmanaged code has a pointer to them List listLiveDelegates = new List(); } public class StaticCoreCommon : ExternalCore { //keep in mind that we may need to make the console thread safe if we ever do any zany multithreaded things public StaticCoreCommon(IExternalCoreAccessor accessor) : base(accessor) { } EmuFile Console; public void Initialize() { Console = new EmuFile(mAccessor); if (Log.HACK_LOG_STREAM != null) Console.BaseStream = Log.HACK_LOG_STREAM; else Console.BaseStream = System.Console.OpenStandardOutput(); mAccessor.Signal("SET_CONSOLE", IntPtr.Zero, null, Console.UnmanagedOpaque); } public void RegisterClientSignal(CoreAccessor.SignalCallbackDelegate ClientSignal) { mAccessor.Signal("SET_CLIENT_SIGNAL", IntPtr.Zero, null, ExportDelegate(ClientSignal)); } public override IntPtr ClientSignal(string type, IntPtr obj, string param, IntPtr value) { return IntPtr.Zero; } } }