4 // Lluis Sanchez Gual (lluis@ideary.com)
\r
6 // (C) 2003 Lluis Sanchez Gual
\r
8 // FIXME: Implement the missing binary elements
\r
12 using System.Collections;
\r
13 using System.Runtime.Serialization;
\r
14 using System.Runtime.Remoting.Messaging;
\r
15 using System.Reflection;
\r
17 namespace System.Runtime.Serialization.Formatters.Binary
\r
19 internal class ObjectWriter
\r
21 ObjectIDGenerator _idGenerator = new ObjectIDGenerator();
\r
22 Hashtable _cachedTypes = new Hashtable();
\r
23 Queue _pendingObjects = new Queue();
\r
24 Hashtable _assemblyCache = new Hashtable();
\r
26 static Assembly _corlibAssembly = typeof(string).Assembly;
\r
28 ISurrogateSelector _surrogateSelector;
\r
29 StreamingContext _context;
\r
33 public Type[] Types;
\r
34 public string[] Names;
\r
35 public Assembly TypeAssembly;
\r
36 public Type InstanceType;
\r
37 public long ObjectID;
\r
38 public bool CustomSerialization;
\r
40 public bool Equals (TypeMetadata other)
\r
42 if (!CustomSerialization) return true;
\r
44 TypeMetadata tm = (TypeMetadata)other;
\r
45 if (Types.Length != tm.Types.Length) return false;
\r
46 if (TypeAssembly != other.TypeAssembly) return false;
\r
47 if (InstanceType != other.InstanceType) return false;
\r
48 for (int n=0; n<Types.Length; n++)
\r
50 if (Types[n] != tm.Types[n]) return false;
\r
51 if (Names[n] != tm.Names[n]) return false;
\r
57 public ObjectWriter(ISurrogateSelector surrogateSelector, StreamingContext context)
\r
59 _surrogateSelector = surrogateSelector;
\r
63 public void WriteObjectGraph (BinaryWriter writer, object obj, Header[] headers)
\r
65 _pendingObjects.Clear();
\r
66 if (headers != null) QueueObject (headers);
\r
68 WriteQueuedObjects (writer);
\r
69 WriteSerializationEnd (writer);
\r
72 public void QueueObject (object obj)
\r
74 _pendingObjects.Enqueue (obj);
\r
77 public void WriteQueuedObjects (BinaryWriter writer)
\r
80 while (_pendingObjects.Count > 0)
\r
81 WriteObjectInstance (writer, _pendingObjects.Dequeue(), false);
\r
84 public void WriteObjectInstance (BinaryWriter writer, object obj, bool isValueObject)
\r
89 // If the object is a value type (not boxed) then there is no need
\r
90 // to register it in the id generator, because it won't have other
\r
93 if (isValueObject) id = _idGenerator.NextId;
\r
94 else id = _idGenerator.GetId (obj, out firstTime);
\r
96 if (obj is string) {
\r
97 WriteString (writer, id, (string)obj);
\r
99 else if (obj is Array) {
\r
100 WriteArray (writer, id, (Array)obj);
\r
103 WriteObject (writer, id, obj);
\r
106 public static void WriteSerializationEnd (BinaryWriter writer)
\r
108 writer.Write ((byte) BinaryElement.End);
\r
111 private void WriteObject (BinaryWriter writer, long id, object obj)
\r
114 TypeMetadata metadata;
\r
116 GetObjectData (obj, out metadata, out values);
\r
118 TypeMetadata chachedMetadata = (TypeMetadata)_cachedTypes[metadata.InstanceType];
\r
120 if (chachedMetadata != null && metadata.Equals(chachedMetadata))
\r
122 // An object of the same type has already been serialized
\r
123 // It is not necessary to write again type metadata
\r
125 writer.Write ((byte) BinaryElement.RefTypeObject);
\r
126 writer.Write ((int)id);
\r
128 // Get the id of the object that has the same type as this
\r
129 long refId = chachedMetadata.ObjectID;
\r
131 writer.Write ((int)refId);
\r
132 WriteObjectContent (writer, metadata.Types, values);
\r
136 if (chachedMetadata == null)
\r
138 metadata.ObjectID = id;
\r
139 _cachedTypes [metadata.InstanceType] = metadata;
\r
142 BinaryElement objectTag;
\r
145 if (metadata.TypeAssembly == _corlibAssembly)
\r
148 objectTag = BinaryElement.RuntimeObject;
\r
153 objectTag = BinaryElement.ExternalObject;
\r
156 assemblyId = RegisterAssembly (metadata.TypeAssembly, out firstTime);
\r
157 if (firstTime) WriteAssembly (writer, assemblyId, metadata.TypeAssembly);
\r
160 // Registers the assemblies needed for each field
\r
161 // If there are assemblies that where not registered before this object,
\r
164 foreach (object value in values)
\r
166 if (value == null) continue;
\r
168 Type memberType = value.GetType();
\r
169 while (memberType.IsArray)
\r
170 memberType = memberType.GetElementType();
\r
172 if (memberType.Assembly != _corlibAssembly)
\r
175 int aid = RegisterAssembly (memberType.Assembly, out firstTime);
\r
176 if (firstTime) WriteAssembly (writer, aid, memberType.Assembly);
\r
180 // Writes the object
\r
182 writer.Write ((byte) objectTag);
\r
183 writer.Write ((int)id);
\r
184 writer.Write (metadata.InstanceType.FullName);
\r
185 WriteObjectMetadata (writer, metadata, assemblyId);
\r
186 WriteObjectContent (writer, metadata.Types, values);
\r
189 private void WriteObjectMetadata (BinaryWriter writer, TypeMetadata metadata, int assemblyId)
\r
191 Type[] types = metadata.Types;
\r
192 string[] names = metadata.Names;
\r
194 writer.Write (types.Length);
\r
197 foreach (string name in names)
\r
198 writer.Write (name);
\r
201 foreach (Type type in types)
\r
202 WriteTypeCode (writer, type);
\r
204 // Type specs of fields
\r
205 foreach (Type type in types)
\r
206 WriteTypeSpec (writer, type);
\r
208 if (assemblyId != -1) writer.Write (assemblyId);
\r
211 private void WriteObjectContent (BinaryWriter writer, Type[] types, object[] values)
\r
213 for (int n=0; n<values.Length; n++)
\r
214 WriteValue (writer, types[n], values[n]);
\r
217 private void GetObjectData (object obj, out TypeMetadata metadata, out object[] values)
\r
219 metadata = new TypeMetadata();
\r
220 metadata.InstanceType = obj.GetType();
\r
221 metadata.TypeAssembly = metadata.InstanceType.Assembly;
\r
223 // Check if the formatter has a surrogate selector
\96 if it does,
\r
224 // check if the surrogate selector handles objects of the given type.
\r
226 if (_surrogateSelector != null)
\r
228 ISurrogateSelector selector;
\r
229 ISerializationSurrogate surrogate = _surrogateSelector.GetSurrogate (metadata.InstanceType, _context, out selector);
\r
230 if (surrogate != null)
\r
232 SerializationInfo info = new SerializationInfo (metadata.InstanceType, new FormatterConverter ());
\r
233 surrogate.GetObjectData(obj, info, _context);
\r
234 GetDataFromSerializationInfo (info, ref metadata, out values);
\r
239 // Check if the object is marked with the Serializable attribute
\r
241 if (!metadata.InstanceType.IsSerializable)
\r
242 throw new SerializationException ("Type " + metadata.InstanceType +
\r
243 " is not marked as Serializable " +
\r
244 "and does not implement ISerializable.");
\r
246 ISerializable ser = obj as ISerializable;
\r
250 SerializationInfo info = new SerializationInfo (metadata.InstanceType, new FormatterConverter ());
\r
251 ser.GetObjectData (info, _context);
\r
252 GetDataFromSerializationInfo (info, ref metadata, out values);
\r
255 GetDataFromObjectFields (obj, ref metadata, out values);
\r
258 private void GetDataFromSerializationInfo (SerializationInfo info, ref TypeMetadata metadata, out object[] values)
\r
260 Type[] types = types = new Type [info.MemberCount];
\r
261 string[] names = new string [info.MemberCount];
\r
262 values = new object [info.MemberCount];
\r
264 SerializationInfoEnumerator e = info.GetEnumerator ();
\r
267 while (e.MoveNext ())
\r
269 values[n] = e.Value;
\r
270 types[n] = e.ObjectType;
\r
275 if (info.FullTypeName != metadata.InstanceType.FullName || info.AssemblyName != metadata.TypeAssembly.FullName)
\r
277 metadata.TypeAssembly = Assembly.Load (info.AssemblyName);
\r
278 metadata.InstanceType = metadata.TypeAssembly.GetType (info.FullTypeName);
\r
281 metadata.Types = types;
\r
282 metadata.Names = names;
\r
283 metadata.CustomSerialization = true;
\r
286 private void GetDataFromObjectFields (object obj, ref TypeMetadata metadata, out object[] values)
\r
288 MemberInfo[] members = FormatterServices.GetSerializableMembers (obj.GetType(), _context);
\r
289 values = FormatterServices.GetObjectData (obj, members);
\r
291 Type[] types = new Type [members.Length];
\r
292 string[] names = new string [members.Length];
\r
294 for (int n=0; n<members.Length; n++)
\r
296 MemberInfo member = members[n];
\r
297 names[n] = member.Name;
\r
298 if (member is FieldInfo)
\r
299 types[n] = ((FieldInfo)member).FieldType;
\r
300 else if (member is PropertyInfo)
\r
301 types[n] = ((PropertyInfo)member).PropertyType;
\r
304 metadata.Types = types;
\r
305 metadata.Names = names;
\r
307 metadata.CustomSerialization = false;
\r
310 private void WriteArray (BinaryWriter writer, long id, Array array)
\r
312 // There are 4 ways of serializing arrays:
\r
313 // The element GenericArray (7) can be used for all arrays.
\r
314 // The element ArrayOfPrimitiveType (15) can be used for single-dimensional
\r
315 // arrays of primitive types
\r
316 // The element ArrayOfObject (16) can be used for single-dimensional Object arrays
\r
317 // The element ArrayOfString (17) can be used for single-dimensional string arrays
\r
319 Type elementType = array.GetType().GetElementType();
\r
321 if (elementType == typeof (object) && array.Rank == 1) {
\r
322 WriteObjectArray (writer, id, array);
\r
324 else if (elementType == typeof (string) && array.Rank == 1) {
\r
325 WriteStringArray (writer, id, array);
\r
327 else if (BinaryCommon.IsPrimitive(elementType) && array.Rank == 1) {
\r
328 WritePrimitiveTypeArray (writer, id, array);
\r
331 WriteGenericArray (writer, id, array);
\r
334 private void WriteGenericArray (BinaryWriter writer, long id, Array array)
\r
336 Type elementType = array.GetType().GetElementType();
\r
338 // Registers and writes the assembly of the array element type if needed
\r
340 if (!elementType.IsArray && elementType.Assembly != _corlibAssembly)
\r
343 int aid = RegisterAssembly (elementType.Assembly, out firstTime);
\r
344 if (firstTime) WriteAssembly (writer, aid, elementType.Assembly);
\r
347 // Writes the array
\r
349 writer.Write ((byte) BinaryElement.GenericArray);
\r
350 writer.Write ((int)id);
\r
352 // Write the structure of the array
\r
354 if (elementType.IsArray)
\r
355 writer.Write ((byte) ArrayStructure.Jagged);
\r
356 else if (array.Rank == 1)
\r
357 writer.Write ((byte) ArrayStructure.SingleDimensional);
\r
359 writer.Write ((byte) ArrayStructure.MultiDimensional);
\r
361 // Write the number of dimensions and the length
\r
362 // of each dimension
\r
364 writer.Write (array.Rank);
\r
365 for (int n=0; n<array.Rank; n++)
\r
366 writer.Write (array.GetUpperBound (n) + 1);
\r
369 WriteTypeCode (writer, elementType);
\r
370 WriteTypeSpec (writer, elementType);
\r
372 // Writes the values. For single-dimension array, a special tag is used
\r
373 // to represent multiple consecutive null values. I don't know why this
\r
374 // optimization is not used for multidimensional arrays.
\r
376 if (array.Rank == 1 && !elementType.IsValueType)
\r
378 WriteSingleDimensionArrayElements (writer, array, elementType);
\r
382 int[] indices = new int[array.Rank];
\r
384 // Initialize indexes
\r
385 for (int dim = array.Rank-1; dim >= 0; dim--)
\r
386 indices[dim] = array.GetLowerBound (dim);
\r
391 WriteValue (writer, elementType, array.GetValue (indices));
\r
393 for (int dim = array.Rank-1; dim >= 0; dim--)
\r
396 if (indices[dim] > array.GetUpperBound (dim))
\r
399 indices[dim] = array.GetLowerBound (dim);
\r
400 continue; // Increment the next dimension's index
\r
402 end = true; // That was the last dimension. Finished.
\r
410 private void WriteObjectArray (BinaryWriter writer, long id, Array array)
\r
412 writer.Write ((byte) BinaryElement.ArrayOfObject);
\r
413 writer.Write ((int)id);
\r
414 writer.Write (array.Length); // Single dimension. Just write the length
\r
415 WriteSingleDimensionArrayElements (writer, array, typeof (object));
\r
418 private void WriteStringArray (BinaryWriter writer, long id, Array array)
\r
420 writer.Write ((byte) BinaryElement.ArrayOfString);
\r
421 writer.Write ((int)id);
\r
422 writer.Write (array.Length); // Single dimension. Just write the length
\r
423 WriteSingleDimensionArrayElements (writer, array, typeof (string));
\r
426 private void WritePrimitiveTypeArray (BinaryWriter writer, long id, Array array)
\r
428 writer.Write ((byte) BinaryElement.ArrayOfPrimitiveType);
\r
429 writer.Write ((int)id);
\r
430 writer.Write (array.Length); // Single dimension. Just write the length
\r
432 Type elementType = array.GetType().GetElementType();
\r
433 WriteTypeSpec (writer, elementType);
\r
435 for (int n=0; n<array.Length; n++)
\r
436 WritePrimitiveValue (writer, array.GetValue (n));
\r
439 private void WriteSingleDimensionArrayElements (BinaryWriter writer, Array array, Type elementType)
\r
442 for (int n = array.GetLowerBound (0); n<=array.GetUpperBound(0); n++)
\r
444 object val = array.GetValue (n);
\r
445 if (val != null && numNulls > 0)
\r
447 WriteNullFiller (writer, numNulls);
\r
448 WriteValue (writer, elementType, val);
\r
451 else if (val == null)
\r
454 WriteValue (writer, elementType, val);
\r
457 WriteNullFiller (writer, numNulls);
\r
460 private void WriteNullFiller (BinaryWriter writer, int numNulls)
\r
462 if (numNulls == 1) {
\r
463 writer.Write ((byte) BinaryElement.NullValue);
\r
465 else if (numNulls == 2) {
\r
466 writer.Write ((byte) BinaryElement.NullValue);
\r
467 writer.Write ((byte) BinaryElement.NullValue);
\r
469 else if (numNulls <= byte.MaxValue) {
\r
470 writer.Write ((byte) BinaryElement.ArrayFiller8b);
\r
471 writer.Write ((byte) numNulls);
\r
474 writer.Write ((byte) BinaryElement.ArrayFiller32b);
\r
475 writer.Write (numNulls);
\r
479 private void WriteObjectReference (BinaryWriter writer, long id)
\r
482 writer.Write ((byte) BinaryElement.ObjectReference);
\r
483 writer.Write ((int)id);
\r
486 private void WriteValue (BinaryWriter writer, Type valueType, object val)
\r
490 writer.Write ((byte) BinaryElement.NullValue);
\r
492 else if (BinaryCommon.IsPrimitive(val.GetType()))
\r
494 if (!BinaryCommon.IsPrimitive(valueType))
\r
496 // It is a boxed primitive type value
\r
497 writer.Write ((byte) BinaryElement.BoxedPrimitiveTypeValue);
\r
498 WriteTypeSpec (writer, val.GetType());
\r
500 WritePrimitiveValue (writer, val);
\r
502 else if (valueType.IsValueType)
\r
504 // Value types are written embedded in the containing object
\r
505 WriteObjectInstance (writer, val, true);
\r
507 else if (val is string)
\r
509 // Strings are written embedded, unless already registered
\r
511 long id = _idGenerator.GetId (val, out firstTime);
\r
513 if (firstTime) WriteObjectInstance (writer, val, false);
\r
514 else WriteObjectReference (writer, id);
\r
518 // It is a reference type. Write a forward reference and queue the
\r
519 // object to the pending object list (unless already written).
\r
522 long id = _idGenerator.GetId (val, out firstTime);
\r
524 if (firstTime) _pendingObjects.Enqueue (val);
\r
525 WriteObjectReference (writer, id);
\r
529 private void WriteString (BinaryWriter writer, long id, string str)
\r
531 writer.Write ((byte) BinaryElement.String);
\r
532 writer.Write ((int)id);
\r
533 writer.Write (str);
\r
536 private void WriteAssembly (BinaryWriter writer, int id, Assembly assembly)
\r
538 writer.Write ((byte) BinaryElement.Assembly);
\r
540 writer.Write (assembly.GetName ().FullName);
\r
543 private int GetAssemblyId (Assembly assembly)
\r
545 return (int)_assemblyCache[assembly];
\r
548 private int RegisterAssembly (Assembly assembly, out bool firstTime)
\r
550 if (_assemblyCache.ContainsKey (assembly))
\r
553 return (int)_assemblyCache[assembly];
\r
557 int id = (int)_idGenerator.GetId (0, out firstTime);
\r
558 _assemblyCache.Add (assembly, id);
\r
563 public static void WritePrimitiveValue (BinaryWriter writer, object value)
\r
565 Type type = value.GetType();
\r
567 switch (Type.GetTypeCode (type))
\r
569 case TypeCode.Boolean:
\r
570 writer.Write ((bool)value);
\r
573 case TypeCode.Byte:
\r
574 writer.Write ((byte) value);
\r
577 case TypeCode.Char:
\r
578 writer.Write ((char) value);
\r
581 case TypeCode.DateTime:
\r
582 writer.Write ( ((DateTime)value).Ticks);
\r
585 case TypeCode.Decimal:
\r
586 writer.Write ((decimal) value);
\r
589 case TypeCode.Double:
\r
590 writer.Write ((double) value);
\r
593 case TypeCode.Int16:
\r
594 writer.Write ((short) value);
\r
597 case TypeCode.Int32:
\r
598 writer.Write ((int) value);
\r
601 case TypeCode.Int64:
\r
602 writer.Write ((long) value);
\r
605 case TypeCode.SByte:
\r
606 writer.Write ((sbyte) value);
\r
609 case TypeCode.Single:
\r
610 writer.Write ((float) value);
\r
613 case TypeCode.UInt16:
\r
614 writer.Write ((ushort) value);
\r
617 case TypeCode.UInt32:
\r
618 writer.Write ((uint) value);
\r
621 case TypeCode.UInt64:
\r
622 writer.Write ((ulong) value);
\r
625 case TypeCode.String:
\r
626 writer.Write ((string) value);
\r
630 if (type == typeof (TimeSpan))
\r
631 writer.Write (((TimeSpan)value).Ticks);
\r
633 throw new NotSupportedException ("Unsupported primitive type: " + value.GetType().FullName);
\r
638 public static void WriteTypeCode (BinaryWriter writer, Type type)
\r
640 writer.Write ((byte) GetTypeTag (type));
\r
643 public static TypeTag GetTypeTag (Type type)
\r
645 if (type == typeof (string)) {
\r
646 return TypeTag.String;
\r
648 else if (BinaryCommon.IsPrimitive (type)) {
\r
649 return TypeTag.PrimitiveType;
\r
651 else if (type == typeof (object)) {
\r
652 return TypeTag.ObjectType;
\r
654 else if (type.IsArray && type.GetArrayRank() == 1 && type.GetElementType() == typeof (object)) {
\r
655 return TypeTag.ArrayOfObject;
\r
657 else if (type.IsArray && type.GetArrayRank() == 1 && type.GetElementType() == typeof (string)){
\r
658 return TypeTag.ArrayOfString;
\r
660 else if (type.IsArray && type.GetArrayRank() == 1 && BinaryCommon.IsPrimitive(type.GetElementType())) {
\r
661 return TypeTag.ArrayOfPrimitiveType;
\r
663 else if (type.Assembly == _corlibAssembly) {
\r
664 return TypeTag.RuntimeType;
\r
667 return TypeTag.GenericType;
\r
670 public void WriteTypeSpec (BinaryWriter writer, Type type)
\r
672 switch (GetTypeTag (type))
\r
674 case TypeTag.PrimitiveType:
\r
675 writer.Write (BinaryCommon.GetTypeCode (type));
\r
678 case TypeTag.RuntimeType:
\r
679 writer.Write (type.FullName);
\r
682 case TypeTag.GenericType:
\r
683 writer.Write (type.FullName);
\r
684 writer.Write ((int)GetAssemblyId (type.Assembly));
\r
687 case TypeTag.ArrayOfPrimitiveType:
\r
688 writer.Write (BinaryCommon.GetTypeCode (type.GetElementType()));
\r
692 // Type spec not needed
\r