This topic is relevant for the following:
Product(s): ArcGIS Desktop: All
Version(s): 9.0, 9.1, 9.2
Language(s): VB6, VC++
Experience level(s): Intermediate to advanced
In this section:
Cloning is the process of copying an objectthe creation of a new instance of a class, representing information equivalent to the original instance.
Creating a copy of a particular object is more complex than simply assigning a new variable. For example, the code below simply creates two variables that point to the same object in memory.
DimpPointOneAsesriGeometry.IPointDimpPointTwo as esriGeometry.IPointSetpPointOne =NewesriGeometry.PointSetpPointTwo = pPointOne
Copying an object is more complex than simply assigning a new variable.
To actually copy the Point object, creating a new instance of a Point with comparable data to the first Point, use the IClone interface.
DimpCloneAsesriSystem.ICloneSetpClone = pPointOneSetpPointTwo = pClone.Clone
Cloning creates a new instance in memory.
The concepts reviewed in this section should be familiar to any VC++ developer; however, some VB developers may find they do not normally consider these kinds of issues.
This technique is used extensively throughout ArcGIS by ArcObjects classes that implement the IClone interface.
Cloning is a technique used extensively throughout ArcGIS.
For example, before the Application passes an object to a property page, it clones the object. Only if the OK or Apply button is pressed are the properties of the cloned object set into the original object. Another use of cloning in ArcObjects is by methods or properties, which specifically return a copy of an object, for example, the IFeature::ShapeCopy method.
You can also find other examples of how cloning is used by searching the samples included in the ArcGIS Developer Help.
Throughout this section, the original object will be referred to as the Clonerthis object performs the cloning operation. The object resulting from the cloning process will be called the Clonee.
The exact details of the clone operation are encapsulated in the class implementationthe class regulates which of its members should be copied and also how they should be copied.
Each class that implements cloning decides how to clone itself.
For a simple object whose members contain only value type information, the cloning process is relatively simple. A new instance of the class is created, and the values of all the members of the clonee are set to equal the values of the cloner. The clonee object is independent of the cloner.
For an object whose members contain object references, the cloning process becomes more complex. Should the cloner copy only the object references to the clonee? This is sometimes known as a shallow clone. Or should new instances of each of the referenced objects be created also and the clonee's members be set to reference these new objects? This is referred to as a deep clone.
There are different levels of cloning, shallow and deep.
Both shallow and deep cloning are used by ArcObjects classes. An example of a deep clone, where referenced objects are also cloned, is when a graphic element is cloned. Both the Geometry and Symbol of the graphic element are also clonedthe Geometry and Symbol properties of the clonee are entirely separate from the original objects' Geometry and Symbol.
In other cases it is logical to simply copy an object reference to the new object. Shallow cloning is used, for example, on the geometries in a FeatureClass. Every geometry has an object reference indicating its coordinate system (IGeometry::SpatialReference). Cloning the Geometry produces an object with a reference to the same underlying SpatialReference object. In this case, only the object reference is copied, as it is logical for all geometries in a FeatureClass to hold a reference to the same spatial reference object, as does the layer itself. The SpatialReference can then be changed by a single method call.
There is no simple rule for deciding whether an object reference should be copied or the referenced object itself should be cloned. This is decided on a case-by-case basis, and both techniques may be included in a single class.
For each of its private members, a class needs to make the appropriate choice between shallow or deep cloning.
You must be particularly careful when cloning objects that hold object references. In many cases, a referenced object may hold references to yet more objects, which in turn hold references to other objects, and so on.
When coding a clone method, bear in mind that some members should not be directly copied at allwindow handles (hWnd), device contexts (hDC), file handles, and Graphical Device Interface (GDI) resources, for example, contain instance-specific information, which should not be duplicated directly to another object.
In most cases, it is inappropriate to clone an object that contains this type of instance-specific information. For example, a Workspace and FeatureClass both have connection-specific information and are not clonable; an OverviewWindow and a ToolControl both have a window handle and are not clonable. If a new instance is required, the object is created from scratch.
Sometimes it is more appropriate for a class not to replicate a member in its clone at all.
If you need to implement IClone on such an object, ensure that any instance-specific information is populated from scratch, instead of simply copying the instance-specific values.
If you implement cloning in your custom components, you will need to make some decisions about how you copy the information contained in your classwhether shallow or deep cloning is most appropriate for each member and how to implement this.
The sections below show you how to implement each of the IClone members in your custom class.
Two different approaches are discussed. The first approach is straightforward and can be implemented using similar logic in either VB or VC++. The second approach can only be used in VC++, as it uses a class's own persistence implementation to perform the clone.
In the Clone method, begin by creating a new instance of the class, which is the clonee. You can then call the IClone::Assign, to copy the properties of the cloner to the clonee. Lastly, return a reference to the clonee from Clone.
Private FunctionIClone_Clone()AsesriSystem.ICloneDimpNewObjectAsMyLibrary.MyClass, pCloneAsesriSystem.ICloneSetpNewObject =NewMyLibrary.MyClassSetpClone = pNewObject' pNewObject represents the Clonee.pClone.Assign Me' Me represents the Cloner.SetIClone_Clone = pCloneEnd Function
Clone should create a new instance of the class.
The Assign method receives a reference to a second instance of the class, srcthis is the clonee. First, check src to see if it is pointing to a valid objectif not, raise the appropriate standard COM error.
Const E_POINTER = &H80004003Assign should receive a valid instance of the class.
Then copy the values of the members from src (the clonee) to the current instance of the class, Me (the cloner).
Private SubIClone_Assign(ByValsrcAsesriSystem.IClone)If(srcIs Nothing)ThenErr.RaiseConstE_POINTERExit Sub ElseIf TypeOfsrcIsMyLibrary.IMyInterfaceThen DimpSrcMyInterfaceAsMyLibrary.IMyInterfaceSetpSrcMyInterface = src' m_MyMember is a class member storing value of MyMember property.m_MyMember = pSrcMyInterface.MyMemberEnd If End Sub
The cloner copies values from the clonee.
The Assign code above shows a shallow clone of the MyMember property. If MyMember is another object reference, you may want to perform a deep cloneif the object itself supports IClone, this is straightforward.
DimpClonedAsesriSystem.ICloneSetpCloned = pSrcInterface.MyMemberSetm_MyMember = pCloned.Clone
If the member object does not support IClone, you must create a new object and set its properties from the existing MyMember property of the source object, scr.
Remember to think about whether it is more appropriate to copy just an object reference (for example, all the geometries of a FeatureClass hold a reference to the same SpatialReference), clone the object reference, or leave the member uncopied to be set by the client code as appropriate.
When coding the Assign method, you should consider the choice of shallow or deep cloning. Consider that some member variables may not be suitable for cloning.
As an example, consider how a RandomColorRamp performs an Assign. The cloner RandomColorRamp will have the same MinSaturation, MaxSaturation, MinValue, MaxValue, StartHue, EndHue, UseSeed, Seed, and Name as the clonee. However, the Assign method does not copy the value of Size or call the CreateRamp method; this means the color ramp has no array of Colors and cannot be used in a renderer at that point. After a call to Assign, the client must set up the Colors array of the RandomColorRamp by setting its Size property and calling its CreateRamp method.
Another consideration when coding your Assign method should be the current state of both the cloner and clonee objects. You may decide to clear any stateful information held by the cloner before assigning the properties from the clonee. In this case, you may want to add an internal initialization function to set the values of the class to a known initial state. This function could then also be called from your class initialization function.
You may want to clear or reinitialize any member variables before performing an Assign to ensure the result is a faithful clone.
The IsEqual method should compare the cloner (Me) and the clonee (other) to see if all the members are equal in valuereturn True if all the members are equal.
Private FunctionIClone_IsEqual(ByValotherAsesriSystem.IClone)As BooleanIClone_IsEqual =True If(srcIs Nothing)ThenErr.RaiseConstE_POINTERExit Sub ElseIf TypeOfotherIsMyLibrary.IMyInterfaceThen DimpSrcMyInterfaceAsIMyInterface, pOtherMyInterfaceAsIMyInterfaceSetpSrcMyInterface = otherSetpOtherMyInterface = Me IClone_IsEqual = IClone_IsEqualAnd_ (pOtherMyInterface.MyMember = pSrcMyInterface.MyMember) ...End If End Function
If a property holds an object reference that supports IClone, use IClone::IsEqual on the member object to evaluate if it is equal to the member object of the passed-in reference, other. Don't forget to check all the members of all the interfaces that are supported by the object.
IsEqual should determine if two different objects have values that can be considered equivalent.
You decide what your class considers to be Equal valuesyou may decide that two IColor members are equal if they have the same RGB value, even though one is an RGBColor and one is a CMYKColor.
To implement IsIdentical, you should compare the interface pointers to see if the cloner (Me) and the clonee (other) point to the same underlying object in memory. In VB, you can compare two object references (or interface pointers) using the Is keyword.
Private FunctionIClone_IsIdentical(ByValotherAsesriSystem.IClone)_As BooleanIClone_IsIdentical =False If(srcIs Nothing)ThenErr.RaiseConstE_POINTERExit Sub ElseIf TypeOfotherIsMyLibrary.IMyInterfaceThen DimpOtherMyInterfaceAsMyLibrary.IMyInterfaceSetpOtherMyInterface = other IClone_IsIdentical = (pOtherMyInterfaceIsMe)End If End Function
IsIdentical should compare interface pointers to see if they reference the same underlying object.
IClone can be implemented in VC++ with a similar approach to that just described for VB. The Clone method is shown below.
STDMETHODIMP CMyClass::Clone(IClone **Clone)
{
if (!Clone) return E_POINTER;
*Clone = 0;
HRESULT hr;
CComObject<CMyClass>* pItem = 0;
hr = CComObject<CMyClass>::CreateInstance(&pItem);
if (FAILED(hr)) return hr;
pItem->AddRef();
IClonePtr ipClonee = pItem;
if (ipClonee == NULL) return E_FAIL;
pItem->Release();
IClonePtr ipCloner = this;
if (ipCloner == NULL) return E_FAIL;
hr = ipClonee->Assign(ipCloner);
if (FAILED(hr)) return hr;
*Clone = ipClonee.Detach();
return S_OK;
}Note the use of the CComObject static member function CreateInstance, rather than the normal object creation via COMmore information on private initialization can be found on page 148 of ATL Internals (see bibliography for more details).
Implement the Assign, IsEqual, and IsIdentical methods by using the same principles as shown previously for VB.
If your VC++ class implements IPersist and IPersistStream, you can take advantage of the persistence functionality when writing your clone methods. By temporarily saving your object to an ObjectStream, you can duplicate the object by creating a new instance of your class and loading its properties from the temporary ObjectStream.
This technique is used internally, for example, when you cut and paste graphic elements in ArcMap. This technique may result in a more efficient Clone method when working with a complex class with many object references.
This technique is not directly available to VB developers as the signature of IObjectStream::LoadObject is not usable in VB.
The code below shows how you could create an implementation of the Clone method using an ObjectStream. The enumerations used in this code (and IsEqual which follows) are part of the COM platform SDK.
Begin by checking the incoming pointer.
if(!Clone)returnE_POINTER; *Clone = 0; HRESULT hr;
Next, create a memory stream using the COM platform SDK function, CreateStreamOnHGlobal. Also, create an ObjectStream, then aggregate the simple stream into the ObjectStream.
IStreamPtr ipStreamPtr; ::CreateStreamOnHGlobal(NULL, TRUE, &ipStreamPtr);if(ipStreamPtr == NULL)returnE_FAIL; IObjectStreamPtr ipObjectStream; hr = ipObjectStream.CreateInstance(CLSID_ObjectStream);if(FAILED(hr))returnhr; hr = ipObjectStream->putref_Stream(ipStreamPtr);if(FAILED(hr))returnhr;
After checking for the presence of IPersistStream, persist the object to the memory stream.
IUnknownPtr ipUnknown = GetUnknown(); IPersistStreamPtr ipPersistStream = ipUnknown;if(ipPersistStream == NULL)returnE_FAIL; hr = ipObjectStream->SaveObject(ipUnknown); if (FAILED(hr)) return hr;
When you have finished persisting to the stream, reset it to the beginning.
ULARGE_INTEGER newPosition; LARGE_INTEGER moveTo; moveTo.QuadPart = 0; ipObjectStream->Seek(moveTo, STREAM_SEEK_SET, &newPosition);
Then clone the object from the stream in memory.
IUnknownPtr ipCloneeUnk; hr = ipObjectStream->LoadObject((GUID*)&IID_IUnknown, NULL, &ipCloneeUnk);if(FAILED(hr))returnhr; IClonePtr ipClonee = ipCloneeUnk;if(ipClonee == NULL)returnE_FAIL; *Clone = ipClonee.Detach();returnS_OK;
An advantage of this approach to cloning is that the solution is generic. You can use the same code to implement cloning on many classes, as long as they already implement IPersistStream.
Using this technique, a deep clone will be performed, as each object reference will be called on to persist itself to the new stream. Check that this is a suitable operation for your class, particularly if your class holds references to objects referenced elsewhere in the application or MxDocument. For example, a custom GraphicElement implements IGraphicElement, which holds a reference to a SpatialReference objectthis object is a property of the Map in which the GraphicElement resides. If the GraphicElement is cloned, the new object should also hold a reference to this same SpatialReference object, NOT a reference to a separate but equal SpatialReference.
If you do use this technique for cloning such classes, you should reset each of the members you prefer to be shallow cloned after the call to LoadObject.
The Assign method can be implemented similarly to Clonesave the supplied source object to a memory stream and use IObjectStream::ReplaceObject to assign to the object in question.
The IsEqual method can be implemented by saving each object to a separate stream, then performing a byte-by-byte comparison. One way to perform this operation is described step-by-step below.
The IsEqual method receives two parameters, a pointer to a second instance of a clonable class (pOther) and a boolean (pbEqual) to return the outcome of the IsEqual operation. Initialize pbEqual to false, and check that pOther points to a valid instance.
*pbEqual = VARIANT_FALSE;if(!pOther)returnS_OK;
Next, create a MemoryBlobStream and persist the current instance of the class to this stream.
IStreamPtr ipStreamPtr1; ::CreateStreamOnHGlobal(NULL, TRUE, &ipStreamPtr1);if(ipStreamPtr1 == NULL)returnE_FAIL; IObjectStreamPtr ipObjectStream1; hr = ipObjectStream.CreateInstance(CLSID_ObjectStream);if(FAILED(hr))returnhr; hr = ipObjectStream1->putref_Stream(ipStreamPtr1);if(FAILED(hr))returnhr;if(FAILED(::SaveObject((IUnknown*)((IClone*)this), ipObjectStream1, FALSE)))returnS_OK;
Create a second memory stream and save pOther to this stream.
IStreamPtr ipStreamPtr2; ::CreateStreamOnHGlobal(NULL, TRUE, &ipStreamPtr2); ...if(FAILED(::SaveObject(pOther, ipObjectStream2, FALSE)))returnS_OK;
Reset both the streams to the beginning.
ULARGE_INTEGER newPosition; LARGE_INTEGER moveTo; moveTo.QuadPart = 0;if(FAILED(ipObjectStream1->Seek(moveTo, STREAM_SEEK_SET, &newPosition)))returnS_OK;if(FAILED(ipObjectStream2->Seek(moveTo, STREAM_SEEK_SET, &newPosition)))returnS_OK;
Now you can begin to compare the streams. First, compare their size by using the IStream::Stat method to get statistical information about each stream. If the size of the streams is not equal, exit the IsEqual method.
STATSTG ss1, ss2; ipObjectStream1->Stat(&ss1, STATFLAG_NONAME); ipObjectStream2->Stat(&ss2, STATFLAG_NONAME);if(ss1.cbSize.QuadPart != ss2.cbSize.QuadPart)returnS_OK;
Now you can compare the streams, first integer by integer. If the values are not equal, exit the IsEqual method.
longl1, l2;longsize =sizeof(l1);longnumInts = (long)ss1.cbSize.QuadPart / size; DWORD bytesRead;for(longi = 0; i < numInts; i++) {if(FAILED(ipObjectStream1->Read(&l1, size, &bytesRead)))returnS_OK;if(FAILED(ipObjectStream2->Read(&l2, size, &bytesRead)))returnS_OK;if(l1 != l2)returnS_OK; }
Finish by comparing the streams byte by byte.
BYTE b1, b2;longnumBytes = (long)ss1.cbSize.QuadPart - (numInts * size);for(i = 0; i < numBytes; i++) {if(FAILED(ipObjectStream1->Read(&b1, 1, &bytesRead)))returnS_OK;if(FAILED(ipObjectStream2->Read(&b2, 1, &bytesRead)))returnS_OK;if(b1 != b2)returnS_OK; }
If you have reached this point, the current instance of the class and the instance referenced by pOther are equal, so set the return value to true, and exit.
*pbEqual = VARIANT_TRUE;
return S_OK;
}The IsIdentical method can be implemented by a simple pointer comparison.