using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Reflection; using System.Reflection.Emit; using System.Linq.Expressions; using System.IO; using BizHawk.Common; using BizHawk.Common.ReflectionExtensions; using System.Collections.Concurrent; using System.Runtime.InteropServices; namespace BizHawk.Emulation.Common { public class BinaryQuickSerializer { // fields are serialized/deserialized in their memory order as reported by Marshal.OffsetOf // to do anything useful, passed targets should be [StructLayout.Sequential] or [StructLayout.Explicit] #region methodinfo from a lambda expression. cool. private static MethodInfo FromExpression(Expression e) { var caller = e as MethodCallExpression; if (caller == null) throw new ArgumentException("Expression must be a method call"); return caller.Method; } private static MethodInfo Method(Expression f) { return FromExpression(f.Body); } private static MethodInfo Method(Expression> f) { return FromExpression(f.Body); } #endregion #region read and write handlers for individual fields private static Dictionary readhandlers = new Dictionary(); private static Dictionary writehandlers = new Dictionary(); private static void AddR(Expression> f) { var method = Method(f); if (!typeof(T).IsAssignableFrom(method.ReturnType)) throw new InvalidOperationException("Type mismatch"); readhandlers.Add(typeof(T), method); } private static void AddW(Expression> f) { var method = Method(f); if (!method.GetParameters()[0].ParameterType.IsAssignableFrom(typeof(T))) throw new InvalidOperationException("Type mismatch"); writehandlers.Add(typeof(T), Method(f)); } static BinaryQuickSerializer() { AddR(r => r.ReadBoolean()); AddW(r => r.Write(false)); AddR(r => r.ReadSByte()); AddW(r => r.Write((sbyte)0)); AddR(r => r.ReadByte()); AddW(r => r.Write((byte)0)); AddR(r => r.ReadInt16()); AddW(r => r.Write((short)0)); AddR(r => r.ReadUInt16()); AddW(r => r.Write((ushort)0)); AddR(r => r.ReadInt32()); AddW(r => r.Write((int)0)); AddR(r => r.ReadUInt32()); AddW(r => r.Write((uint)0)); AddR(r => r.ReadInt64()); AddW(r => r.Write((long)0)); AddR(r => r.ReadUInt64()); AddW(r => r.Write((ulong)0)); } #endregion #region dynamic code generation private delegate void Reader(object target, BinaryReader r); private delegate void Writer(object target, BinaryWriter w); private class SerializationFactory { public Type Type; public Reader Read; public Writer Write; } private static SerializationFactory CreateFactory(Type t) { var fields = DeepEquality.GetAllFields(t) //.OrderBy(fi => (int)fi.GetManagedOffset()) // [StructLayout.Sequential] doesn't work with this .OrderBy(fi => (int)Marshal.OffsetOf(t, fi.Name)) .ToList(); var rmeth = new DynamicMethod(t.Name + "_r", null, new[] { typeof(object), typeof(BinaryReader) }, true); var wmeth = new DynamicMethod(t.Name + "_w", null, new[] { typeof(object), typeof(BinaryWriter) }, true); { var il = rmeth.GetILGenerator(); var target = il.DeclareLocal(t); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Castclass, t); il.Emit(OpCodes.Stloc, target); foreach (var field in fields) { il.Emit(OpCodes.Ldloc, target); il.Emit(OpCodes.Ldarg_1); MethodInfo m; if (!readhandlers.TryGetValue(field.FieldType, out m)) throw new InvalidOperationException("(R) Can't handle nested type " + field.FieldType); il.Emit(OpCodes.Callvirt, m); il.Emit(OpCodes.Stfld, field); } il.Emit(OpCodes.Ret); } { var il = wmeth.GetILGenerator(); var target = il.DeclareLocal(t); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Castclass, t); il.Emit(OpCodes.Stloc, target); foreach (var field in fields) { il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Ldloc, target); il.Emit(OpCodes.Ldfld, field); MethodInfo m; if (!writehandlers.TryGetValue(field.FieldType, out m)) throw new InvalidOperationException("(W) Can't handle nested type " + field.FieldType); il.Emit(OpCodes.Callvirt, m); } il.Emit(OpCodes.Ret); } return new SerializationFactory { Type = t, Read = (Reader)rmeth.CreateDelegate(typeof(Reader)), Write = (Writer)wmeth.CreateDelegate(typeof(Writer)) }; } #endregion private static IDictionary serializers = new ConcurrentDictionary(); private static SerializationFactory GetFactory(Type t) { SerializationFactory f; if (!serializers.TryGetValue(t, out f)) { f = CreateFactory(t); serializers[t] = f; } return f; } public static T Create(BinaryReader r) where T : new() { T target = new T(); Read(target, r); return target; } public static object Create(Type t, BinaryReader r) { object target = Activator.CreateInstance(t); Read(target, r); return target; } public static void Read(object target, BinaryReader r) { GetFactory(target.GetType()).Read(target, r); } public static void Write(object target, BinaryWriter w) { GetFactory(target.GetType()).Write(target, w); } } }