/// <summary> /// Writes a primitive array to the stream. /// </summary> /// <typeparam name="T">The element type of the primitive array. Valid element types can be determined using <see cref="FormatterUtilities.IsPrimitiveArrayType(Type)" />.</typeparam> /// <param name="array">The primitive array to write.</param> /// <exception cref="System.ArgumentException">Type + typeof(T).Name + is not a valid primitive array type.</exception> /// <exception cref="System.ArgumentNullException">array</exception> public override void WritePrimitiveArray <T>(T[] array) { if (FormatterUtilities.IsPrimitiveArrayType(typeof(T)) == false) { throw new ArgumentException("Type " + typeof(T).Name + " is not a valid primitive array type."); } if (array == null) { throw new ArgumentNullException("array"); } Action <string, T> writer = (Action <string, T>) this.primitiveTypeWriters[typeof(T)]; this.WriteInt64(JsonConfig.PRIMITIVE_ARRAY_LENGTH_SIG, array.Length); this.WriteEntry(JsonConfig.PRIMITIVE_ARRAY_CONTENT_SIG, "["); this.forceNoSeparatorNextLine = true; this.PushArray(); for (int i = 0; i < array.Length; i++) { writer(null, array[i]); } this.PopArray(); this.StartNewLine(true); this.EnsureBufferSpace(1); this.buffer[this.bufferIndex++] = (byte)']'; }
public bool TryGetFormatter(Type type, FormatterLocationStep step, ISerializationPolicy policy, out IFormatter formatter) { if (!type.IsArray) { formatter = null; return(false); } if (type.GetArrayRank() == 1) { if (FormatterUtilities.IsPrimitiveArrayType(type.GetElementType())) { formatter = (IFormatter)Activator.CreateInstance(typeof(PrimitiveArrayFormatter <>).MakeGenericType(type.GetElementType())); } else { formatter = (IFormatter)Activator.CreateInstance(typeof(ArrayFormatter <>).MakeGenericType(type.GetElementType())); } } else { formatter = (IFormatter)Activator.CreateInstance(typeof(MultiDimensionalArrayFormatter <,>).MakeGenericType(type, type.GetElementType())); } return(true); }
private static void EmitWriteMethodContents( ILGenerator gen, Type formattedType, Dictionary <Type, FieldBuilder> serializerFields, Dictionary <MemberInfo, List <string> > memberNames, Dictionary <Type, MethodInfo> serializerWriteMethods) { foreach (var member in memberNames.Keys) { var memberType = FormatterUtilities.GetContainedType(member); gen.Emit(OpCodes.Ldsfld, serializerFields[memberType]); // Load serializer instance for type gen.Emit(OpCodes.Ldstr, member.Name); // Load member name string // Now we load the value of the actual member if (member is FieldInfo) { var fieldInfo = member as FieldInfo; if (formattedType.IsValueType) { gen.Emit(OpCodes.Ldarg_0); // Load value argument gen.Emit(OpCodes.Ldfld, fieldInfo.DeAliasField()); // Load value of field } else { gen.Emit(OpCodes.Ldarg_0); // Load value argument reference gen.Emit(OpCodes.Ldind_Ref); // Indirectly load value of reference gen.Emit(OpCodes.Ldfld, fieldInfo.DeAliasField()); // Load value of field } } else if (member is PropertyInfo) { var propInfo = member as PropertyInfo; if (formattedType.IsValueType) { gen.Emit(OpCodes.Ldarg_0); // Load value argument gen.Emit(OpCodes.Call, propInfo.DeAliasProperty().GetGetMethod(true)); // Call property getter } else { gen.Emit(OpCodes.Ldarg_0); // Load value argument reference gen.Emit(OpCodes.Ldind_Ref); // Indirectly load value of reference gen.Emit(OpCodes.Callvirt, propInfo.DeAliasProperty().GetGetMethod(true)); // Call property getter } } else { throw new NotImplementedException(); } gen.Emit(OpCodes.Ldarg_1); // Load writer argument gen.Emit(OpCodes.Callvirt, serializerWriteMethods[memberType]); // Call Serializer.WriteValue(string name, T value, IDataWriter writer) } gen.Emit(OpCodes.Ret); // Return method }
/// <summary> /// Reads a primitive array value. This call will succeed if the next entry is an <see cref="EntryType.PrimitiveArray" />. /// <para /> /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode" /> or an <see cref="EntryType.EndOfArray" />. /// </summary> /// <typeparam name="T">The element type of the primitive array. Valid element types can be determined using <see cref="FormatterUtilities.IsPrimitiveArrayType(Type)" />.</typeparam> /// <param name="array">The resulting primitive array.</param> /// <returns> /// <c>true</c> if reading a primitive array succeeded, otherwise <c>false</c> /// </returns> /// <exception cref="System.ArgumentException">Type + typeof(T).Name + is not a valid primitive array type.</exception> public override bool ReadPrimitiveArray <T>(out T[] array) { if (FormatterUtilities.IsPrimitiveArrayType(typeof(T)) == false) { throw new ArgumentException("Type " + typeof(T).Name + " is not a valid primitive array type."); } if (this.peekedEntryType != EntryType.PrimitiveArray) { this.SkipEntry(); array = null; return(false); } if (typeof(T) == typeof(byte)) { array = (T[])(object)ProperBitConverter.HexStringToBytes(this.peekedEntryData); return(true); } else { this.PeekEntry(); long length; if (this.peekedEntryType != EntryType.PrimitiveArray) { this.Context.Config.DebugContext.LogError("Expected entry of type '" + EntryType.StartOfArray + "' when reading primitive array but got entry of type '" + this.peekedEntryType + "'."); this.SkipEntry(); array = new T[0]; return(false); } if (!long.TryParse(this.peekedEntryData, NumberStyles.Any, CultureInfo.InvariantCulture, out length)) { this.Context.Config.DebugContext.LogError("Failed to parse primitive array length from entry data '" + this.peekedEntryData + "'."); this.SkipEntry(); array = new T[0]; return(false); } this.ConsumeCurrentEntry(); this.PushArray(); array = new T[length]; Func <T> reader = (Func <T>) this.primitiveTypeReaders[typeof(T)]; for (int i = 0; i < length; i++) { array[i] = reader(); } this.ExitArray(); return(true); } }
private static IFormatter CreateGenericFormatter(Type formattedType, ModuleBuilder moduleBuilder, ISerializationPolicy policy) { Dictionary <string, MemberInfo> serializableMembers = FormatterUtilities.GetSerializableMembersMap(formattedType, policy); if (serializableMembers.Count == 0) { return((IFormatter)Activator.CreateInstance(typeof(EmptyTypeFormatter <>).MakeGenericType(formattedType))); } string helperTypeName = moduleBuilder.Name + "." + formattedType.GetCompilableNiceFullName() + "___" + formattedType.Assembly.GetName().Name + "___FormatterHelper___" + Guid.NewGuid().ToString(); Dictionary <Type, MethodInfo> serializerReadMethods; Dictionary <Type, MethodInfo> serializerWriteMethods; Dictionary <Type, FieldBuilder> serializerFields; FieldBuilder dictField; Dictionary <MemberInfo, List <string> > memberNames; BuildHelperType( moduleBuilder, helperTypeName, formattedType, serializableMembers, out serializerReadMethods, out serializerWriteMethods, out serializerFields, out dictField, out memberNames ); Type formatterType = typeof(RuntimeEmittedFormatter <>).MakeGenericType(formattedType); Delegate del1, del2; // Read { Type readDelegateType = typeof(ReadDataEntryMethodDelegate <>).MakeGenericType(formattedType); MethodInfo readDataEntryMethod = formatterType.GetMethod("ReadDataEntry", Flags.InstanceAnyVisibility); DynamicMethod dynamicReadMethod = new DynamicMethod("Dynamic_" + formattedType.GetCompilableNiceFullName(), null, readDataEntryMethod.GetParameters().Select(n => n.ParameterType).ToArray(), true); readDataEntryMethod.GetParameters().ForEach(n => dynamicReadMethod.DefineParameter(n.Position, n.Attributes, n.Name)); EmitReadMethodContents(dynamicReadMethod.GetILGenerator(), formattedType, dictField, serializerFields, memberNames, serializerReadMethods); del1 = dynamicReadMethod.CreateDelegate(readDelegateType); } // Write { Type writeDelegateType = typeof(WriteDataEntriesMethodDelegate <>).MakeGenericType(formattedType); MethodInfo writeDataEntriesMethod = formatterType.GetMethod("WriteDataEntries", Flags.InstanceAnyVisibility); DynamicMethod dynamicWriteMethod = new DynamicMethod("Dynamic_Write_" + formattedType.GetCompilableNiceFullName(), null, writeDataEntriesMethod.GetParameters().Select(n => n.ParameterType).ToArray(), true); writeDataEntriesMethod.GetParameters().ForEach(n => dynamicWriteMethod.DefineParameter(n.Position + 1, n.Attributes, n.Name)); EmitWriteMethodContents(dynamicWriteMethod.GetILGenerator(), formattedType, serializerFields, memberNames, serializerWriteMethods); del2 = dynamicWriteMethod.CreateDelegate(writeDelegateType); } return((IFormatter)Activator.CreateInstance(formatterType, del1, del2)); }
/// <summary> /// Provides the actual implementation for deserializing a value of type <see cref="T" />. /// </summary> /// <param name="value">The uninitialized value to serialize into. This value will have been created earlier using <see cref="BaseFormatter{T}.GetUninitializedObject" />.</param> /// <param name="reader">The reader to deserialize with.</param> protected override void DeserializeImplementation(ref T value, IDataReader reader) { // We sadly *must* box so that complex value types get their values properly set by reflection. // At least we only box these once. object boxedValue = value; var members = FormatterUtilities.GetSerializableMembersMap(typeof(T), this.OverridePolicy ?? reader.Context.Config.SerializationPolicy); EntryType entryType; string name; while ((entryType = reader.PeekEntry(out name)) != EntryType.EndOfNode && entryType != EntryType.EndOfArray && entryType != EntryType.EndOfStream) { if (string.IsNullOrEmpty(name)) { reader.Context.Config.DebugContext.LogError("Entry of type \"" + entryType + "\" in node \"" + reader.CurrentNodeName + "\" is missing a name."); reader.SkipEntry(); continue; } MemberInfo member; if (members.TryGetValue(name, out member) == false) { reader.Context.Config.DebugContext.LogWarning("Lost serialization data for entry \"" + name + "\" of type \"" + entryType + "\"in node \"" + reader.CurrentNodeName + "\"."); reader.SkipEntry(); continue; } Type expectedType = FormatterUtilities.GetContainedType(member); try { var serializer = Serializer.Get(expectedType); object entryValue = serializer.ReadValueWeak(reader); FormatterUtilities.SetMemberValue(member, boxedValue, entryValue); } catch (Exception ex) { reader.Context.Config.DebugContext.LogException(ex); } } value = (T)boxedValue; // Unbox }
/// <summary> /// Not yet documented. /// </summary> public override void WritePrimitiveArray <T>(T[] array) { if (FormatterUtilities.IsPrimitiveArrayType(typeof(T)) == false) { throw new ArgumentException("Type " + typeof(T).Name + " is not a valid primitive array type."); } if (typeof(T) == typeof(byte)) { string hex = ProperBitConverter.BytesToHexString((byte[])(object)array); this.Nodes.Add(new SerializationNode() { Name = string.Empty, Entry = EntryType.PrimitiveArray, Data = hex }); } else { this.Nodes.Add(new SerializationNode() { Name = string.Empty, Entry = EntryType.PrimitiveArray, Data = array.LongLength.ToString(CultureInfo.InvariantCulture) }); this.PushArray(); Action <string, T> writer = (Action <string, T>) this.primitiveTypeWriters[typeof(T)]; for (int i = 0; i < array.Length; i++) { writer(string.Empty, array[i]); } this.EndArrayNode(); } }
/// <summary> /// Provides the actual implementation for serializing a value of type <see cref="T" />. /// </summary> /// <param name="value">The value to serialize.</param> /// <param name="writer">The writer to serialize with.</param> protected override void SerializeImplementation(ref T value, IDataWriter writer) { var members = FormatterUtilities.GetSerializableMembers(typeof(T), this.OverridePolicy ?? writer.Context.Config.SerializationPolicy); for (int i = 0; i < members.Length; i++) { var member = members[i]; Type type; var memberValue = FormatterUtilities.GetMemberValue(member, value); type = FormatterUtilities.GetContainedType(member); var serializer = Serializer.Get(type); try { serializer.WriteValueWeak(member.Name, memberValue, writer); } catch (Exception ex) { writer.Context.Config.DebugContext.LogException(ex); } } }
/// <summary> /// Reads a value of type <see cref="T" />. /// </summary> /// <param name="reader">The reader to use.</param> /// <returns> /// The value which has been read. /// </returns> public override T ReadValue(IDataReader reader) { var context = reader.Context; if (context.Config.SerializationPolicy.AllowNonSerializableTypes == false && TypeOf_T.IsSerializable == false) { context.Config.DebugContext.LogError("The type " + TypeOf_T.Name + " is not marked as serializable."); return(default(T)); } bool exitNode = true; string name; var entry = reader.PeekEntry(out name); if (ComplexTypeIsValueType) { if (entry == EntryType.Null) { context.Config.DebugContext.LogWarning("Expecting complex struct of type " + TypeOf_T.GetNiceFullName() + " but got null value."); reader.ReadNull(); return(default(T)); } else if (entry != EntryType.StartOfNode) { context.Config.DebugContext.LogWarning("Unexpected entry '" + name + "' of type " + entry.ToString() + ", when " + EntryType.StartOfNode + " was expected. A value has likely been lost."); reader.SkipEntry(); return(default(T)); } try { Type expectedType = TypeOf_T; Type serializedType; if (reader.EnterNode(out serializedType)) { if (serializedType != expectedType) { if (serializedType != null) { context.Config.DebugContext.LogWarning("Expected complex struct value " + expectedType.Name + " but the serialized value is of type " + serializedType.Name + "."); if (serializedType.IsCastableTo(expectedType)) { object value = FormatterLocator.GetFormatter(serializedType, context.Config.SerializationPolicy).Deserialize(reader); bool serializedTypeIsNullable = serializedType.IsGenericType && serializedType.GetGenericTypeDefinition() == typeof(Nullable <>); bool allowCastMethod = !ComplexTypeIsNullable && !serializedTypeIsNullable; var castMethod = allowCastMethod ? serializedType.GetCastMethodDelegate(expectedType) : null; if (castMethod != null) { return((T)castMethod(value)); } else { return((T)value); } } else if (AllowDeserializeInvalidDataForT || reader.Context.Config.AllowDeserializeInvalidData) { context.Config.DebugContext.LogWarning("Can't cast serialized type " + serializedType.Name + " into expected type " + expectedType.Name + ". Attempting to deserialize with possibly invalid data. Value may be lost or corrupted for node '" + name + "'."); return(GetBaseFormatter(context.Config.SerializationPolicy).Deserialize(reader)); } else { context.Config.DebugContext.LogWarning("Can't cast serialized type " + serializedType.Name + " into expected type " + expectedType.Name + ". Value lost for node '" + name + "'."); return(default(T)); } } else if (AllowDeserializeInvalidDataForT || reader.Context.Config.AllowDeserializeInvalidData) { context.Config.DebugContext.LogWarning("Expected complex struct value " + expectedType.Name + " but the serialized type could not be resolved. Attempting to deserialize with possibly invalid data. Value may be lost or corrupted for node '" + name + "'."); return(GetBaseFormatter(context.Config.SerializationPolicy).Deserialize(reader)); } else { context.Config.DebugContext.LogWarning("Expected complex struct value " + expectedType.Name + " but the serialized type could not be resolved. Value lost for node '" + name + "'."); return(default(T)); } } else { return(GetBaseFormatter(context.Config.SerializationPolicy).Deserialize(reader)); } } else { context.Config.DebugContext.LogError("Failed to enter node '" + name + "'."); return(default(T)); } } catch (SerializationAbortException ex) { exitNode = false; throw ex; } catch (Exception ex) { context.Config.DebugContext.LogException(ex); return(default(T)); } finally { if (exitNode) { reader.ExitNode(); } } } else { switch (entry) { case EntryType.Null: { reader.ReadNull(); return(default(T)); } case EntryType.ExternalReferenceByIndex: { int index; reader.ReadExternalReference(out index); object value = context.GetExternalObject(index); try { return((T)value); } catch (InvalidCastException) { context.Config.DebugContext.LogWarning("Can't cast external reference type " + value.GetType().Name + " into expected type " + TypeOf_T.Name + ". Value lost for node '" + name + "'."); return(default(T)); } } case EntryType.ExternalReferenceByGuid: { Guid guid; reader.ReadExternalReference(out guid); object value = context.GetExternalObject(guid); try { return((T)value); } catch (InvalidCastException) { context.Config.DebugContext.LogWarning("Can't cast external reference type " + value.GetType().Name + " into expected type " + TypeOf_T.Name + ". Value lost for node '" + name + "'."); return(default(T)); } } case EntryType.ExternalReferenceByString: { string id; reader.ReadExternalReference(out id); object value = context.GetExternalObject(id); try { return((T)value); } catch (InvalidCastException) { context.Config.DebugContext.LogWarning("Can't cast external reference type " + value.GetType().Name + " into expected type " + TypeOf_T.Name + ". Value lost for node '" + name + "'."); return(default(T)); } } case EntryType.InternalReference: { int id; reader.ReadInternalReference(out id); object value = context.GetInternalReference(id); try { return((T)value); } catch (InvalidCastException) { context.Config.DebugContext.LogWarning("Can't cast internal reference type " + value.GetType().Name + " into expected type " + TypeOf_T.Name + ". Value lost for node '" + name + "'."); return(default(T)); } } case EntryType.StartOfNode: { try { Type expectedType = TypeOf_T; Type serializedType; int id; if (reader.EnterNode(out serializedType)) { id = reader.CurrentNodeId; T result; if (serializedType != null && expectedType != serializedType) // We have type metadata different from the expected type { bool success = false; var isPrimitive = FormatterUtilities.IsPrimitiveType(serializedType); bool assignableCast; if (ComplexTypeMayBeBoxedValueType && isPrimitive) { // It's a boxed primitive type, so simply read that straight and register success var serializer = Serializer.Get(serializedType); result = (T)serializer.ReadValueWeak(reader); success = true; } else if ((assignableCast = expectedType.IsAssignableFrom(serializedType)) || serializedType.HasCastDefined(expectedType, false)) { try { object value; if (isPrimitive) { var serializer = Serializer.Get(serializedType); value = serializer.ReadValueWeak(reader); } else { var alternateFormatter = FormatterLocator.GetFormatter(serializedType, context.Config.SerializationPolicy); value = alternateFormatter.Deserialize(reader); } if (assignableCast) { result = (T)value; } else { var castMethod = serializedType.GetCastMethodDelegate(expectedType); if (castMethod != null) { result = (T)castMethod(value); } else { // Let's just give it a go anyways result = (T)value; } } success = true; } catch (SerializationAbortException ex) { exitNode = false; throw ex; } catch (InvalidCastException) { success = false; result = default(T); } } else if (!ComplexTypeIsAbstract && (AllowDeserializeInvalidDataForT || reader.Context.Config.AllowDeserializeInvalidData)) { // We will try to deserialize an instance of T with the invalid data. context.Config.DebugContext.LogWarning("Can't cast serialized type " + serializedType.Name + " into expected type " + expectedType.Name + ". Attempting to deserialize with invalid data. Value may be lost or corrupted for node '" + name + "'."); result = GetBaseFormatter(context.Config.SerializationPolicy).Deserialize(reader); success = true; } else { // We couldn't cast or use the type, but we still have to deserialize it and register // the reference so the reference isn't lost if it is referred to further down // the data stream. var alternateFormatter = FormatterLocator.GetFormatter(serializedType, context.Config.SerializationPolicy); object value = alternateFormatter.Deserialize(reader); if (id >= 0) { context.RegisterInternalReference(id, value); } result = default(T); } if (!success) { // We can't use this context.Config.DebugContext.LogWarning("Can't cast serialized type " + serializedType.Name + " into expected type " + expectedType.Name + ". Value lost for node '" + name + "'."); result = default(T); } } else if (ComplexTypeIsAbstract) { result = default(T); } else { result = GetBaseFormatter(context.Config.SerializationPolicy).Deserialize(reader); } if (id >= 0) { context.RegisterInternalReference(id, result); } return(result); } else { context.Config.DebugContext.LogError("Failed to enter node '" + name + "'."); return(default(T)); } } catch (SerializationAbortException ex) { exitNode = false; throw ex; } catch (Exception ex) { context.Config.DebugContext.LogException(ex); return(default(T)); } finally { if (exitNode) { reader.ExitNode(); } } } // // The below cases are for when we expect an object, but have // serialized a straight primitive type. In such cases, we can // often box the primitive type as an object. // // Sadly, the exact primitive type might be lost in case of // integer and floating points numbers, as we don't know what // type to expect. // // To be safe, we read and box the most precise type available. // case EntryType.Boolean: { if (!ComplexTypeMayBeBoxedValueType) { goto default; } bool value; reader.ReadBoolean(out value); return((T)(object)value); } case EntryType.FloatingPoint: { if (!ComplexTypeMayBeBoxedValueType) { goto default; } double value; reader.ReadDouble(out value); return((T)(object)value); } case EntryType.Integer: { if (!ComplexTypeMayBeBoxedValueType) { goto default; } long value; reader.ReadInt64(out value); return((T)(object)value); } case EntryType.String: { if (!ComplexTypeMayBeBoxedValueType) { goto default; } string value; reader.ReadString(out value); return((T)(object)value); } case EntryType.Guid: { if (!ComplexTypeMayBeBoxedValueType) { goto default; } Guid value; reader.ReadGuid(out value); return((T)(object)value); } default: // Lost value somehow context.Config.DebugContext.LogWarning("Unexpected entry of type " + entry.ToString() + ", when a reference or node start was expected. A value has been lost."); reader.SkipEntry(); return(default(T)); } } }
private static IFormatter CreateFormatter(Type type, ISerializationPolicy policy) { if (FormatterUtilities.IsPrimitiveType(type)) { throw new ArgumentException("Cannot create formatters for a primitive type like " + type.Name); } // First call formatter locators before checking for registered formatters for (int i = 0; i < FormatterLocators.Count; i++) { try { IFormatter result; if (FormatterLocators[i].LocatorInstance.TryGetFormatter(type, FormatterLocationStep.BeforeRegisteredFormatters, policy, out result)) { return(result); } } catch (TargetInvocationException ex) { throw ex; } catch (TypeInitializationException ex) { throw ex; } #pragma warning disable CS0618 // Type or member is obsolete catch (ExecutionEngineException ex) #pragma warning restore CS0618 // Type or member is obsolete { throw ex; } catch (Exception ex) { Debug.LogException(new Exception("Exception was thrown while calling FormatterLocator " + FormatterLocators[i].GetType().FullName + ".", ex)); } } // Then check for valid registered formatters for (int i = 0; i < FormatterInfos.Count; i++) { var info = FormatterInfos[i]; Type formatterType = null; if (type == info.TargetType) { formatterType = info.FormatterType; } else if (info.FormatterType.IsGenericType && info.TargetType.IsGenericParameter) { Type[] inferredArgs; if (info.FormatterType.TryInferGenericParameters(out inferredArgs, type)) { formatterType = info.FormatterType.GetGenericTypeDefinition().MakeGenericType(inferredArgs); } } else if (type.IsGenericType && info.FormatterType.IsGenericType && info.TargetType.IsGenericType && type.GetGenericTypeDefinition() == info.TargetType.GetGenericTypeDefinition()) { Type[] args = type.GetGenericArguments(); if (info.FormatterType.AreGenericConstraintsSatisfiedBy(args)) { formatterType = info.FormatterType.GetGenericTypeDefinition().MakeGenericType(args); } } if (formatterType != null) { var instance = GetFormatterInstance(formatterType); if (instance == null) { continue; } if (info.AskIfCanFormatTypes && !((IAskIfCanFormatTypes)instance).CanFormatType(type)) { continue; } return(instance); } } // Then call formatter locators after checking for registered formatters for (int i = 0; i < FormatterLocators.Count; i++) { try { IFormatter result; if (FormatterLocators[i].LocatorInstance.TryGetFormatter(type, FormatterLocationStep.AfterRegisteredFormatters, policy, out result)) { return(result); } } catch (TargetInvocationException ex) { throw ex; } catch (TypeInitializationException ex) { throw ex; } #pragma warning disable CS0618 // Type or member is obsolete catch (ExecutionEngineException ex) #pragma warning restore CS0618 // Type or member is obsolete { throw ex; } catch (Exception ex) { Debug.LogException(new Exception("Exception was thrown while calling FormatterLocator " + FormatterLocators[i].GetType().FullName + ".", ex)); } } // If we can, emit a formatter to handle serialization of this object { if (EmitUtilities.CanEmit) { var result = FormatterEmitter.GetEmittedFormatter(type, policy); if (result != null) { return(result); } } } if (EmitUtilities.CanEmit) { Debug.LogWarning("Fallback to reflection for type " + type.Name + " when emit is possible on this platform."); } // Finally, we fall back to a reflection-based formatter if nothing else has been found return((IFormatter)Activator.CreateInstance(typeof(ReflectionFormatter <>).MakeGenericType(type))); }
/// <summary> /// Writes a value of type <see cref="T" />. /// </summary> /// <param name="name">The name of the value to write.</param> /// <param name="value">The value to write.</param> /// <param name="writer">The writer to use.</param> public override void WriteValue(string name, T value, IDataWriter writer) { var context = writer.Context; var policy = context.Config.SerializationPolicy; if (policy.AllowNonSerializableTypes == false && TypeOf_T.IsSerializable == false) { context.Config.DebugContext.LogError("The type " + TypeOf_T.Name + " is not marked as serializable."); return; } FireOnSerializedType(); if (ComplexTypeIsValueType) { bool endNode = true; try { writer.BeginStructNode(name, TypeOf_T); GetBaseFormatter(policy).Serialize(value, writer); } catch (SerializationAbortException ex) { endNode = false; throw ex; } finally { if (endNode) { writer.EndNode(name); } } } else { int id; int index; string strId; Guid guid; bool endNode = true; if (object.ReferenceEquals(value, null)) { writer.WriteNull(name); } else if (context.TryRegisterExternalReference(value, out index)) { writer.WriteExternalReference(name, index); } else if (context.TryRegisterExternalReference(value, out guid)) { writer.WriteExternalReference(name, guid); } else if (context.TryRegisterExternalReference(value, out strId)) { writer.WriteExternalReference(name, strId); } else if (context.TryRegisterInternalReference(value, out id)) { Type type = value.GetType(); // Get type of actual stored object if (ComplexTypeMayBeBoxedValueType && FormatterUtilities.IsPrimitiveType(type)) // It's a boxed primitive type { try { writer.BeginReferenceNode(name, type, id); var serializer = Serializer.Get(type); serializer.WriteValueWeak(value, writer); } catch (SerializationAbortException ex) { endNode = false; throw ex; } finally { if (endNode) { writer.EndNode(name); } } } else { IFormatter formatter; if (object.ReferenceEquals(type, TypeOf_T)) { formatter = GetBaseFormatter(policy); } else { formatter = FormatterLocator.GetFormatter(type, policy); } try { writer.BeginReferenceNode(name, type, id); formatter.Serialize(value, writer); } catch (SerializationAbortException ex) { endNode = false; throw ex; } finally { if (endNode) { writer.EndNode(name); } } } } else { writer.WriteInternalReference(name, id); } } }
private static Serializer Create(Type type) { try { Type resultType = null; if (type.IsEnum) { resultType = typeof(EnumSerializer <>).MakeGenericType(type); } else if (FormatterUtilities.IsPrimitiveType(type)) { try { resultType = PrimitiveReaderWriterTypes[type]; } catch (KeyNotFoundException) { UnityEngine.Debug.LogError("Failed to find primitive serializer for " + type.Name); } } else { resultType = typeof(ComplexTypeSerializer <>).MakeGenericType(type); } return((Serializer)Activator.CreateInstance(resultType)); } // System.ExecutionEngineException is marked obsolete in .NET 4.6 // That's all very good for .NET, but Unity still uses it! #pragma warning disable 618 catch (TargetInvocationException ex) { if (ex.GetBaseException() is ExecutionEngineException) { LogAOTError(type, ex.GetBaseException() as ExecutionEngineException); return(null); } else { throw ex; } } catch (TypeInitializationException ex) { if (ex.GetBaseException() is ExecutionEngineException) { LogAOTError(type, ex.GetBaseException() as ExecutionEngineException); return(null); } else { throw ex; } } catch (ExecutionEngineException ex) { LogAOTError(type, ex); return(null); } }
internal static List <IFormatter> GetAllCompatiblePredefinedFormatters(Type type, ISerializationPolicy policy) { if (FormatterUtilities.IsPrimitiveType(type)) { throw new ArgumentException("Cannot create formatters for a primitive type like " + type.Name); } List <IFormatter> formatters = new List <IFormatter>(); // First call formatter locators before checking for registered formatters for (int i = 0; i < FormatterLocators.Count; i++) { try { IFormatter result; if (FormatterLocators[i].LocatorInstance.TryGetFormatter(type, FormatterLocationStep.BeforeRegisteredFormatters, policy, out result)) { formatters.Add(result); } } catch (TargetInvocationException ex) { throw ex; } catch (TypeInitializationException ex) { throw ex; } #pragma warning disable CS0618 // Type or member is obsolete catch (ExecutionEngineException ex) #pragma warning restore CS0618 // Type or member is obsolete { throw ex; } catch (Exception ex) { Debug.LogException(new Exception("Exception was thrown while calling FormatterLocator " + FormatterLocators[i].GetType().FullName + ".", ex)); } } // Then check for valid registered formatters for (int i = 0; i < FormatterInfos.Count; i++) { var info = FormatterInfos[i]; Type formatterType = null; if (type == info.TargetType) { formatterType = info.FormatterType; } else if (info.FormatterType.IsGenericType && info.TargetType.IsGenericParameter) { Type[] inferredArgs; if (info.FormatterType.TryInferGenericParameters(out inferredArgs, type)) { formatterType = info.FormatterType.GetGenericTypeDefinition().MakeGenericType(inferredArgs); } } else if (type.IsGenericType && info.FormatterType.IsGenericType && info.TargetType.IsGenericType && type.GetGenericTypeDefinition() == info.TargetType.GetGenericTypeDefinition()) { Type[] args = type.GetGenericArguments(); if (info.FormatterType.AreGenericConstraintsSatisfiedBy(args)) { formatterType = info.FormatterType.GetGenericTypeDefinition().MakeGenericType(args); } } if (formatterType != null) { var instance = GetFormatterInstance(formatterType); if (instance == null) { continue; } if (info.AskIfCanFormatTypes && !((IAskIfCanFormatTypes)instance).CanFormatType(type)) { continue; } formatters.Add(instance); } } // Then call formatter locators after checking for registered formatters for (int i = 0; i < FormatterLocators.Count; i++) { try { IFormatter result; if (FormatterLocators[i].LocatorInstance.TryGetFormatter(type, FormatterLocationStep.AfterRegisteredFormatters, policy, out result)) { formatters.Add(result); } } catch (TargetInvocationException ex) { throw ex; } catch (TypeInitializationException ex) { throw ex; } #pragma warning disable CS0618 // Type or member is obsolete catch (ExecutionEngineException ex) #pragma warning restore CS0618 // Type or member is obsolete { throw ex; } catch (Exception ex) { Debug.LogException(new Exception("Exception was thrown while calling FormatterLocator " + FormatterLocators[i].GetType().FullName + ".", ex)); } } formatters.Add((IFormatter)Activator.CreateInstance(typeof(ReflectionFormatter <>).MakeGenericType(type))); return(formatters); }
private static void ReplaceAllReferencesInGraph(object graph, object oldReference, object newReference, HashSet <object> processedReferences = null) { if (processedReferences == null) { processedReferences = new HashSet <object>(ReferenceEqualityComparer <object> .Default); } processedReferences.Add(graph); if (graph.GetType().IsArray) { Array array = (Array)graph; for (int i = 0; i < array.Length; i++) { var value = array.GetValue(i); if (object.ReferenceEquals(value, null)) { continue; } if (object.ReferenceEquals(value, oldReference)) { array.SetValue(newReference, i); value = newReference; } if (!processedReferences.Contains(value)) { ReplaceAllReferencesInGraph(value, oldReference, newReference, processedReferences); } } } else { var members = FormatterUtilities.GetSerializableMembers(graph.GetType(), SerializationPolicies.Everything); for (int i = 0; i < members.Length; i++) { FieldInfo field = (FieldInfo)members[i]; if (field.FieldType.IsPrimitive || field.FieldType == typeof(SerializationData) || field.FieldType == typeof(string)) { continue; } object value = field.GetValue(graph); if (object.ReferenceEquals(value, null)) { continue; } Type valueType = value.GetType(); if (valueType.IsPrimitive || valueType == typeof(SerializationData) || valueType == typeof(string)) { continue; } if (object.ReferenceEquals(value, oldReference)) { field.SetValue(graph, newReference); value = newReference; } if (!processedReferences.Contains(value)) { ReplaceAllReferencesInGraph(value, oldReference, newReference, processedReferences); } } } }
/// <summary> /// Emits a formatter for a given type into a given module builder, using a given serialization policy to determine which members to serialize. /// </summary> /// <param name="formattedType">Type to create a formatter for.</param> /// <param name="moduleBuilder">The module builder to emit a formatter into.</param> /// <param name="policy">The serialization policy to use for creating the formatter.</param> /// <returns>The fully constructed, emitted formatter type.</returns> public static Type EmitAOTFormatter(Type formattedType, ModuleBuilder moduleBuilder, ISerializationPolicy policy) { Dictionary <string, MemberInfo> serializableMembers = FormatterUtilities.GetSerializableMembersMap(formattedType, policy); string formatterName = moduleBuilder.Name + "." + formattedType.GetCompilableNiceFullName() + "__AOTFormatter"; string formatterHelperName = moduleBuilder.Name + "." + formattedType.GetCompilableNiceFullName() + "__FormatterHelper"; if (serializableMembers.Count == 0) { return(moduleBuilder.DefineType( formatterName, TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Class, typeof(EmptyAOTEmittedFormatter <>).MakeGenericType(formattedType) ).CreateType()); } Dictionary <Type, MethodInfo> serializerReadMethods; Dictionary <Type, MethodInfo> serializerWriteMethods; Dictionary <Type, FieldBuilder> serializerFields; FieldBuilder dictField; Dictionary <MemberInfo, List <string> > memberNames; BuildHelperType( moduleBuilder, formatterHelperName, formattedType, serializableMembers, out serializerReadMethods, out serializerWriteMethods, out serializerFields, out dictField, out memberNames ); TypeBuilder formatterType = moduleBuilder.DefineType( formatterName, TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Class, typeof(AOTEmittedFormatter <>).MakeGenericType(formattedType) ); // Read { MethodInfo readBaseMethod = formatterType.BaseType.GetMethod("ReadDataEntry", Flags.InstanceAnyVisibility); MethodBuilder readMethod = formatterType.DefineMethod( readBaseMethod.Name, MethodAttributes.Family | MethodAttributes.Virtual, readBaseMethod.ReturnType, readBaseMethod.GetParameters().Select(n => n.ParameterType).ToArray() ); readBaseMethod.GetParameters().ForEach(n => readMethod.DefineParameter(n.Position, n.Attributes, n.Name)); EmitReadMethodContents(readMethod.GetILGenerator(), formattedType, dictField, serializerFields, memberNames, serializerReadMethods); } // Write { MethodInfo writeBaseMethod = formatterType.BaseType.GetMethod("WriteDataEntries", Flags.InstanceAnyVisibility); MethodBuilder dynamicWriteMethod = formatterType.DefineMethod( writeBaseMethod.Name, MethodAttributes.Family | MethodAttributes.Virtual, writeBaseMethod.ReturnType, writeBaseMethod.GetParameters().Select(n => n.ParameterType).ToArray() ); writeBaseMethod.GetParameters().ForEach(n => dynamicWriteMethod.DefineParameter(n.Position + 1, n.Attributes, n.Name)); EmitWriteMethodContents(dynamicWriteMethod.GetILGenerator(), formattedType, serializerFields, memberNames, serializerWriteMethods); } var result = formatterType.CreateType(); // Register the formatter on the assembly ((AssemblyBuilder)moduleBuilder.Assembly).SetCustomAttribute(new CustomAttributeBuilder(typeof(RegisterFormatterAttribute).GetConstructor(new Type[] { typeof(Type), typeof(int) }), new object[] { formatterType, -1 })); return(result); }
/// <summary> /// Skips the next entry value, unless it is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>. If the next entry value is an <see cref="EntryType.StartOfNode"/> or an <see cref="EntryType.StartOfArray"/>, all of its contents will be processed, deserialized and registered in the deserialization context, so that internal reference values are not lost to entries further down the stream. /// </summary> public virtual void SkipEntry() { var peekedEntry = this.PeekEntry(); if (peekedEntry == EntryType.StartOfNode) { Type type; bool exitNode = true; this.EnterNode(out type); try { if (type != null) { // We have the necessary metadata to read this type, and register all of its reference values (perhaps including itself) in the serialization context // Sadly, we have no choice but to risk boxing of structs here // Luckily, this is a rare case if (FormatterUtilities.IsPrimitiveType(type)) { // It is a boxed primitive type; we read the value and register it var serializer = Serializer.Get(type); object value = serializer.ReadValueWeak(this); if (this.CurrentNodeId >= 0) { this.Context.RegisterInternalReference(this.CurrentNodeId, value); } } else { var formatter = FormatterLocator.GetFormatter(type, this.Context.Config.SerializationPolicy); object value = formatter.Deserialize(this); if (this.CurrentNodeId >= 0) { this.Context.RegisterInternalReference(this.CurrentNodeId, value); } } } else { // We have no metadata, and reference values might be lost // We must read until a node on the same level terminates while (true) { peekedEntry = this.PeekEntry(); if (peekedEntry == EntryType.EndOfStream) { break; } else if (peekedEntry == EntryType.EndOfNode) { break; } else if (peekedEntry == EntryType.EndOfArray) { this.ReadToNextEntry(); // Consume end of arrays that we can potentially get stuck on } else { this.SkipEntry(); } } } } catch (SerializationAbortException ex) { exitNode = false; throw ex; } finally { if (exitNode) { this.ExitNode(); } } } else if (peekedEntry == EntryType.StartOfArray) { // We must read until an array on the same level terminates this.ReadToNextEntry(); // Consume start of array while (true) { peekedEntry = this.PeekEntry(); if (peekedEntry == EntryType.EndOfStream) { break; } else if (peekedEntry == EntryType.EndOfArray) { this.ReadToNextEntry(); // Consume end of array and break break; } else if (peekedEntry == EntryType.EndOfNode) { this.ReadToNextEntry(); // Consume end of nodes that we can potentially get stuck on } else { this.SkipEntry(); } } } else if (peekedEntry != EntryType.EndOfArray && peekedEntry != EntryType.EndOfNode) // We can't skip end of arrays and end of nodes { this.ReadToNextEntry(); // We can just skip a single value entry } }
private static Type BuildHelperType( ModuleBuilder moduleBuilder, string helperTypeName, Type formattedType, Dictionary <string, MemberInfo> serializableMembers, out Dictionary <Type, MethodInfo> serializerReadMethods, out Dictionary <Type, MethodInfo> serializerWriteMethods, out Dictionary <Type, FieldBuilder> serializerFields, out FieldBuilder dictField, out Dictionary <MemberInfo, List <string> > memberNames) { TypeBuilder helperTypeBuilder = moduleBuilder.DefineType(helperTypeName, TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Class); memberNames = new Dictionary <MemberInfo, List <string> >(); foreach (var entry in serializableMembers) { List <string> list; if (memberNames.TryGetValue(entry.Value, out list) == false) { list = new List <string>(); memberNames.Add(entry.Value, list); } list.Add(entry.Key); } dictField = helperTypeBuilder.DefineField("SwitchLookup", typeof(Dictionary <string, int>), FieldAttributes.Public | FieldAttributes.Static | FieldAttributes.InitOnly); List <Type> neededSerializers = memberNames.Keys.Select(n => FormatterUtilities.GetContainedType(n)).Distinct().ToList(); serializerReadMethods = new Dictionary <Type, MethodInfo>(neededSerializers.Count); serializerWriteMethods = new Dictionary <Type, MethodInfo>(neededSerializers.Count); serializerFields = new Dictionary <Type, FieldBuilder>(neededSerializers.Count); foreach (var t in neededSerializers) { string name = t.GetCompilableNiceFullName() + "__Serializer"; int counter = 1; while (serializerFields.Values.Any(n => n.Name == name)) { counter++; name = t.GetCompilableNiceFullName() + "__Serializer" + counter; } Type serializerType = typeof(Serializer <>).MakeGenericType(t); serializerReadMethods.Add(t, serializerType.GetMethod("ReadValue", Flags.InstancePublicDeclaredOnly)); serializerWriteMethods.Add(t, serializerType.GetMethod("WriteValue", Flags.InstancePublicDeclaredOnly, null, new[] { typeof(string), t, typeof(IDataWriter) }, null)); serializerFields.Add(t, helperTypeBuilder.DefineField(name, serializerType, FieldAttributes.Public | FieldAttributes.Static | FieldAttributes.InitOnly)); } //FieldBuilder readMethodFieldBuilder = helperTypeBuilder.DefineField("ReadMethod", readDelegateType, FieldAttributes.Public | FieldAttributes.Static | FieldAttributes.InitOnly); //FieldBuilder writeMethodFieldBuilder = helperTypeBuilder.DefineField("WriteMethod", writeDelegateType, FieldAttributes.Public | FieldAttributes.Static | FieldAttributes.InitOnly); // We generate a static constructor for our formatter helper type that initializes our switch lookup dictionary and our needed Serializer references { var addMethod = typeof(Dictionary <string, int>).GetMethod("Add", Flags.InstancePublic); var dictionaryConstructor = typeof(Dictionary <string, int>).GetConstructor(Type.EmptyTypes); var serializerGetMethod = typeof(Serializer).GetMethod("Get", Flags.StaticPublic, null, new[] { typeof(Type) }, null); var typeOfMethod = typeof(Type).GetMethod("GetTypeFromHandle", Flags.StaticPublic, null, new Type[] { typeof(RuntimeTypeHandle) }, null); ConstructorBuilder staticConstructor = helperTypeBuilder.DefineTypeInitializer(); ILGenerator gen = staticConstructor.GetILGenerator(); gen.Emit(OpCodes.Newobj, dictionaryConstructor); // Create new dictionary int count = 0; foreach (var entry in memberNames) { foreach (var name in entry.Value) { gen.Emit(OpCodes.Dup); // Load duplicate dictionary value gen.Emit(OpCodes.Ldstr, name); // Load entry name gen.Emit(OpCodes.Ldc_I4, count); // Load entry index gen.Emit(OpCodes.Call, addMethod); // Call dictionary add } count++; } gen.Emit(OpCodes.Stsfld, dictField); // Set static dictionary field to dictionary value foreach (var entry in serializerFields) { gen.Emit(OpCodes.Ldtoken, entry.Key); // Load type token gen.Emit(OpCodes.Call, typeOfMethod); // Call typeof method (this pushes a type value onto the stack) gen.Emit(OpCodes.Call, serializerGetMethod); // Call Serializer.Get(Type type) method gen.Emit(OpCodes.Stsfld, entry.Value); // Set static serializer field to result of get method } gen.Emit(OpCodes.Ret); // Return } // Now we need to actually create the serializer container type so we can generate the dynamic methods below without getting TypeLoadExceptions up the wazoo return(helperTypeBuilder.CreateType()); }
private static void EmitReadMethodContents( ILGenerator gen, Type formattedType, FieldInfo dictField, Dictionary <Type, FieldBuilder> serializerFields, Dictionary <MemberInfo, List <string> > memberNames, Dictionary <Type, MethodInfo> serializerReadMethods) { MethodInfo skipMethod = typeof(IDataReader).GetMethod("SkipEntry", Flags.InstancePublic); MethodInfo tryGetValueMethod = typeof(Dictionary <string, int>).GetMethod("TryGetValue", Flags.InstancePublic); //methodBuilder.DefineParameter(5, ParameterAttributes.None, "switchLookup"); LocalBuilder lookupResult = gen.DeclareLocal(typeof(int)); Label defaultLabel = gen.DefineLabel(); Label switchLabel = gen.DefineLabel(); Label endLabel = gen.DefineLabel(); Label[] switchLabels = memberNames.Select(n => gen.DefineLabel()).ToArray(); gen.Emit(OpCodes.Ldarg_1); // Load entryName string gen.Emit(OpCodes.Ldnull); // Load null gen.Emit(OpCodes.Ceq); // Equality check gen.Emit(OpCodes.Brtrue, defaultLabel); // If entryName is null, go to default case //gen.Emit(OpCodes.Ldarg, (short)4); // Load lookup dictionary argument (OLD CODE) gen.Emit(OpCodes.Ldsfld, dictField); // Load lookup dictionary from static field on helper type gen.Emit(OpCodes.Ldarg_1); // Load entryName string gen.Emit(OpCodes.Ldloca, (short)lookupResult.LocalIndex); // Load address of lookupResult gen.Emit(OpCodes.Callvirt, tryGetValueMethod); // Call TryGetValue on the dictionary gen.Emit(OpCodes.Brtrue, switchLabel); // If TryGetValue returned true, go to the switch case gen.Emit(OpCodes.Br, defaultLabel); // Else, go to the default case gen.MarkLabel(switchLabel); // Switch starts here gen.Emit(OpCodes.Ldloc, lookupResult); // Load lookupResult gen.Emit(OpCodes.Switch, switchLabels); // Perform switch on switchLabels int count = 0; foreach (var member in memberNames.Keys) { var memberType = FormatterUtilities.GetContainedType(member); var propInfo = member as PropertyInfo; var fieldInfo = member as FieldInfo; gen.MarkLabel(switchLabels[count]); // Switch case for [count] starts here // Now we load the instance that we have to set the value on gen.Emit(OpCodes.Ldarg_0); // Load value reference if (formattedType.IsValueType == false) { gen.Emit(OpCodes.Ldind_Ref); // Indirectly load value of reference } // Now we deserialize the value itself gen.Emit(OpCodes.Ldsfld, serializerFields[memberType]); // Load serializer from serializer container type gen.Emit(OpCodes.Ldarg, (short)3); // Load reader argument gen.Emit(OpCodes.Callvirt, serializerReadMethods[memberType]); // Call Serializer.ReadValue(IDataReader reader) // The stack now contains the formatted instance and the deserialized value to set the member to // Now we set the value if (fieldInfo != null) { gen.Emit(OpCodes.Stfld, fieldInfo.DeAliasField()); // Set field } else if (propInfo != null) { gen.Emit(OpCodes.Callvirt, propInfo.DeAliasProperty().GetSetMethod(true)); // Call property setter } else { throw new NotImplementedException(); } gen.Emit(OpCodes.Br, endLabel); // Jump to end of method count++; } gen.MarkLabel(defaultLabel); // Default case starts here gen.Emit(OpCodes.Ldarg, (short)3); // Load reader argument gen.Emit(OpCodes.Callvirt, skipMethod); // Call IDataReader.SkipEntry gen.MarkLabel(endLabel); // Method end starts here gen.Emit(OpCodes.Ret); // Return method }