// ObjectReader.cs // // Author: // Lluis Sanchez Gual (lluis@ideary.com) // Patrik Torstensson // // (C) 2003 Lluis Sanchez Gual // // Copyright (C) 2004 Novell, Inc (http://www.novell.com) // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System; using System.Runtime.Serialization; using System.IO; using System.Collections; using System.Reflection; using System.Runtime.Remoting.Messaging; using System.Globalization; namespace System.Runtime.Serialization.Formatters.Binary { internal class ObjectReader { BinaryFormatter _formatter; ISurrogateSelector _surrogateSelector; StreamingContext _context; SerializationBinder _binder; #if NET_1_1 TypeFilterLevel _filterLevel; #endif ObjectManager _manager; Hashtable _registeredAssemblies = new Hashtable(); Hashtable _typeMetadataCache = new Hashtable(); object _lastObject = null; long _lastObjectID = 0; long _rootObjectID = 0; class TypeMetadata { public Type Type; public Type[] MemberTypes; public string[] MemberNames; public MemberInfo[] MemberInfos; public int FieldCount; public bool NeedsSerializationInfo; } class ArrayNullFiller { public ArrayNullFiller(int count) { NullCount = count; } public int NullCount; } public ObjectReader (BinaryFormatter formatter) { _formatter = formatter; _surrogateSelector = formatter.SurrogateSelector; _context = formatter.Context; _binder = formatter.Binder; _manager = new ObjectManager (_surrogateSelector, _context); #if NET_1_1 _filterLevel = formatter.FilterLevel; #endif } public void ReadObjectGraph (BinaryReader reader, bool readHeaders, out object result, out Header[] headers) { headers = null; // Reads the objects. The first object in the stream is the // root object. while (ReadNextObject (reader)) { if (readHeaders && (headers == null)) headers = (Header[])CurrentObject; else if (_rootObjectID == 0) _rootObjectID = _lastObjectID; } result = _manager.GetObject (_rootObjectID); } public bool ReadNextObject (BinaryReader reader) { BinaryElement element = (BinaryElement)reader.ReadByte (); if (element == BinaryElement.End) { _manager.DoFixups(); _manager.RaiseDeserializationEvent(); return false; } SerializationInfo info; long objectId; ReadObject (element, reader, out objectId, out _lastObject, out info); if (objectId != 0) { RegisterObject (objectId, _lastObject, info, 0, null, null); _lastObjectID = objectId; } return true; } public object CurrentObject { get { return _lastObject; } } // Reads an object from the stream. The object is registered in the ObjectManager. // The result can be either the object instance // or the id of the object (when what is found in the stream is an object reference). // If an object instance is read, the objectId is set to 0. private void ReadObject (BinaryElement element, BinaryReader reader, out long objectId, out object value, out SerializationInfo info) { switch (element) { case BinaryElement.RefTypeObject: ReadRefTypeObjectInstance (reader, out objectId, out value, out info); break; case BinaryElement.RuntimeObject: ReadObjectInstance (reader, true, out objectId, out value, out info); break; case BinaryElement.ExternalObject: ReadObjectInstance (reader, false, out objectId, out value, out info); break; case BinaryElement.String: info = null; ReadStringIntance (reader, out objectId, out value); break; case BinaryElement.GenericArray: info = null; ReadGenericArray (reader, out objectId, out value); break; case BinaryElement.BoxedPrimitiveTypeValue: value = ReadBoxedPrimitiveTypeValue (reader); objectId = 0; info = null; break; case BinaryElement.NullValue: value = null; objectId = 0; info = null; break; case BinaryElement.Assembly: ReadAssembly (reader); ReadObject ((BinaryElement)reader.ReadByte (), reader, out objectId, out value, out info); break; case BinaryElement.ArrayFiller8b: value = new ArrayNullFiller(reader.ReadByte()); objectId = 0; info = null; break; case BinaryElement.ArrayFiller32b: value = new ArrayNullFiller(reader.ReadInt32()); objectId = 0; info = null; break; case BinaryElement.ArrayOfPrimitiveType: ReadArrayOfPrimitiveType (reader, out objectId, out value); info = null; break; case BinaryElement.ArrayOfObject: ReadArrayOfObject (reader, out objectId, out value); info = null; break; case BinaryElement.ArrayOfString: ReadArrayOfString (reader, out objectId, out value); info = null; break; default: throw new SerializationException ("Unexpected binary element: " + (int)element); } } private void ReadAssembly (BinaryReader reader) { long id = (long) reader.ReadUInt32 (); string assemblyName = reader.ReadString (); _registeredAssemblies [id] = assemblyName; } private void ReadObjectInstance (BinaryReader reader, bool isRuntimeObject, out long objectId, out object value, out SerializationInfo info) { objectId = (long) reader.ReadUInt32 (); TypeMetadata metadata = ReadTypeMetadata (reader, isRuntimeObject); ReadObjectContent (reader, metadata, objectId, out value, out info); } private void ReadRefTypeObjectInstance (BinaryReader reader, out long objectId, out object value, out SerializationInfo info) { objectId = (long) reader.ReadUInt32 (); long refTypeObjectId = (long) reader.ReadUInt32 (); // Gets the type of the referred object and its metadata object refObj = _manager.GetObject (refTypeObjectId); if (refObj == null) throw new SerializationException ("Invalid binary format"); TypeMetadata metadata = (TypeMetadata)_typeMetadataCache [refObj.GetType()]; ReadObjectContent (reader, metadata, objectId, out value, out info); } private void ReadObjectContent (BinaryReader reader, TypeMetadata metadata, long objectId, out object objectInstance, out SerializationInfo info) { #if NET_1_1 if (_filterLevel == TypeFilterLevel.Low) objectInstance = FormatterServices.GetSafeUninitializedObject (metadata.Type); else #endif objectInstance = FormatterServices.GetUninitializedObject (metadata.Type); info = metadata.NeedsSerializationInfo ? new SerializationInfo(metadata.Type, new FormatterConverter()) : null; if (metadata.MemberNames != null) for (int n=0; n= 0; dim--) indices[dim] = array.GetLowerBound (dim); bool end = false; while (!end) { ReadValue (reader, array, objectId, null, elementType, null, null, indices); for (int dim = array.Rank-1; dim >= 0; dim--) { indices[dim]++; if (indices[dim] > array.GetUpperBound (dim)) { if (dim > 0) { indices[dim] = array.GetLowerBound (dim); continue; // Increment the next dimension's index } end = true; // That was the last dimension. Finished. } break; } } val = array; } private object ReadBoxedPrimitiveTypeValue (BinaryReader reader) { Type type = ReadType (reader, TypeTag.PrimitiveType); return ReadPrimitiveTypeValue (reader, type); } private void ReadArrayOfPrimitiveType (BinaryReader reader, out long objectId, out object val) { objectId = (long) reader.ReadUInt32 (); int length = reader.ReadInt32 (); Type elementType = ReadType (reader, TypeTag.PrimitiveType); Array array = Array.CreateInstance (elementType, length); for (int n = 0; n < length; n++) array.SetValue (ReadPrimitiveTypeValue (reader, elementType), n); val = array; } private void ReadArrayOfObject (BinaryReader reader, out long objectId, out object array) { ReadSimpleArray (reader, typeof (object), out objectId, out array); } private void ReadArrayOfString (BinaryReader reader, out long objectId, out object array) { ReadSimpleArray (reader, typeof (string), out objectId, out array); } private void ReadSimpleArray (BinaryReader reader, Type elementType, out long objectId, out object val) { objectId = (long) reader.ReadUInt32 (); int length = reader.ReadInt32 (); int[] indices = new int[1]; Array array = Array.CreateInstance (elementType, length); for (int n = 0; n < length; n++) { indices[0] = n; ReadValue (reader, array, objectId, null, elementType, null, null, indices); n = indices[0]; } val = array; } private TypeMetadata ReadTypeMetadata (BinaryReader reader, bool isRuntimeObject) { TypeMetadata metadata = new TypeMetadata(); string className = reader.ReadString (); int fieldCount = reader.ReadInt32 (); Type[] types = new Type[fieldCount]; string[] names = new string[fieldCount]; TypeTag[] codes = new TypeTag[fieldCount]; for (int n=0; n 1) throw new SerializationException ("There are two public members named \"" + names[n] + "\" in the class hirearchy of " + metadata.Type.FullName); metadata.MemberInfos [n] = members[0]; } metadata.MemberNames = null; // Info now in MemberInfos } } // Registers the type's metadata so it can be reused later if // a RefTypeObject element is found if (!_typeMetadataCache.ContainsKey (metadata.Type)) _typeMetadataCache [metadata.Type] = metadata; return metadata; } private void ReadValue (BinaryReader reader, object parentObject, long parentObjectId, SerializationInfo info, Type valueType, string fieldName, MemberInfo memberInfo, int[] indices) { // Reads a value from the stream and assigns it to the member of an object object val; if (BinaryCommon.IsPrimitive (valueType)) { val = ReadPrimitiveTypeValue (reader, valueType); SetObjectValue (parentObject, fieldName, memberInfo, info, val, valueType, indices); return; } // Gets the object BinaryElement element = (BinaryElement)reader.ReadByte (); if (element == BinaryElement.ObjectReference) { // Just read the id of the referred object and record a fixup long childObjectId = (long) reader.ReadUInt32(); RecordFixup (parentObjectId, childObjectId, parentObject, info, fieldName, memberInfo, indices); return; } long objectId; SerializationInfo objectInfo; ReadObject (element, reader, out objectId, out val, out objectInfo); // There are two cases where the object cannot be assigned to the parent // and a fixup must be used: // 1) When what has been read is not an object, but an id of an object that // has not been read yet (an object reference). This is managed in the // previous block of code. // 2) When the read object is a value type object. Value type fields hold // copies of objects, not references. Thus, if the value object that // has been read has pending fixups, those fixups would be made to the // boxed copy in the ObjectManager, and not in the required object instance // First of all register the fixup, and then the object. ObjectManager is more // efficient if done in this order bool hasFixup = false; if (objectId != 0) { if (val.GetType().IsValueType) { RecordFixup (parentObjectId, objectId, parentObject, info, fieldName, memberInfo, indices); hasFixup = true; } // Register the value if (info == null && !(parentObject is Array)) RegisterObject (objectId, val, objectInfo, parentObjectId, memberInfo, null); else RegisterObject (objectId, val, objectInfo, parentObjectId, null, indices); } // Assign the value to the parent object, unless there is a fixup if (!hasFixup) SetObjectValue (parentObject, fieldName, memberInfo, info, val, valueType, indices); } private void SetObjectValue (object parentObject, string fieldName, MemberInfo memberInfo, SerializationInfo info, object value, Type valueType, int[] indices) { if (value is IObjectReference) value = ((IObjectReference)value).GetRealObject (_context); if (parentObject is Array) { if (value is ArrayNullFiller) { // It must be a single dimension array of objects. // Just increase the index. Elements are null by default. int count = ((ArrayNullFiller)value).NullCount; indices[0] += count - 1; } else ((Array)parentObject).SetValue (value, indices); } else if (info != null) { info.AddValue (fieldName, value, valueType); } else { if (memberInfo is FieldInfo) ((FieldInfo)memberInfo).SetValue (parentObject, value); else ((PropertyInfo)memberInfo).SetValue (parentObject, value, null); } } private void RecordFixup (long parentObjectId, long childObjectId, object parentObject, SerializationInfo info, string fieldName, MemberInfo memberInfo, int[] indices) { if (info != null) { _manager.RecordDelayedFixup (parentObjectId, fieldName, childObjectId); } else if (parentObject is Array) { if (indices.Length == 1) _manager.RecordArrayElementFixup (parentObjectId, indices[0], childObjectId); else _manager.RecordArrayElementFixup (parentObjectId, (int[])indices.Clone(), childObjectId); } else { _manager.RecordFixup (parentObjectId, memberInfo, childObjectId); } } private Type GetDeserializationType (long assemblyId, string className) { string assemblyName = (string)_registeredAssemblies[assemblyId]; if (_binder == null) { Assembly assembly = Assembly.Load (assemblyName); return assembly.GetType (className, true); } else return _binder.BindToType (assemblyName, className); } public Type ReadType (BinaryReader reader, TypeTag code) { switch (code) { case TypeTag.PrimitiveType: return BinaryCommon.GetTypeFromCode (reader.ReadByte()); case TypeTag.String: return typeof(string); case TypeTag.ObjectType: return typeof(object); case TypeTag.RuntimeType: { string name = reader.ReadString (); return Type.GetType (name, true); } case TypeTag.GenericType: { string name = reader.ReadString (); long asmid = (long) reader.ReadUInt32(); return GetDeserializationType (asmid, name); } case TypeTag.ArrayOfObject: return typeof(object[]); case TypeTag.ArrayOfString: return typeof(string[]); case TypeTag.ArrayOfPrimitiveType: Type elementType = BinaryCommon.GetTypeFromCode (reader.ReadByte()); return Type.GetType(elementType.FullName + "[]"); default: throw new NotSupportedException ("Unknow type tag"); } } public static object ReadPrimitiveTypeValue (BinaryReader reader, Type type) { if (type == null) return null; switch (Type.GetTypeCode (type)) { case TypeCode.Boolean: return reader.ReadBoolean(); case TypeCode.Byte: return reader.ReadByte(); case TypeCode.Char: return reader.ReadChar(); case TypeCode.DateTime: return new DateTime (reader.ReadInt64()); case TypeCode.Decimal: return Decimal.Parse (reader.ReadString(), CultureInfo.InvariantCulture); case TypeCode.Double: return reader.ReadDouble(); case TypeCode.Int16: return reader.ReadInt16(); case TypeCode.Int32: return reader.ReadInt32(); case TypeCode.Int64: return reader.ReadInt64(); case TypeCode.SByte: return reader.ReadSByte(); case TypeCode.Single: return reader.ReadSingle(); case TypeCode.UInt16: return reader.ReadUInt16(); case TypeCode.UInt32: return reader.ReadUInt32(); case TypeCode.UInt64: return reader.ReadUInt64(); case TypeCode.String: return reader.ReadString(); default: if (type == typeof(TimeSpan)) return new TimeSpan (reader.ReadInt64 ()); else throw new NotSupportedException ("Unsupported primitive type: " + type.FullName); } } } }