HOW TO
When writing customized objects, many cases need to support serialization. For example, a custom symbol or element needs to be saved with the map document and load when the map document is opened. This functionality is available by implementing the interface IPersistVariant.
To support cloning through serialization, temporarily save the object to an ObjectSteam and then duplicate the object by creating a new instance of the class and loading its properties from the temporary ObjectStream. use an ObjectCopy class, which uses ObjectStream internally. This class requires that the cloned object, or clonee, support IPersistStream.
IPersistStream is a Microsoft interface which provides methods for saving and loading objects that use a simple serial stream for their storage needs. In order to use the structured stream given by these methods using .NET, the objects must be converted into a byte array.
There are several ways to convert objects into byte arrays in .NET, however each method is object type specific. For objects that only have managed class members, use MemoryStream in conjunction with a BinaryFormatter, assuming that all of these members support serialization. The reality is that the object has different types of class members, including both managed and unmanaged types, i.e., ArcObjects components. This makes it a challenge to implement the IPersistStream .Save() and IPersistStream.Load() methods. Writing this code as a part of the custom object can also be cumbersome and difficult to read and maintain.
The solution is to write a helper static class with helper methods Save() and Load () to delegate the calls when implementing IPersistStream in an object.
Note:
Some class members should not be directly copied, for example window handles (hWnd), device contexts (hDC), file handles, and Graphical Device Interface (GDI).
Warning:
The following code uses unsafe code since interface IStream requires usage of pointers and therefore only given in C#.
Code:
if (Marshal.IsComObject(data))
{
//*** Create XmlWriter ***
IXMLWriter xmlWriter = new XMLWriterClass();
//*** Create XmlStream ***
IXMLStream xmlStream = new XMLStreamClass();
//*** Write the object to the stream ***
xmlWriter.WriteTo(xmlStream as IStream);
//*** Serialze object ***
IXMLSerializer xmlSerializer = new XMLSerializerClass();
xmlSerializer.WriteObject(xmlWriter, null, null, "arcobject", "http://www.esri.com/schemas/ArcGIS/9.2", data);
Code:
string str = xmlStream.SaveToString();
data = (object)str;
if (null == data)
return;
Code:
//make sure that the object is serializable
if (!data.GetType().IsSerializable)
throw new Exception("Object is not serializable.");
// Convert the string into a byte array
MemoryStream memoryStream = new MemoryStream();
BinaryFormatter binaryFormatter = new BinaryFormatter();
binaryFormatter.Serialize(memoryStream, data);
Code:
byte[] bytes = memoryStream.ToArray();
memoryStream.Close();
Note:
This is very important, since when reading objects from the structured stream in .NET, the size of the object must be specified in bytes. The length of the byte array is given as an integer therefore converting this information into a byte array always results in an array of 4 bytes. When reading the object from the structured stream, first read the four bytes specifying the length of the object in bytes and only then read the actual object from the stream.
Code:
// Get Byte Length
byte[] arrLen = BitConverter.GetBytes(bytes.Length);
Code:
// Get Memory Pointer to Int32
int cb;
int* pcb = &cb;
// Write Byte Length
stream.Write(arrLen, arrLen.Length, new IntPtr(pcb));
// Write Btye Array
stream.Write(bytes, bytes.Length, new IntPtr(pcb));
Code:
if (bytes.Length != cb)
throw new Exception("Error writing object to stream");
Code:
// Get Pointer to Int32
int cb;
int* pcb = &cb;
// Get Size of the object's Byte Array
byte[] arrLen = new Byte[4];
stream.Read(arrLen, arrLen.Length, new IntPtr(pcb));
cb = BitConverter.ToInt32(arrLen, 0);
Code:
// Read the object's Byte Array
byte[] bytes = new byte[cb];
stream.Read(bytes, cb, new IntPtr(pcb));
Code:
if (bytes.Length != cb)
throw new Exception("Error reading object from stream");
Code:
// Deserialize byte array
object data = null;
MemoryStream memoryStream = new MemoryStream(bytes);
BinaryFormatter binaryFormatter = new BinaryFormatter();
object objectDeserialize = binaryFormatter.Deserialize(memoryStream);
if (objectDeserialize != null)
{
data = objectDeserialize;
}
memoryStream.Close();
Code:
//deserialize arcobjects
if (data is string)
{
string str = (string)data;
if (str.IndexOf("http://www.esri.com/schemas/ArcGIS/9.2") != -1)
{
IXMLStream readerStream = new XMLStreamClass();
readerStream.LoadFromString(str);
IXMLReader xmlReader = new XMLReaderClass();
xmlReader.ReadFrom((IStream)readerStream);
IXMLSerializer xmlReadSerializer = new XMLSerializerClass();
object retObj = xmlReadSerializer.ReadObject(xmlReader, null, null);
if (null != retObj)
data = retObj;
}
}
Code:
return data;
Code:
//class members
private int m_version = 1;
private ISpatialReference m_spatialRef = null;
private IPoint m_point = null;
private string m_name = string.Empty;
private ArrayList m_arr = null;
private Guid m_ID;
Code:
public void GetClassID(out Guid pClassID)
{
pClassID = new Guid(ClonableObjClass.GUID);
}
Code:
public void Save(IStream pStm, int fClearDirty)
{
//cast the ESRI.ArcGIS.IStream
System.Runtime.InteropServices.ComTypes.IStream stream = (System.Runtime.InteropServices.ComTypes.IStream)pStm;
//save the different objects to the stream
PeristStream.PeristStreamHelper.Save(stream, m_version);
PeristStream.PeristStreamHelper.Save(stream, m_ID.ToByteArray());
PeristStream.PeristStreamHelper.Save(stream, m_name);
PeristStream.PeristStreamHelper.Save(stream, m_spatialRef);
//save the guid
PeristStream.PeristStreamHelper.Save(stream, m_ID);
//save the point to the stream
if (null == m_point)
m_point = new PointClass();
PeristStream.PeristStreamHelper.Save(stream, m_point);
if (null == m_arr)
m_arr = new ArrayList();
PeristStream.PeristStreamHelper.Save(stream, m_arr);
}
public void Load(IStream pStm)
{
// cast the ESRI.ArcGIS.IStream
System.Runtime.InteropServices.ComTypes.IStream stream = (System.Runtime.InteropServices.ComTypes.IStream)pStm;
//load the information from the stream
object obj = null;
obj = PeristStream.PeristStreamHelper.Load(stream);
m_version = Convert.ToInt32(obj);
obj = PeristStream.PeristStreamHelper.Load(stream);
byte[] arr = (byte[])obj;
m_ID = new Guid(arr);
obj = PeristStream.PeristStreamHelper.Load(stream);
m_name = Convert.ToString(obj);
obj = PeristStream.PeristStreamHelper.Load(stream);
m_spatialRef = obj as ISpatialReference;
obj = PeristStream.PeristStreamHelper.Load(stream);
m_ID = (Guid)obj;
obj = PeristStream.PeristStreamHelper.Load(stream);
m_point = obj as IPoint;
obj = PeristStream.PeristStreamHelper.Load(stream);
m_arr = obj as ArrayList;
}
Get help from ArcGIS experts
Download the Esri Support App