About Interoperating with COM

Code running under the control of the .NET Framework is called managed code; conversely, code executing outside the .NET Framework is called unmanaged code. Component Object Model (COM) is one example of unmanaged code. The .NET Framework interacts with COM via a technology known as COM Interop. For COM Interop to work, the Common Language Runtime (CLR) requires metadata for all the COM types. This means that the COM type definitions normally stored in the type libraries need to be converted to .NET metadata. This is easily accomplished with the Type Library Importer utility (tlbimp.exe) that ships with the .NET Framework Software Developer Kit (SDK). This utility generates interop assemblies containing the metadata for all the COM definitions in a type library. Once metadata is available, .NET clients can seamlessly create instances of COM types and call its methods as though they were native .NET instances.

Primary interop assemblies
Primary interop assemblies (PIAs) are the official, vendor-supplied .NET type definitions for interoperating with underlying COM types. PIAs are strongly named by the COM library publisher to guarantee uniqueness. ESRI provides primary interop assemblies for all the ArcObjects type libraries that are implemented with COM. ArcGIS .NET developers should only use the PIAs that are installed in the Global Assembly Cache (GAC) during install if version 1.1 of the .NET Framework is detected. ESRI only supports the interop assemblies that ship with ArcGIS. You can identify a valid ESRI assembly by its public key (8FC3CC631E44AD86). The ArcGIS installation program also installs the Microsoft Stdole.dll PIA, providing interop for Object Linking and Embedding (OLE) font and picture classes, which are used by some ESRI libraries. For more information about this Microsoft PIA, see the Microsoft Developer Network Knowledge Base article number 328912.

COM wrappers
The .NET runtime provides wrapper classes to make both managed and unmanaged clients believe they are communicating with objects within their respective environment. When managed clients call a method on a COM object, the runtime creates a runtime-callable wrapper (RCW) that handles the marshaling between the two environments. Similarly, the .NET runtime creates COM-callable wrappers (CCWs) for the reverse case, in which COM clients communicate with .NET components. The following illustration outlines this process:

Exposing .NET objects to COM
When creating .NET components for COM clients to use, observe the following guidelines to ensure interoperability:

• • • • • •

Avoid using parameterized constructors. Avoid using static methods. Define event source interfaces in managed code. Include HRESULTs in user-defined exceptions. Supply globally unique identifiers (GUIDs) for types that require them. Expect inheritance differences.

For more information, see Registering .NET components with COM and Registering .NET components in COM component categories and also review Interoperating with Unmanaged Code in the Microsoft Developer Network (MSDN) help system.

Performance considerations
COM Interop adds a new layer of overhead to applications, but the overall cost of interoperating between COM and .NET is small and often unnoticeable. However, the cost of creating wrappers and having them marshal between environments does add up. If you suspect COM Interop is the bottleneck in your application's performance, try creating a COM worker class that wraps all the chatty COM calls into one function that managed code can invoke. This improves performance by limiting the marshaling between the two environments. For more information, see Performance of ArcObjects in .NET.

COM to .NET type conversion
Generally speaking, the type library importer imports types with the name they originally had in COM. All imported types are also added to a namespace that has the following naming convention: ESRI.ArcGIS.<name of the library>. For example, the namespace for the Geometry library is ESRI.ArcGIS.Geometry. All types are identified by their complete namespace and type name. Classes, interfaces, and members All COM coclasses are converted to managed classes; the managed classes have the same name as the original with "Class" appended. For example, the runtime-callable wrapper for the Point coclass is called PointClass. All classes also have an interface with the same name as the coclass that corresponds to the default interface for the coclass. For example, the PointClass has a Point interface. The type library importer adds this interface so clients can register as event sinks. See the following Class interfaces section for more information. The .NET classes also have class members that .NET supports, but COM does not. Each member of each interface the class implements is added as a class member. Any property or method a class implements can be accessed directly from the class rather than having to cast to a specific interface. Since interface member names are not unique, name conflicts are resolved by adding the interface name and an underscore as a prefix to the name of each conflicting member. When member names conflict, the first interface listed with the coclass remains unchanged. See the following code example:

ESRI.ArcGIS.Geometry.PointClass thePt = new ESRI.ArcGIS.Geometry.PointClass(); thePt.PutCoords(10.0F, 8.0F); ESRI.ArcGIS.Geometry.IGeometry geom = thePt.Buffer(2.0F); MessageBox.Show(geom.Dimension.ToString());


Dim thePoint As New PointClass thePoint.PutCoords(10.0, 11.9) Dim geom As IGeometry = thePoint.Buffer(2.0F) MessageBox.Show(geom.Dimension.ToString()) Since interface member names are not unique, name conflicts are resolved by prefixing the interface name and an underscore to the name of each conflicting member. When member names

conflict, the first interface listed with the coclass remains unchanged. For example, the MapClass has members called AreaOfInterest and IBasicMap_AreaOfInterest. Properties in C# that have by-reference or multiple parameters are not supported with the regular property syntax. In these cases, it is necessary to use the accessory methods instead. See the following code example:

ILayer layer = mapControl.get_Layer(0); MessageBox.Show(layer.Name); Events The type library importer creates several types that enable managed applications to sink to events fired by COM classes. The first type is a delegate named after the event interface plus an underscore followed by the event name, then the word EventHandler. For example, the SelectionChanged event defined on the IActiveViewEvents interface has the following delegate defined: IActiveViewEvents_SelectionChangedEventHandler. The importer also creates an event interface with an "_Event" suffix added to the original interface name. For example, IActiveViewEvents generates IActiveViewEvents_Event. Use the event interfaces to set up event sinks. Non-OLE automation-compliant types COM types that are not OLE automation compliant generally do not work in .NET. ArcGIS contains a few noncompliant methods, and these cannot be used in .NET. However, in most cases, supplemental interfaces have been added that have the offending members rewritten compliantly. For example, when defining an envelope via a point array, you can’t use IEnvelope.DefineFromPoints; instead, you must use IEnvelopeGEN.DefineFromPoints. See the following code example:

Dim pointArray(1) As IPoint pointArray(0) = New PointClass pointArray(1) = New PointClass pointArray(0).PutCoords(0, 0) pointArray(1).PutCoords(100, 100)

Dim env As IEnvelope Dim envGEN As IEnvelopeGEN env = New EnvelopeClass envGEN = New EnvelopeClass 'Won't compile. env.DefineFromPoints(2, pointArray) 'Doesn't work. env.DefineFromPoints(2, pointArray(0)) 'Works.

PutCoords(100.DefineFromPoints(3.NET. All the members of the default interface of the COM class are added to this class interface. ref pointArray[0]). pointArray[0] = new PointClass().NET. it is recommended that you avoid using the class interfaces in the ESRI interop assemblies.NET adds "class interfaces" to each interop assembly. 0). If the COM class has a source interface (is the source of events). A second RCW is created that represents the underlying COM class. To allow Visual Basic developers a more seamless introduction to . . Envelope.DefineFromPoints(3. envGEN. ref pointArray).envGEN.DefineFromPoints(pointArray) [C#] IPoint[] pointArray = new IPoint[2]. which allow a programmer to instantiate a class by using its class interface. //Doesn't work. which helps a programmer to link up events.NET compilers. the type library importer in . and a programmer could instantiate a variable and access the members of its default interface without performing a specific query interface (QI) for that interface.NET. allowing COM objects to be used with this same syntax inside ." for example. they are also commonly used in code produced by the Visual Basic . then the class interface will also include all the events of this interface. a class interface RCW is created for each COM class. 100).0] Dim fnt As New Stdole. IEnvelope env = new EnvelopeClass().NET does not provide this same ability.NET Upgrade wizard or the code snippet converter in Visual Studio . the details of default interfaces were hidden from the user. . for example. the name of the class interface is the same as the COM class. //Won't compile. The class interface is linked to the class by an attribute that indicates the class to which it belongs. IEnvelopeGEN envGEN = new EnvelopeClass(). EnvelopeClass. the name of this is the same as the COM class with a suffix of "Class. the following Visual Basic 6 code instantiates the StdFont class and sets a variable equal to the default interface (IStdFont) of that class: [Visual Basic 6. When an object library is imported. pointArray[1] = new PointClass(). This section explains a little more about class interfaces. env.StdFont However.NET. //Works. This attribute is recognized by the . as they may change in future versions of ArcGIS. For example. pointArray[0].PutCoords(0. However. env. Class interfaces Class interfaces are created to help Visual Basic programmers transition to . pointArray[1].DefineFromPoints(ref pointArray). In Visual Basic 6.

PutCoords method from this class interface: [C#] ESRI. When using Visual Basic .ArcGIS.Point thePt = new ESRI.Point = New ESRI.Geometry. the next implemented interface is exposed on the class interface instead. which are never exposed on RCW classes because the members are called internally by the .PutCoords(10. [VB. it is recommended that you avoid using the previous syntax. this class is accessed by using the Point class interface.PutCoords(10. that variable can be used to access the IPoint. and the class PointClass as shown in the following screen shot: .Geometry. 8). PointClass is not shown by default but can be made visible by selecting the Show Hidden Members option.ArcGIS. For example.The exception is classes that have a default interface of IUnknown or IDispatch. therefore.Point().ArcGIS.Point() thePt.Geometry. its inherited interface IPoint. you can see more clearly the class interface Point.Geometry. the Point COM class in the esriGeometry object library lists the IPoint interface as its first implemented interface. this affects most ArcObjects classes. As most ArcObjects define IUnknown as their default interface.NET Framework runtime.ArcGIS.NET] Dim thePt As ESRI. You can view these types in the Visual Basic . thePt. and the PointClass class. In .NET Object Browser as shown in the following screen shot. 8) The inherited interface of a class interface is not guaranteed to remain the same between versions of ArcGIS. The following code example shows that by declaring a variable type as a Point class interface. which inherits the IPoint interface. In this case.NET.NET. In the C# Object Browser.

This includes any class that is inside a project that is registered for COM Interop. such as the class ID. However. It is recommended that you specify GUIDs for any class that you expose to the COM.NET to apply this attribute to an existing class with a new GUID. To maintain binary compatibility in . interface ID. The following code example shows a GUID attribute being applied to a class. developers control the GUIDs in a component by specifying them in the project's Interface Definition Language (IDL) file. you can use the GuidAttribute class to manually specify a class ID for your class. Although the exporter will generally keep using the same GUIDs on subsequent exports. Each component is associated with a number of globally unique identifiers (GUIDs). this behavior is not guaranteed. This has the adverse effect of having to reregister the components in their appropriate component categories after each recompilation. If you do not specify a GUID. the type library exporter will automatically generate one when you first export your components to the Component Object Model (COM). when this flag is not set.NET. the Binary Compatibility compiler flag ensures that components maintain the same GUID each time they are compiled. A component has binary compatibility with a previous version of the same component if clients compiled against one version will run against the new version without the client needing to be recompiled. By specifying a GUID.About binary compatibility Most ArcGIS developers are familiar with binary compatibility. Maintaining the same GUIDs between component versions indicates that the versions are binary compatible. helping to control binary compatibility. In Visual C++. you control when that GUID changes. In Visual Basic 6. You can use the ESRI GuidGen add-in for Visual Studio . [C#] [GuidAttribute("9ED54F84-A89D-4fcd-A854-44251E925F09")] public class SampleClass { . and type library ID. a new GUID is generated for each class each time the project is compiled.

_ "58C9B6E4-B4F3-48dc-862A-181E566C4A31")> _ Public Class SampleClass ' End Class SummaryThis topic describes the System. you can use the ComClass attribute instead. You can add a new ComClass to your project by right-clicking the project in the Solution Explorer and selecting Add New Item. The Type class.NET] <ComClass("B676A49F-2672-42ea-A378-20C17D1F2AFF".NET application.NET. you can apply the attribute to an existing class. if you are working in Visual Basic . This attribute can be used to specify GUIDs for the class. This not only applies the attribute but also generates and adds new GUIDs for use in the attribute. When you create a new Component Object Model (COM) object in . holds information about the data and function members of a data type. and the default events interface. interface.NET via interop. each class. you would find that the sym type is SimpleMarkerSymbolClass. In the following code example. If you were to run this code. the variable holds a reference to the ISimpleMarkerSymbol interface of the SimpleMarkerSymbolClass RCW.// } [VB. An RCW can hold a reference to a COM object inside a .NET] <GuidAttribute("9ED54F84-A89D-4fcd-A854-44251E925F09")> _ Public Class SampleClass ' End Class Alternatively. Types and runtime callable wrappers In .NET Framework. [VB. as you might expect. a variable called sym is declared as the ISimpleMarkerSymbol interface type and is set to a new SimpleMarkerSymbolClass. [C#] . is described by its type. enumeration.__ComObject type and some issues related to it including why casting a variable sometimes fails when it appears it should succeed and creating the AppRef class in . then choosing ComClass in the Add New Item dialog box. For more information on different ways you can generate new GUIDs. as shown in the following code example. see Interoperating with COM. The type of the variable sym is retrieved and written to the debug window. Alternatively.NET. and so on. which is part of the . you get a reference to your object that is wrapped in a strongly typed runtime callable wrapper (RCW). the default interface.NET. _ "5A90404F-8D1A-4b77-8F3F-C347A97FA34C".

Display.NET] Dim sym As ESRI.Display.Symbol as ESRI.ISimpleMarkerSymbol sym = new ESRI.Display.ISimpleMarkerSymbol. However.ArcGIS.NET runtime wraps the COM object reference in a generic RCW called System.Display. Debug.Symbol Debug. [VB. When this code is compiled.__ComObject. when you access the Symbol property.GetType. the . When the code runs.__ComObject.WriteLine(sym.GetType(). [VB.WriteLine(CType(sym.GetType. This is a class internal to the . Debug.__ComObject type The difference between the two previous excerpts of code is that.ArcGIS.FullName) In a different coding situation. in the similar code that follows.Display.Display. the type of the actual class of object cannot be discovered. The System. you could get a reference to an RCW from another property or method.NET] Dim sym As ESRI.Display. Object). When the code is compiled. the Symbol property of a renderer (ISimpleRenderer interface) is retrieved. the runtime then has all the information (the metadata) that describes the exact type of the variable.WriteLine(CType(sym. where the renderer uses a single SimpleMarkerSymbol to draw.FullName). its purpose is to act as the RCW for an unknown type of COM object.GetType(). in the second example. In this case.ArcGIS. the exact type of the variable is discovered by the compiler using Reflection.ESRI. in the first.ArcGIS. the only metadata that the compiler can find is that the Symbol property returns an ISymbol reference.NET Framework that can be used to hold a reference to any kind of COM object.SimpleMarkerSymbolClass(). Although you can perform a cast to get the ISimpleMarkerSymbol interface of the sym variable (or any other interface that the symbol implements).WriteLine(sym.ISimpleMarkerSymbol sym = rend. you set the sym variable from the Symbol property of the ISimpleRenderer interface.ArcGIS. you create the symbol using the New (or new) keyword and the type SimpleMarkerSymbolClass.FullName) Although you might expect to get the same output as before.SimpleMarkerSymbolClass Debug. you will actually find that the reported type of sym is System. Casting .ArcGIS.ISimpleMarkerSymbol = New ESRI. Object).NET runtime does not have the metadata required at run time to discover exactly what the type of the variable is. the . and metadata about that type is stored in the compiled code. [C#] ESRI.ISimpleMarkerSymbol = rend.FullName).ArcGIS. For example.

whereas if the object is preexisting. [VB.NET runtime still does not have the metadata required to cast the variable to a strongly typed RCW.ArcGIS. the type of the RCW is the generic System. Sometimes when you use the new keyword to instantiate a COM object.IStyleGallery = = New ESRI.__ComObject to type <Typename>. If your code has encountered such a situation. as attempting a cast to the SimpleMarkerSymbolClass type would fail: [C#] // The following line would result in sym2 being null.ArcGIS.Display.SimpleMarkerSymbolClass sym2 = sym as ESRI.ISimpleMarkerSymbol = sym Singletons and System. Therefore.ArcGIS. even if you know the exact type of class to which you have a reference. casting to specific interfaces (as long as they are implemented on the object) will be successful.NET] ' The following line would result in a runtime error.ArcGIS. you are actually getting a reference to an object that already exists.In the second example.NET] Dim sg As ESRI.Display. The . the . See the following code example: [C#] ESRI.ArcGIS. as the cast would fail. you may receive an error such as "Unable to cast object of type System.NET] Dim sym3 As ESRI.__ComObject class is specifically designed to work with COM objects.NET Framework is unable to wrap in a strongly typed RCW an instance of an object that has previously been wrapped in the generic System. [VB.SimpleMarkerSymbol = sym However. it can always perform a query interface (QI) to any COM interfaces that are implemented by an object. Dim sym2 As ESRI. this happens when attempting to instantiate a singleton class that has previously been instantiated.Display.ISimpleMarkerSymbol sym3 = sym as ESRI.IStyleGallery sg = new ESRI.Display.Framework.SimpleMarkerSymbolClass.ArcGIS. a strongly typed RCW is created when you instantiate the COM object by using the new keyword.ArcGIS.StyleGalleryClass .__ComObject RCW. as the implicit cast would fail.ArcGIS.ArcGIS.__ComObject.ISimpleMarkerSymbol. [VB." See the following code example: [C#] ESRI. ESRI.Display.Display. because the System.__ComObject In the previous examples.ArcGIS.Framework.Display.Display.StyleGalleryClass(). This is shown in the following code example.

Object obj = Activator. . as Activator is able to get the required metadata to perform the cast. you can avoid such errors. IStyleGallery sg = obj as IStyleGallery. System.This error can occur even though you have declared your variable using the interface name rather than the class name.Framework.ArcGIS.__ComObject RCW. Sams Publishing.IStyleGallery = obj You can use this technique to instantiate the AppRef class. other ArcObjects tools written in .NET] Dim t As Type = Type.ArcGIS. ESRI. This can occur in situations beyond your control.__ComObject wrapper.NET runtime first attempts to wrap the object in the strongly typed class type (the type stated after the new keyword) before attempting a cast to the interface type.CreateInstance(t).ArcGIS.IApplication pApp = obj as ESRI. Additionally.Framework. Using the Activator class to create singletons If you use the CreateInstance method of the Activator class instead of the new keyword to instantiate singletons.GetTypeFromProgID("esriFramework. however. refer to the book by Adam Nathan. (The type is the generic System. . the .StyleGallery") Dim obj As System.NET from third parties may wrap an object in the generic wrapper.CreateInstance(t) Dim pApp As ESRI. System.Object obj = Activator. when your code instantiates an object. [VB. For example.GetTypeFromProgID("esriFramework. the AppRef class can only be created within an ArcGIS application.Display.IApplication = obj For more information about RCWs and interop.StyleGallery"). [VB.IApplication. 2002.AppRef") Dim obj As System.CreateInstance(t) Dim pApp As ESRI. you should generally always declare variables holding RCWs using an interface rather than a class Type.NET] Dim t As Type = Type.Framework.Object = Activator. The cast to the strongly typed RCW cannot succeed because the COM object has previously been wrapped in the generic System.) See the following code example: [C#] Type t = Type. causing your code to fail.ArcGIS. as in the previous example. The problem occurs because. The solution is to use the Activator class (shown as follows) to safely wrap singleton objects in a strongly typed RCW when you first get a reference to them.AppRef").CreateInstance(t).GetTypeFromProgID("esriFramework.NET and COM– The Complete Interoperability Guide.Object = Activator. See the following code example: [C#] Type t = Type.GetTypeFromProgID("esriFramework.

To ensure a COM object is released when it goes out of scope.ArcGIS.Shutdown Unexpected errors can occur when a stand-alone application attempts to shut down. For example. ComReleaser class The AOUninitialize.Shutdown function handles many of the shutdown problems in stand-alone applications.EventArgs) Handles MyBase. The memory could not be read. as it can help ensure your object references are disposed of when your code terminates. Internally.ReleaseComObject section and the Microsoft Developer Network (MSDN) documentation on ReleaseComObject for more information.ADF assembly.ADF.NET including how memory is managed in the two different models. The ComReleaser class can be found in the ESRI. It is important to note that this should only be called on objects that are no longer required in any managed code in the process.COMSupport. a static Shutdown function has been added to the ESRI. particularly relating to applications containing the Engine Controls.ArcGIS.ArcGIS. you may have experienced errors on exit from an ArcGIS Engine application hosting a MapControl with a loaded map document.AOUninitialize.NET] Private Sub Form1_Disposed(ByVal sender As Object. System.ArcGIS. For example. preventing the correct unloading of the COM libraries from the process when it shuts down.COMSupport. This function can help avoid such errors by ensuring that COM references no longer in use are unloaded prior to the process shutdown. with an error message similar to The instruction x references memory at x.Shutdown in the Form Disposed event handler. ByVal e As System. } This function can only help with unloading libraries for which no COM objects still exist.ADF.InteropServices.ADF library reference documentation for more information on using this class. Such an error will be outside the scope of any error handling statements in your code. The following code example shows how you can use this function in the Disposed method of a form: [VB. To help stop such errors. it is most useful to apply this function after any COM objects have been disposed of.SummaryThis topic provides information on the interoperation of Component Object Model (COM) and . this class uses the ReleaseCOMObject method on the System.dll assembly.ArcGIS. These errors can often result when COM objects remain in memory longer than expected.EventArgs e) { ESRI.ADF.Runtime. you can use the ComReleaser class. However. You may want to use this class to keep track of your COM objects.Shutdown().Shutdown() End Sub [C#] private void Form1_Disposed(object sender. See the following Marshal.AOUninitialize. it is useful to place your call to AOUninitialize.Marshal class to ensure COM object references are terminated. .Disposed ESRI. you may still experience problems where COM objects remain in memory in your application and need to be explicitly released from memory. in an ArcGIS Engine windows application with a startup form. See the ESRI. AOUninitialize.

For example. you are able to determine more precisely when you can release the COM objects. you may need to explicitly release certain COM objects to free the resources held by the COM object. If you are creating a stand-alone application and have the advantage of controlling all the managed code in that application.ArcGIS. which is loaded into an ArcGIS Desktop application.InteropServices namespace in the .Runtime. error messages may occur on application shutdown. If no other COM objects hold a reference to the underlying COM object at that point.Display. the COM runtime will also clear up the COM object itself. repeatedly opening geodatabase cursors against a personal geodatabase without ensuring the last cursor was freed may cause an error indicating that no more tables can be opened.IStyleGallery = New ESRI.Marshal. The following example code shows how you can call ReleaseComObject to release a StyleGallery object.Framework. which is part of the System. and so on). if you have stored a reference to MxDocument and call ReleaseComObject on that reference. then any other managed in-process component is unable to access the MxDocument object from that point on. as object references remain in memory. To fully free the COM object underlying an RCW from memory at a deterministic point. Calling ReleaseComObject affects all managed references to a COM object in the current process.NET code. You should be particularly careful when calling this method from an in-process component. such as a dynamic-link library (DLL). In other situations.NET Framework.NET] Sub Main() Dim styCls As ESRI. It is not recommended that you call ReleaseComObject on any object to which another managed component may have a reference.ArcGIS. The .Runtime.ReleaseComObject In . [VB. In a case where a COM object holds system resources (file handles.Marshal. and such code should only be used when you are sure no other managed code will require further access to the object. Calling ReleaseComObject will decrease the reference count held on an RCW. it is possible to use the ReleaseComObject method on the Marshal class. For example. the StyleGallery is one such class that can often cause errors on exit if you do not explicitly release it. The code shown recursively calls ReleaseComObject until the returned value is zero. once the reference count on the RCW reaches zero (which may require repeated calls to ReleaseComObject).StyleGalleryClass ' Use the StyleGalleryClass here.InteropServices. references to COM objects are held via runtime callable wrappers (RCWs). This indicates there are no longer any managed references to the StyleGallery. Dim refsLeft As Integer = 0 Do refsLeft = System. Such problems can cause different types of errors depending on the circumstances.ReleaseComObject(styCls) Loop While (refsLeft > 0) End Sub [C#] . database connections. the RCW is marked for garbage collection. managed objects that act as proxy objects for the underlying COM object. which happens in a nondeterministic way.NET garbage collector is responsible for clearing managed objects from memory.

For example. For more information about the garbage collection process and the ReleaseComObject method. your reference on the cursor (or any other COM object) will not be released until garbage collection occurs. While the shared schema lock is in place. other applications can continue to query or update the rows in the table. do { refsLeft = Marshal.IStyleGallery. errors can vary depending on that data source. In the case of SDE data sources. they may indicate the lack of resources. The effect of the number of SDE streams exceeding the maximum is that other clients will fail to open their own cursors to query the database.NET.Framework. At this point. refer to MSDN documentation. such as shapefiles. only then will the resources be released.IStyleGallery styCls = new ESRI. you may find that these resources can accumulate up to the maximum allowable limits for the data source. This is useful when dealing with cursor objects and other geodatabase objects that hold resources as well as style gallery enumerators and item objects. rather than waiting for the garbage collection process to perform the cleanup. eventually exhausting the maximum allowable streams. in the ESRI libraries. and if the application has multiple clients. the server may exhaust its available memory. the cursor holds on to an SDE stream. In the case of filebased data sources. In . } You may also want to call ReleaseComObject on objects that you create in a loop (such as enumerators). but they cannot delete the feature class or modify its schema. See the following Releasing geodatabase cursors section for more information. int refsLeft = 0.ReleaseComObject(styCls). // Use the StyleGalleryClass here. each may get and hold on to an SDE stream. Therefore. because you can be sure the objects are freed in a timely manner.ArcGIS. The effect of these locks is that the data may be unavailable to other applications until all the references on the cursor object are released. a geodatabase cursor can acquire a shared schema lock on a file-based feature class or table on which it is based or can hold on to a Spatial Database Engine (SDE) stream. . which prevents other applications from accessing the file for read or write purposes.ArcGIS.StyleGalleryClass()as ESRI.Display. In other cases.ArcGIS. update cursors acquire an exclusive write lock on the file. For example.Display. if you are performing a number of operations using objects that lock database resources. Releasing geodatabase cursors Some objects may lock or use resources that the object frees only in its destructor. } while (refsLeft > 0).private void MyFunction() { ESRI.

Similarly. you should always release cursor objects in this way (for example.InteropServices. forcing garbage collection is not generally recommended. as the call itself can be slow and forcing the collection can also interfere with optimum garbage collector operation.InteropServices. System. // Use the feature cursor as required. objects that are long lived that you did not create in your own code. } .ToString(). IFeatureCursor featCursor = featClass.Collect.WhereClause = "Area = " & i. you should not free any object that needs to be accessed from elsewhere within .NET method to initiate garbage collection at a known point.Search(qu.NET] For i As Integer = 1 To 2500 Dim qu As IQueryFilter = New QueryFilterClass qu. for example. the exceptions may not be obviously related to the lack of resources. You may also want to release objects implementing ISet as well as geodatabase enumerators. Calling GC. the .WhereClause = @"Area = " + i.ReleaseComObject(featCursor). The following code example demonstrates this pattern: [VB.ToString() Dim featCursor As IFeatureCursor = featClass. in an ArcGIS Desktop or Engine application. relying on garbage collection to release references on objects holding resources will result in cursors and their resources not being released in a timely manner.Marshal.Search(qu. relying on garbage collection to release such references can result in your application or other applications receiving errors. System. ArcGIS Desktop and Engine If you are using geodatabase cursors in your code.Runtime.ReleaseComObject(featCursor) Next i [C#] for (int i = 1. i++) { IQueryFilter qu = New QueryFilterClass(). In other cases. true).ReleaseComObject.accessing personal geodatabase tables may indicate only 255 connections to tables are allowed. qu.NET code. i < 2500. True) ' Use the feature cursor as required. In a Web application or Web service servicing multiple concurrent sessions and requests.Runtime. you may want to call Marshal. The correct approach is to free objects that may hold resources by using Marshal. However. objects implementing IFeatureCursor).Marshal. Typically. can be used to clear any pending objects.ReleaseComObject on each cursor object when you no longer require it to ensure any geodatabase resources are released in a timely manner. Again.

True) Dim som As IServerObjectManager = serverConn. Use the ManageLifetime method to add your COM object to the set of objects that will be explicitly released when the WebObject is disposed of. ArcGIS Server To ensure a COM object is released when it goes out of scope. As mentioned previously. For more information on specific singletons used in ArcGIS Engine. COM objects left in memory for an extended period after use may cause problems and errors on shutdown. regardless of the specific API you are using. Singletons are a type of COM object that have only one instance per process.ManageLifetime(fcursor) Dim f As IFeature = fcursor.NextFeature() . f = fcursor.NET APIs. ByVal e As System.Object doSomething_Click(ByVal sender As System. release the singleton COM object using the ComReleaser class as specified in the previous ComReleaser Class section. the WebControls assembly contains a helper object called WebObject. "MapServer") Dim mapsrv As IMapServer = ctx.NET API.FeatureClass Dim fcursor As IFeatureCursor = fClass.Map(mapsrv. For the . The following code example demonstrates this pattern: [VB. you may find your code results in errors if singletons are left hanging (a process known as pinning). it is necessary to always unpin (or release) the reference.Click Dim webobj As WebObject = New WebObject Dim ctx As IServerContext = Nothing Try Dim serverConn As ServerConnection = New ServerConnection("doug".ServerObjectManager ctx = som.NET] Private SubSystem.ServerObject Dim mapo As IMapServerObjects = mapsrv Dim map As IMap = mapo.Layer(0) Dim fClass As IFeatureClass = flayer. see Using the control commands: Singleton objects. You must scope the use of WebObject within a using block so that any object (including your cursor) that you have added to the WebObject using the ManageLifetime method will be explicitly released at the end of the using block.DefaultMapName) Dim flayer As IFeatureLayer = map.ArcGIS Engine The ComReleaser class plays an important role in working with singletons in ArcGIS Engine.EventArgs) Handles doSomething.Search(Nothing.CreateServerContext("Yellowstone". True) webobj. In the case of singletons. simply set the member variable equal to nothing.Object.NextFeature() Do Until f Is Nothing ' Do something with the feature. For non-.

get_Layer(0)as IFeatureLayer. IFeature f = null.ReleaseContext(). } } .DefaultMapName).CreateServerContext("Yellowstone".Search(null.FeatureClass. } ctx. true). IFeatureLayer flayer = map.ReleaseContext() webobj. webobj.get_Map(mapsrv.EventArgs e) { using(WebObject webobj = new WebObject()) { ServerConnection serverConn = new ServerConnection("doug". IMapServer mapsrv = ctx. IFeatureCursor fcursor = fclass. IMapServerObjects mapo = mapsrv as IMapServerObjects.Dispose() End Try End Sub [C#] private void doSomthing_Click(object sender. IServerContext ctx = som.ServerObjectManager. IServerObjectManager som = serverConn.ServerObject as IMapServer.ManageLifetime(fcursor). System.NextFeature()) != null) { // Do something with the feature. true). IMap map = mapo.Loop Finally ctx. IFeatureClass fclass = flayer. "MapServer"). while ((f = fcursor.

strings can be specified and used directly in your code. See the following code example: [C#] this.ArcGIS. If you are using. [VB.ArcGIS.NET applications.NET) function Shutdown. For more information on shutting down ArcGIS .resources file Using resources with localization How to use resources with localization Developer sample with localization Embedding a default . and WebPageLayout objects also have a ManageLifetime method.ArcGIS.NET] . for example. the AOUninitialize class provides the static (shared in VB. WebGeocode.NET Creating a .ADF. a WebMap and scope your code in a using block.ADF.resources file in a project Creating .ArcGIS.ADF.Shutdown() In this topic • • Using strings and embedded images directly (without localization) Creating resource files • • • • • • • • • • • Resources in Visual Studio . [VB. see How to release COM references.NET applications.NET applications (ESRI. For example.dll files for cultures supported by a project Using strings and embedded images directly (without localization) If your customization does not support localization now and won't in the future.AOUninitialize.ADF) To help unload Component Object Model (COM) references in .Shutdown().AOUninitialize.COMSupport.COMSupport namespace in the ESRI.textBox1.NET] ESRI.NET SDK tools Creating resource files programmatically Compiling a .resources.ADF. See the following code example: [C#] ESRI.COMSupport.The WebMap.dll assembly. This class is part of the ESRI.ArcGIS.resx file for string resources Creating resource files for image resources Creating resource files using . you can use strings and images directly without resource files. About shutting down ArcGIS . you can rely on these objects to explicitly release objects you add with ManageLifetime at the end of the using block.resx file into a .Text = "My String".

bmp. 4.NET] Dim res() As String = GetType(Form1). [VB.GetManifestResourceNames(). such as .TextBox1.Bitmap( _ GetType(Form1).GetManifestResourceNames() If (res. then click Add Existing Item.Drawing.GetManifestR esourceStream(res(0))) .Assembly. .GetLength(0) > 0) Dim bmp As System.Assembly. .Text = "My String" Image files... can be embedded in your assembly as follows: 1.Bitmap bmp = new System. . In the Add Existing Item dialog box.GetManifestResourceStream(res[0])). Right-click the project in the Solution Explorer.Assembly.Drawing.GetLength(0) > 0) { System. and so forth.Drawing.png.Bitmap = New System.Bitmap(GetType() .. For example. click Add. See the following screen shot: Now you can reference the image in your code. then press F4 to display its properties. browse to your image file and click Open. 2.Drawing. . In the Solution Explorer.Me. Set the Build Action property to Embedded Resource.. if (res. the following code example creates a bitmap object from the first embedded resource in the assembly: [C#] string[] res = GetType(). 3.jpg. select the image file you just added.Assembly.

resources module embedded in the main assembly. Right-click the project name in the Solution Explorer. Various options exist for how you can create resource files. you can still use resource files instead of images and strings directly as previously described. you should be familiar with the process of creating resource files for your . Creating a . cursors.resx and can contain any kind of data (images. you can use Visual Studio . These XML files have the extension .resx file for string resources If you're only localizing strings (not images or cursors). Binary .resources files can be embedded by the compiler into either the main project assembly or a separate satellite assembly that contains only resources. click Add.resx file that will be compiled automatically into a . Resx files are compiled to . 1. which are binary representations of the resource data.NET to create a new . click Assembly Resource File in the Templates area.Creating resource files Before providing localized resources. Open the new . In the Add New Item dialog box.resources files. then click Add New Item. Even if you don't intend to localize your resources. See the following screen shot: 3.NET Visual Studio . Resources in Visual Studio .resx file in Visual Studio and add name-value pairs for the culture-specific strings in your application. 2. and so forth) as long as the data is converted to the American Standard Code for Information Interchange (ASCII) format.NET projects. See the following screen shot: .NET projects use an Extensible Markup Language (XML) based file format to contain managed resources.

or cursors to a resource file in .NET that can help you with resource files.NET integrated development environment (IDE) can only be used to add string resources.resources module inside your main assembly.resx file will be compiled to a .NET installation. . You can find information on building the SDK samples under the SDK subdirectory of your Visual Studio .NET Framework Software Developer Kit (SDK) sample can be used to add images. When you compile your project. image lists. Creating resource files for image resources The process of adding images. and strings to a resource file. The ResEditor . because the tools currently available in the Visual Studio .resx or . For more information.NET SDK tools A number of sample projects are available with Visual Studio . Creating resource files using . icons. Files can be saved as either . see Resource Tools. the .NET Framework SDK sample is provided by Microsoft as source code. icons. A list of the available tools for working with resources can be found on the Microsoft Web site. You must build the sample first if you want to create resource files using this tool. The tool cannot be used to add cursor resources.NET is more complex than creating a file containing only string values.4.resources files. See the following screen shot: The ResEditor .

cur) rsxw.ResXResourceWriter("en-AU.Close() Compiling a . rsxw.Drawing.Windows.cur").Image img = (System.cur") Dim rsxw As New System.Drawing.Resources.ResXResourceWriter rsxw = new System. System. .bmp").NET framework SDK samples and tools.resx files can be compiled to binary .Resources. img) rsxw.Windows.NET] Dim img As System. You can create binary .Cursor("Pencil.resx") rsxw. rsxw.Bitmap("ABitmap.Forms.Drawing. System.AddResource("MyBmp_jpg". first. then add resources individually using the AddResource method.NET framework). cur).Resources.AddResource("Mycursor_cur". [VB. The basic usage of the two classes is similar. and add a bitmap and cursor to the file: [C#] System.Drawing.resources file XML-based .resources files programmatically by using the ResourceWriter class (also part of the .Bitmap( "ABitmap.resx file into a . The following code example demonstrates how you can create a new .NET framework).Forms.Windows. System.resx file using the ResXResourceWriter class.resx"). These classes will allow more flexibility to add the kind of resources you require.bmp").Close().Cursor cur = new System.Cursor("Pencil.AddResource("MyBmp_jpg". cursors.Bitmap)new System.Forms.Drawing.resources files by using either the Visual Studio IDE or the ResX Generator (ResXGen) sample in the tutorial. for example.Drawing. create a new resource writer class specifying the file name.ResXResourceWriter( "en-GB.Creating resource files programmatically You can create XML . These classes can be useful if you want to add resources that cannot be handled by the .resx files containing resources programmatically by using the ResXResourceWriter class (part of the . rsxw. img).Image) Dim cur As New System.Image = CType(New System.AddResource("Mycursor_cur".

the sample only requires localized strings.resources file independently of the build process by using the .resources files containing culture-specific strings and images for each culture. 2. Strictly speaking. 2.resources file in a project 1. Swiss-French resources would be contained in a fr-CH subdirectory. If a satellite assembly for the appropriate culture cannot be found.resources files are compiled into satellite assemblies (using the naming convention <Main Assembly Name>.resources file and create a separate localized file for each culture you want to support. Take the default . For example.resources. A batch file named buildResources.resx file into a . then click Add Existing Item to navigate to your .resources file in the main assembly. the default resources (those embedded in the main assembly) will be used instead. The sample can be found in the Developer Samples\ArcMap\Commands and Tools\Pan Tool folder. Culture-specific .NET and C# examples of the Pan Tool developer sample illustrate how to localize resources for German language environments. choose the file you just added and press F4 to display its properties.dll files for cultures supported by a project 1.resources. you should construct separate .resources.resources file in your project.NET. . • Using resources with localization The following explains how you can localize resources for your customizations.NET project that uses resources.resources file. see Creating .resx PanToolCS. a combination of a specific language and country/region is called a culture. Right-click the project name in the Solution Explorer.resx or ." and the Swiss dialect of French is indicated by "fr-CH." If you want your project to support various cultures (languages and dialects).bat is provided in the Pan Tool sample to facilitate creating the default .dll files for cultures supported by a project on how multiple resource files are used for localization.NET framework SDK command resgen. In the Solution Explorer. When an application runs. Developer sample with localization The Visual Basic . Set the Build Action property to Embedded Resource.resources files and the culture-specific satellite assemblies. Embedding a default .NET embeds the default .• Any . click Add. How to use resources with localization In .resources. . 3. as the following example shows: resgen PanToolCS.dll) and placed in subdirectories of the main build directory. it automatically uses the resources contained in the satellite assembly with the appropriate culture. to serve as illustration. For example. The appropriate culture is determined by the Windows settings.resx file included in a Visual Studio project will be compiled to a . Ensure you have a default . the American dialect of English is indicated by the string "en-US.resx or . This will ensure that your application always has a set of resources to fall back on if there isn't a resource .resources module when the project is built. For more information.resx or . Creating . but the images have been changed for the "de" culture as well.dll for the culture in which your application runs. When you build a . You can convert a . The subdirectories are named after the culture of the satellite assembly they contain.

and perform some simple geometry operations in a loop when the button is clicked. Add the new resource files to the project. The main (default) resources file will be embedded in the main assembly.Geometry.resources. create a Windows application in C# or VB. Example 1: Looping ArcObjects geometry operations To analyze the performance of ArcObjects in . The example code was run on a machine with a processor speed of 3 GHz. 4. When you call the members of the object in .ArcGIS. [VB. an intermediate layer is needed. <BaseName>.NET process to the COM process. the RCW object creates the appropriate COM object.NET Framework contains the Interop application programming interface (API). allowing the . System.• • 3.NET.NET.NET: [C#] ESRI.Point(). The following code example shows this comparison. which you may want to consider when creating applications. define a COM object in . The satellite assemblies will be placed in subdirectories under the directory holding your main assembly.resx or <BaseName>.NET.Point() An RCW object is defined instead of the COM object itself. with NET Framework version 2.Point pt = new ESRI. Localized resource files should be named according to their culture.NET. the parameters of the method call will be marshaled from the . Build the project.NET.Geometry. The subdirectories will be named by culture.ArcGIS. About performance of ArcObjects ArcObjects is based on the Component Object Model (COM).0.NET but demonstrates possible differences in the speed of execution of ArcObjects code. ensuring each one has its Build Action set to Embedded Resource.Geometry.ArcGIS.NET] Dim pt As ESRI.Point = New ESRI. For .NET and COM. for example.NET 2005. see the Microsoft Developer Network (MSDN) tutorial Resources and Localization using the . add a form with one button.<Culture>. Each file should contain resources with the same names. the Value of each resource in the file should contain the localized value. as compared to the actual running time of your ArcObjects code. The . The Interop API provides Runtime Callable Wrappers (RCWs) as the mechanism that allows the use of COM objects in . When the object is instantiated (using the new keyword). using Visual Studio .NET objects to interoperate with COM objects.ArcGIS.NET Framework SDK. [C#] private void button1_Click(object sender. As shown in the following code example. First.<Culture>. For more information on working with resources in . you need to consider the overhead of creating wrapper objects and marshaling. This example code is not intended as a benchmark of ArcObjects performance in . which acts as the intermediate layer between .NET runtime to locate the resources appropriate to the culture in which the application runs. and the return value will be marshaled back.EventArgs e) .Geometry. The compiler and linker will create a separate satellite assembly for each culture.

ILine = New ESRI. int tick1.ArcGIS. } [VB.PutCoords(1.ArcGIS.Point p1. 4).NET] Private Sub Button1_Click(ByVal sender As System.Forms. ESRI.ArcGIS.ArcGIS. p2 = new ESRI.Geometry.Geometry. i++) { pLine.PutCoords(p1. p1 = new ESRI.Geometry. // Time a loop of geometry operations.PutCoords(1.Geometry. i <= 10000000.MessageBox.IPoint p1 = New ESRI.IPoint p1. p2 As ESRI.Point p2 = New ESRI.EventArgs) Handles Button1.Geometry.Point().Geometry.Environment.{ // Prepare the objects.Point().Click ' Prepare the objects.Geometry. p2).PutCoords(3.ToString()).Geometry.Object.TickCount.Geometry. tick3.TickCount. tick2. tick3 = tick2 .Windows.PutCoords(3.Environment.tick1.ArcGIS. System. 2).ArcGIS.Line(). for (long i = 0. ByVal e As System. p1. 2) p2. Dim p1. p2.Show(tick3.Geometry. ESRI.ArcGIS.Line . tick1 = System.ArcGIS.ArcGIS. 4) Dim pLine As ESRI.ILine pLine = new ESRI. p2. } tick2 = System.ArcGIS.

In cases like this. tick3). use Visual C++ to create a standard executable (EXE). . i++) { ipLine->PutCoords(ipP1.tick1 System. MB_OK).0. p2) Next i tick2 = System.0. _stprintf(msg. _T("Total Time: %ld ms"). For comparison. tick3.4. _T("").PutCoords(p1. tick3 = tick2 . using the following code example. TCHAR msg[255]. ipP2). tick3 As Integer tick1 = System. the time spent in the interoperation between COM and .' Time a loop of geometry operations. the previous example is an extreme case. WORD wID. This equivalent Visual C++ code takes approximately 5 seconds to execute. } return 0. making the code about six times faster than . msg.NET Framework TickCount property. Dim tick1.NET. tick2. BOOL& /*bHandled*/) { IPointPtr ipP1(CLSID_Point).tick1. ::MessageBox(0. ipP1->PutCoords(1.2.TickCount Dim i As Integer For i = 0 To 10000000 pLine. DWORD tick1. IPointPtr ipP2(CLSID_Point). tick2.) [VC++] LRESULT OnOK(WORD /*wNotifyCode*/. } tick2 = ::GetTickCount(). performing a small operation thousands of times that executes very quickly. HWND /*hWndCtl*/.NET.0). ILinePtr ipLine(CLSID_Line).ToString()) End Sub This code took an average of approximately 30 seconds to run in C# and VB.TickCount tick3 = tick2 . However. ipP2->PutCoords(3.NET is likely to dominate the total running time.Show(tick3.Forms. i<10000000L. for(long i=0L.MessageBox. tick1 = ::GetTickCount().Environment.0).Environment. (The GetTickCount Windows API call replaces the .Windows.

ArcGIS. then populated with features by importing the shapefiles that comprise the sample data. no difference is seen in performance between the environments.ipPt2).dll" #using "C:\Program Files\ArcGIS\Dotnet\ESRI. this code consists of operations that take longer to execute. the execution time can be greatly reduced. A personal geodatabase is first created.NET— large iterations of small operations. i < = 10000000. How can the . you can create a proxy class in Managed C++. this code takes an average of 25 seconds to run. } }. IPointPtripPt2(CLSID_Point). This class will contain a function that performs the loop of geometry operations. considering the extra instructions required for the interop layer? The VB6 code is executed by the Visual Basic runtime. You have the choice to write a proxy class in Managed C++ where you can place ArcObjects code that uses the interop layer heavily. to perform the previous operations. } public: int line_test() { IPointPtr ipPt1(CLSID_Point).NET. Subtypes are created.NET code execute just as quickly. } . 3). ipPt1->PutCoords(1. Once you expose this code as public methods.NET code. i++) { ipLine->PutCoords(ipPt1. which can be less efficient than the . and in VB6. A composite relationship class is also created. For example.System. for(long i = 0.NET Framework. Example 1 revisited: Creating a proxy class in Managed C++ Realistically.Example 2: Creating and populating a geodatabase Consider instead example code that creates and populates a personal geodatabase. For this example then.dll" namespace MCpp { public __gc class AOWrapper { public: AOWrapper() { ::CoInitialize(0). you may need to perform the kind of operation that took so much longer in . In contrast to Example 1.Geometry. Since the interop layer is only required when you call the methods of this proxy class. Do you have to take the performance hit? Not necessarily. } ~AOWrapper() { ::CoUninitialize(). } return 0. 2). ILinePtr ipLine(CLSID_Line). you can call the methods from your C# or VB. and a geometric network with connectivity rules is built. ipPt2->PutCoords(2. the execution time also averages approximately 25 seconds.ArcGIS. [C++] #using "C:\Program Files\ArcGIS\Dotnet\ESRI. In C# and VB.

ByVal e As System.MessageBox.Windows.PutCoords(p1.Show(tick3.ToString()).dll created previously): [C#] private void button1_Click(object sender.Forms.TickCount.NET may add additional functionality that may alter this situation. System.ToString()) End Sub Using the proxy class written in Managed C++.Windows. tick2.AOWrapper = New MCpp.Show(tick3. tick2 = System.AOWrapper() tick1 = System.tick1 System. tick3 As Integer Dim w As MCpp.Click Dim tick1.TickCount. } [VB. so it needs to run in a trusted environment.Forms. One disadvantage of using Managed C++ is that the assembly written in Managed C++ cannot use the verification mechanism of .NET application using code like the following (remember to add a reference to the . tick2. Future releases of Visual C++ . p2) Next i tick2 = System.line_test(). w.Environment. MCpp. the average running time is improved to approximately 5 seconds.Environment.NET] Private Sub Button1_Click(ByVal sender As System. tick1 = System.Environment. tick3 = tick2 .TickCount tick3 = tick2 .Object.tick1.Environment.AOWrapper w = new MCpp. tick3.TickCount For i = 0 To 10000000 pLine.NET security.EventArgs) Handles Button1.AOWrapper().MessageBox. System.This function can be called from a .EventArgs e) { int tick1. .

writing Managed C++ proxy classes for parts of your code could provide a solution. which may or may not be the interop time. [C#] IPoint pointOne = new PointClass(). Variables of value type objects hold the object bits themselves and have "copy-on-assignment" behavior.NET will not significantly deteriorate the performance of your code. . If you find the interop is indeed the bottleneck. creating a new instance of a Point with comparable data to the first Point. the following code example creates two variables that point to the same object in memory. That is. To actually copy the Point object. IPoint pointTwo = pointOne. About implementing cloning Cloning is the process of copying an object—the creation of a new instance of a class representing information equivalent to the original instance.NET] Dim pointOne As IPoint = New PointClass() Dim pointTwo As IPoint = pointOne See the following illustration of two variables that point to the same object in memory: Copying an object is more complex than simply assigning a new variable. Variables of reference (ref) type are actually pointers to memory. See the following code example: [C#] IClone clone = pointOne as IClone. Implementing cloning Summary. developing in . you are actually creating another pointer to the same memory. This topic explains how to create a new copy of an object and hold it in a variable. For most ArcObjects applications. [VB.NET objects are of two types: value and reference. Creating a copy of a particular object is more complex than simply assigning a new variable. For example. use the IClone interface.Clone()as IPoint.The key to improving performance is to find the performance bottleneck of your application. pointTwo = clone. when you create a new variable of ref type and assign it to an existing object.

Cloning in ArcGIS The technique shown previously is used extensively in ArcGIS by ArcObjects classes that implement the IClone interface. before the application passes an object to a property page. You can find other examples of how cloning is used by searching the samples included in the ArcGIS Developer Help. The object resulting from the cloning process will be called the clonee. the cloning process is . it clones the object. the IFeature. Shallow and deep cloning For a simple object whose members contain only value type information. Terminology used in this section—The original object will be referred to as the cloner.[VB. the object that performs the cloning operation. Each class that implements cloning decides how to clone itself. for example.ShapeCopy property. Another use of cloning in ArcObjects is by methods or properties that specifically return a copy of an object.NET] Dim clone As IClone clone = Ctype(pointOne. IPoint) See the following illustration of the IClone interface: Cloning creates a new instance in memory. the properties of the cloned object are set into the original object. Copying members: Values and object references The details of the clone operation are encapsulated in the class implementation—the class regulates which of its members should be copied and how they should be copied. For example. If the OK or Apply button is clicked.Clone(). IClone) pointTwo = Ctype(clone.

SpatialReference). as it is logical for all geometries in a feature class to hold a reference to the same spatial reference object. which should not be duplicated directly to another object. In many cases. See the following illustration that shows the different levels of cloning: shallow and deep: Both shallow and deep cloning are used by ArcObjects classes. This is decided on a case-by-case basis. is that of a graphic element. it is referred to as a deep clone. a referenced object may hold references to yet more objects. on the geometries in a feature class. For example. There is no simple rule for deciding whether an object reference should be copied or the referenced object itself should be cloned. The spatial reference can then be changed by a single method call. Cloning the geometry produces an object with a reference to the same underlying spatial reference object. the cloning process becomes more complex. it is sometimes known as a shallow clone. For each of its private members. a class needs to make the appropriate choice between shallow or deep cloning. Every geometry has an object reference indicating its coordinate system (for example. IGeometry. only the object reference is copied. as does the layer itself. If new instances of each referenced object are created. which in turn hold references to other objects. handle device context (HDC). keep in mind that some members should not be directly copied at all —a window handle (hWnd).relatively simple. The clonee object is independent of the cloner. For an object whose members contain object references. You must be careful when cloning objects that hold object references. it is inappropriate to clone an object that contains this type of instance-specific information. and the values of all the members of the clonee are set to equal the values of the cloner. the geometry and symbol properties of the clonee are entirely separate from the original object's geometry and symbol. it is logical to simply copy an object reference to the new object. contain instance-specific information. and Graphical Device Interface (GDI) resources. An example of a deep clone. Both the geometry and symbol of the graphic element are cloned. If the cloner copies only the object references to the clonee. for example. Shallow cloning is used. where referenced objects are also cloned. a workspace and feature class both have connection-specific information . and so on. and both techniques may be included in a single class. file handles. A new instance of the class is created. In most cases. for example. In other cases. Transient members When coding a clone method. and the clonee's members are set to reference these new objects. In this case.

Assign(this). First. Last. Sometimes it is more appropriate for a class not to replicate a member in its clone at all. It is quite complicated to implement IPersistStream in .Assign() to copy the properties of the cloner to the clonee. This technique requires the object to implement interfaces IPersist and IPersistStream. begin by creating a new instance of the class. If you need to implement IClone on such an object. which is the clonee. src—this is the clonee. . Assign should receive a valid instance of the class. In the first.Assign(Me) Return CType(obj.NET. The following code example is part of a sample Clonable object: [C#] public IClone Clone() { ClonableObjClass obj = new ClonableObjClass(). the cloner object will create a new instance of itself. you need to decide how you want to copy the information contained in your class—whether shallow or deep cloning is most appropriate for each member—and how to implement it. The second technique takes advantage of the object's persistence mechanism functionality by temporarily saving the object to a memory stream (ObjectStream). Implementing IClone If you implement cloning in your custom components. check src to see if it is pointing to a valid object—if not.and are not clonable. obj. this technique is not covered in this topic. You can then call IClone. If a new instance is required. IClone) End Function Clone should create a new instance of the class. raise the appropriate standard Component Object Model (COM) error. Coding IClone In general. ensure that any instance-specific information is populated from scratch instead of simply copying the instance-specific values. there are two primary techniques to implement cloning. then "rehydrating" it by loading it back from the memory stream. In the Clone method. return (IClone)obj. The Assign method receives a reference to a second instance of the class. then copy all of its members onto it.Clone Dim obj As ClonableObjClass = New ClonableObjClass() obj. therefore. the object is created from scratch.NET] Public Function Clone() As IClone Implements IClone. } [VB. return a reference to the clonee from clone. an overview window and a tool control both have a window handle and are not clonable.

").Name. m_spatialRef = srcClonable.ID. Assign the properties of src to the current instance.Assign '1. } //3.[C#] public void Assign(IClone src) { //1. Verify the type of src. m_name = srcClonable. } //2. if (!(src is ClonableObjClass)) { throw new COMException("Bad object type. If Nothing Is src Then Throw New COMException("Invalid object. //Use shallow cloning (use a reference to the same member object)."). m_version = srcClonable. ClonableObjClass srcClonable = (ClonableObjClass)src.SpatialReference) } [VB. Make sure src is pointing to a valid object. m_ID = srcClonable. Make sure src is pointing to a valid object.NET] Public Sub Assign(ByVal src As IClone) Implements IClone.Version.") End If . if (null == src) { throw new COMException("Invalid object.

Verify the type of src. See the following illustration: . Dim srcClonable As ClonableObjClass = CType(src.SpatialReference) End Sub The cloner copies values from the clonee.'2.Version m_ID = srcClonable.") End If '3. Assign the properties of src to the current instance. ClonableObjClass) m_name = srcClonable. m_spatialRef = spatialRef = srcClonable. If Not (TypeOf src Is ClonableObjClass) Then Throw New COMException("Bad object type.ID 'Use shallow cloning (use a reference to the same member object).Name m_version = srcClonable.

if (null != cloned) { m_spatialRef = (ISpatialReference)cloned.SpatialReference. See the following code example: [C#] IClone cloned = srcClonable. If SpatialReference is another object reference.NET] Dim cloned As IClone = CType(srcClonable. IClone) If Not cloned Is Nothing Then .The previous Assign code shows a shallow clone of the SpatialReference property. this is straightforward. If the object itself supports IClone. } [VB.SpatialReference as IClone. you can perform a deep clone.Clone().

Consider whether it is more appropriate to copy only an object reference (for example. The object's state is written to a temporary stream. clone the object reference.m_spatialRef = CType(cloned. scr. The cloner RandomColorRamp will have the same MinSaturation. m_spatialRef = (ISpatialReference)obj. this means the color ramp has no array of colors and cannot be used in a renderer at that point. you may want to add an internal initialization function to set the values of the class to a known initial state. In this case. Consider that some member variables may not be suitable for cloning. you must create a new object and set its properties from the existing property of the source object. It provides a mechanism to duplicate an object using an object's persistence mechanism (IPersistStream). As an example. object obj = objectCopy. and Name as the clonee. Even if the object supports IClone.SpatialReference) m_spatialRef = null. if (null == srcClonable. EndHue. consider the choice of shallow or deep cloning. However. This process is also known as a deep clone since an object will also duplicate all subobjects it contains. UseSeed. the client must set up the Colors array of the RandomColorRamp by setting its Size property and calling its CreateRamp method. When coding the Assign method. Seed. you may still want to use ObjectCopy since it does a full copy. After a call to Assign. Guaranteeing deep cloning To verify deep cloning of your object's ref members. StartHue.SpatialReference). consider how a RandomColorRamp performs an Assign. all the geometries of a feature class hold a reference to the same spatial reference). MaxSaturation.Copy((object)srcClonable. } . then rehydrated from that stream into a new instance of the object. consider using the ObjectCopy object. else { IObjectCopy objectCopy = new ObjectCopyClass(). You may decide to clear any stateful information held by the cloner before assigning the properties from the clonee. or leave the member uncopied to be set by the client code as appropriate. [C#] //Deep clone the spatial reference using an ObjectCopy. Another consideration when coding your Assign method should be the current state of both the cloner and clonee objects. You may want to clear or reinitialize any member variables before performing an Assign to ensure the result is a faithful clone. MinValue. ISpatialReference) End If If the member object does not support IClone. or deep clone. of the object. MaxValue. the object must implement the IPersistStream interface. the Assign method does not copy the value of Size or call the CreateRamp method. See the following code example: To use ObjectCopy to deep clone an object. This function could then be called from your class initialization function.

[VB. //Test that all the object's properties are the same.SpatialReference Then m_spatialRef = Nothing Else Dim objectCopy As IObjectCopy = New ObjectCopyClass() Dim obj As Object = objectCopy. ISpatialReference) End If The IsEqual method should compare the cloner (this) and the clonee (other) to see if all the members are equal in value. if (null == other) throw new COMException("Invalid object.Name == m_name otherClonable."). //2.NET] 'Deep clone the spatial reference using an ObjectCopy." if (!(other is ClonableObjClass)) throw new COMException("Bad object type.SpatialReference)) m_spatialRef = CType(obj. If Nothing Is srcClonable. it returns true if all the members are equal.SpatialReference). && if (otherClonable. ClonableObjClass otherClonable = (ClonableObjClass)other."). .ID == m_ID && ((IClone)otherClonable. See the following code example: [C#] public bool IsEqual(IClone other) { //1. Verify the type of "other. Make sure the "other" object is pointing to a valid object.IsEqual ((IClone)m_spatialRef)) )return true.Version == m_version && otherClonable.Copy(CObj(srcClonable.

You may decide that two IColor members are equal if they have the same red.NET] Public Function IsEqual(ByVal other As IClone) As Boolean Implements IClone.SpatialReference. } [VB. IsEqual should determine if two different objects have values that can be considered equivalent. yellow.ID = m_ID AndAlso _ CType(otherClonable. magenta.") End If '2. . Make sure that "other" is pointing to a valid object.IsEqual '1. and black (CMYK) color. other.Name = m_name AndAlso _ otherClonable. blue (RGB) value. If Nothing Is other Then Throw New COMException("Invalid object. If otherClonable. green. Verify the type of "other. Remember to check all the members of all the interfaces that are supported by the object." If Not (TypeOf other Is ClonableObjClass) Then Throw New COMException("Bad object type. IClone)) Then Return True Return True End If Return False End Function If a property holds an object reference that supports IClone.") End If Dim otherClonable As ClonableObjClass = CType(other. ClonableObjClass) 'Test that all the object's properties are the same. IClone).IsEqual on the member object to evaluate if it is equal to the member object of the passed-in reference.return false. use IClone. even though one is an RGB color and one is a cyan.Version = m_version AndAlso _ otherClonable.IsEqual(CType(m_spatialRef. You decide what your class considers to be equal values.

") End If . return false. //2."). If Nothing Is other Then Throw New COMException("Invalid object." if (!(other is ClonableObjClass)) throw new COMException("Bad object type.IsIdentical 'Make sure that other is pointing to a valid object."). Verify the type of "other. } [VB." if ((ClonableObjClass)other == this) return true. Make sure the "other" object is pointing to a valid object. See the following code example: [C#] public bool IsIdentical(IClone other) { //1. If Not (TypeOf other Is ClonableObjClass) Then Throw New COMException("Bad object type.") End If 'Verify the type of other. //3. compare the interface pointers to see if the cloner (this) and the clonee (other) point to the same underlying object in memory. if (null == other) throw new COMException("Invalid object.To implement IsIdentical. Test if the other is "this.NET] Public Function IsIdentical(ByVal other As IClone) As Boolean Implements IClone.

you can change the properties of many of the objects that belong to a map document. Persistence is used in ArcGIS to save the current state of documents and templates. a renderer. Prior to structured storage. ClonableObjClass) Is Me Then Return True End If Return False End Function IsIdentical should compare interface pointers to see if they reference the same underlying object. and streams Map documents and their contents are saved using a technique known as structured storage.NET. By interacting with the ArcGIS user interface.'Test if the other is this. Storage objects provide structure (for example. whereby each file contains storage objects and streams. a compound file model is used. such as a file on disk. only a single file pointer was used to access a file. See the following illustration: Implementing persistence SummaryThis document reviews the technique of persistence of custom ArcObjects implemented using . compound files. About implementing persistence Persistence is a general term that refers to the process by which information indicating the current state of an object is written to a persistent storage medium. If CType(other. Structured storage is one implementation of persistence defined by a number of standard Component Object Model (COM) interfaces. for example. However. When the map document is saved and closed. in structured storage. . the instance of the renderer class is terminated. traditional files) and can contain any type of data in any internal structure. folders on your operating system) and can contain other storage and stream objects. Structured storage. documents. you can see that the state of the renderer object has been preserved. Stream objects provide storage (for example. When the document is reopened.

. Page layout. renderers. when a user clicks Save in ArcMap. you can save map documents so you can open and work with them in previous versions of ArcGIS. • • • • • • Starting in ArcGIS 9. graphics. The following sections explain when to implement persistence and which interfaces to implement and also review a number of issues that you may encounter when persisting objects. globe documents.mxd file (if the document has previously been saved). persists many items. their members. then requests the document to persist itself to these streams. including standard and custom toolbars and commands and UIControls. Persistence in ArcGIS Structured storage interfaces specified by COM are implemented extensively throughout the ArcGIS framework. such as map documents. elements. items are stored in a style by using persistence—This can include a custom StyleGalleryItem or StyleGalleryClass. map frames. their type. it is not ubiquitous. map surrounds. Notable areas that may include custom objects are described as follows: • Map collection—Each map persists its layers. symbology. Structured storage is only used for nongeographic information system (GIS) data. Although persistence is used throughout the ArcGIS framework. Benefits of structured storage include incremental file read/write and a standardization of file structure. a single compound file can act as a mini-file system. Compound document structure ArcGIS applications use the compound document structure to store documents. spatial reference. current extent. or other map items. Understanding when persistence is used in the ArcGIS framework will help you to implement correct persistence behavior in classes you create. If there are changes to the normal template or map template. symbols. for example. For example. and contents—This may include a custom DataWindow. In this way. This may include custom layers. layout of items. map and globe templates. Current DataWindows. although larger file sizes can also result.When the stream is later reopened. in a map document. location. normal templates.1. All the objects currently running in a document or template are persisted to streams in the compound file when the document is saved. not every object will always be given the opportunity to persist itself. although other persistence techniques are also used. See the Version compatibility and Version compatibility consistency sections in this topic for more information on handling this kind of persistence in your custom components. a new object can be initialized and its state set from the information in the stream. List of styles currently referenced by the StyleGallery. ArcMap. associates them with the existing . this process is repeated for the appropriate . This process allows the current state of a document to be re-created when the file is reopened. re-creating the state of the previous object. Visible table of contents (TOC) views and their state—This may include a custom TOC view. it can be accessed by many file pointers. the MxApplication creates streams as required. and so on. Toolbars currently visible. and so on.mxt file. if floating. and their position. Registered extensions and their state—This may include custom extensions. and so on—This may include custom map surrounds or frames. ArcGIS uses structured storage to persist the current state of all the objects used by an application.

This cascading effect ensures that all the referenced objects are given a chance to persist. A persistence event cascades through the document as each object asks its members to persist themselves in turn. therefore. which is the order of their class identifiers (CLSIDs). although you don't necessarily need to write any data to the stream. If so. See the following section for more information about ObjectStreams. does not necessarily have to support persistence—no errors are raised if it does not—it depends on whether the extension needs to persist the state when a document is closed. An extension. If for some reason you decide your custom class does not need to save any information about its state to the stream but is expected to support persistence. Extensions are persisted in the order they are referenced. rendering the document unusable. You should therefore always be clear whether your class needs to implement persistence and implement correct persistence behavior if required. If one of the members references another object and that object is also persistable. Loaded extensions During the save process. Persistent classes When an object is asked to persist itself. each extension is asked to persist itself. ArcMap in the case of ArcGIS Desktop and the MapDocument class in the case of ArcGIS Engine—it is unlikely you need to create a new storage or stream. it is most likely to delegate the persistence work by asking the member object to persist itself. See the following illustration: As seen previously in document persistence. A separate ObjectStream is also created for the extension. This may include your own custom objects if they are referenced by an object that is persisted. it writes the current value of its member variables to the stream. objects are persisted to one of the streams created by the framework—for example. the values of its private member variables). The application object creates a separate stream for the persistence of each extension and the new streams are stored in the same compound file as the other document streams. errors may be raised to a user and the completion of the save may be prevented. . each class decides what defines its own state and persists only this data (in most cases. the application checks all currently loaded extensions to see if they implement persistence.If any object referenced by the map document is expected to support persistence and does not. For most custom classes you create. then you still must implement persistence.

GetClassID and IPersistVariant. When an ArcObjects object initiates a persist. See the following illustration: In addition to ensuring the integrity of object references. . This bloats the file size and also corrupts object references.Load methods.Layer. ObjectStreams are used in ArcObjects to persist objects and maintain object references correctly when persisted.ObjectStreams An object's state is not always defined by value types. implement either IPersist and IPersistStream or IPersistVariant.Save methods. The first time an object is encountered. you have already seen how a map document persists itself by calling other objects to persist themselves. Often. the same layer in a map may be referenced by IMap. Save data from an object to a stream using the IPersistStream. a reference to the existing saved object is stored. one ObjectStream can be associated with one or more streams. It also creates an ObjectStream and associates it with the stream. The ObjectStream maintains a list of objects that have been persisted to that stream.Save and IPersistVariant. two separate copies of the layer are persisted in different sections of the stream. this helps to keep file sizes to a minimum. for example. IPersistStream and IPersistVariant specify the following three basic pieces of functionality: • • • Identify the class that is being persisted using the IPersistStream. that object creates a stream for the persistence. If the same object is encountered again.Layer and ILegendItem. Only COM objects supporting IUnknown and IPersist can be stored in this way. Implementing a persistence class To create a persistable class.Load and IPersistVariant. it is persisted in the usual manner. If each of these properties is called to persist. the ObjectStream ensures that instead of persisting the object a second time.ID properties. To avoid this problem. multiple references are held to the same object. Retrieve data from a stream and set the members of an object from that data using the IPersistStream.

or alternatively. the client writes the identity of the class to the stream (using the ID). uid. you do not need to implement both interfaces. IPersistVariant… In the ID property.CLASSGUID + "}".NET] Public NotInheritable Class TriangleElementClass Implements …. Implementing IPersistVariant This interface was specifically designed for use by non C++/VC++ programmers.Implementing IPersistStream in . see Version compatibility for more information.NET is an advanced technique and is not discussed in this document. the SelectedLayer property is null. What you need to save When you implement a persistent class. Ensuring that your code can re-create the state of an instance may include storing data about public properties and any internal members of the class. the decision of what constitutes the persistent state for your class is yours to make. When a document is persisted. allowing an instance of the correct class to be created.SelectedLayer property.Value = "{" + TriangleElementClass. the identity is read first. See the following code example: [C#] public UID ID { get { UID uid = new UIDClass(). When a document is loaded. a map does not persist the IMap. create a unique identifier (UID) and set the object to the fully qualified class name of your class. return uid. See the following code example: [C#] public sealed class TriangleElementClass: � � � IPersistVariant � � � [VB. For example. upon opening a map document. the rest of the persisted data can be loaded into the new instance of the class. In any case. Then it calls the Save method to write the actual class data to the stream. this document discusses the implementation of IPersistVariant. Instead. If you want to implement version-specific persistence code. You should also decide exactly how a newly instantiated instance of the class is initialized from the data stored in the stream. exactly what data you choose to write to a stream is up to you. You may decide that certain items of state are not persisted. . At this point. use your component's class identifier (CLASSID/globally unique identifier [GUID]). All code sections in this document are taken from the Triangle graphic element sample.

Read(). } [VB.Value = "{" & TriangleElementClass.Write(m_pointGeometry).Write(m_size).NET] .Read()as IPoint.Read()..CLASSGUID & "}" Return uid End Get End Property A basic implementation of Save and Load is shown in the following code example: [C#] public void Save(IVariantStream Stream) { Stream.NET] Public ReadOnly Property ID() As UID Implements IPersistVariant. m_pointGeometry = Stream. � � � } public void Load(IVariantStream Stream) { m_size = (double)Stream.ID Get Dim uid As UID = New UIDClass() uid. Stream. m_elementType = (string)Stream. ..} } [VB. Stream.Write(m_elementType).

The stream passed to the IPersistVariant interface is a specialist stream class that implements IVariantStream. Within a call to load or save.Save Stream. the Load method must read the data from the stream in the same order the data was written to the stream in the Save method. Ensure that your data is saved and loaded in the correct order so that the correct data is written to the correct member. Streams are sequential. IPoint) … End Sub In the previous example. m_pointGeometry). one string. and one point are persisted (m_size. adapt your implementation of IPersistVariant to identify the document version that your component is being persisted to. Identifying the document version If your object can be saved to a previous version of ArcGIS but you need to account for this in your persistence code by having different persistence code for different ArcGIS versions.Read()) m_elementType = CStr(Stream. Coding the Save and Load methods may be considerably more complex if you have a large complex class. Using this interface.Write(m_pointGeometry) … End Sub Public Sub Load(ByVal Stream As IVariantStream) Implements IPersistVariant. m_elementType. you can determine the version of the document by casting to the IDocumentVersion interface on the stream object as shown in the following code example: [C#] public void Save(IVariantStream Stream) { if (Stream is IDocumentVersion) .Read().Write(m_size) Stream. This stream class is internal to ArcObjects.Read()) m_pointGeometry = TryCast(Stream.Public Sub Save(ByVal Stream As IVariantStream) Implements IPersistVariant. one double. The IVariantStream interface allows you to write COM objects and value data types to a stream using the same semantics. any value type or COM object can be written to a stream.Load m_size = CDbl(Stream.Write(m_elementType) Stream.

DocumentVersion = esriArcGISVersion. always try to cast for this interface and take appropriate action if this interface is not found. For more information. End If End If If your code is installed on machines with an installation of ArcGIS prior to 9.NET] Public Sub Save(ByVal Stream As IVariantStream) Implements IPersistVariant. you cannot guarantee that the stream passed to the persistence methods will support IDocumentVersion. . You may want to provide your own functions to discover the installed version of ArcGIS.esriArcGISVersion83 Then 'Load object as 8.{ IDocumentVersion docVersion = (IDocumentVersion)Stream 'if (docVersion. see Coding backward compatibility in persistence.1. } else { //Save object. Else 'Load object.DocumentVersion == esriArcGISVersion.Save If TypeOf Stream Is IDocumentVersion Then Dim docVersion As IDocumentVersion docVersion = CType(Stream. or you may want to rely on your internal persistence version number. IDocumentVersion) If docVersion. Techniques for persisting different data The following sections give advice on persisting certain types of data to a stream for implementors of IPersistVariant.3 version of itself. } } } [VB. As previously shown.esriArcGISVersion83) { //Save object as 8.3 version of itself.

Stream.NET] Stream. i++) { Stream.Read()as IPoint. [VB.Count.NET] m_pointGeometry = TryCast(Stream. since the Load method needs to be able to size the array and read the correct number of members from the stream to assign to the array. IPoint) Persisting arrays Often. See the following code example: [C#] Stream. i < count. } [VB.Write(m_array[i]).Count . where m_array is a member of the class: [C#] int count = m_array. for (int i = 0. write the value of the member directly to a stream in its entirety.Read(). See the following code example: [C#] m_pointGeometry = Stream. You can write each array member in turn to the stream as long as you include extra information about the size of the array. coding the persistence of an object is syntactically the same as coding the persistence of a value type.Write(m_pointGeometry) The object is reloaded in a similar way.Persisting objects If you are using IPersistVariant. In this case.NET] Dim Count As Integer = m_array. [VB. The following code example demonstrates how this technique can be used. a class member may be a dynamic array having a variable number of members.Write(count).Write(m_pointGeometry). as it is not a COM object. the stream uses the ObjectStream associated internally with the stream to persist the object. When you pass an object reference like this.

1 Stream. i < count.1 m_array. Error handling when loading . If you are persisting an object to an ObjectStream. Persisting a PropertySet You can make use of the PropertySet class to persist a class's member data. all core ArcObjects components can therefore be relied on to either persist correctly regardless of version or convert themselves to suitable replacement objects using methods similar to the IDocumentVersionSupportGEN.Stream. All core ArcObjects components deal correctly with document version persistence—they do not implement the IDocumentVersionSupportGEN interface but instead deal with this issue internally. you can store object references in an ESRI Array class and persist each of these references in the same way (the Array class is not persistable).Read()) Next Instead of using a standard dynamic array.ConvertToSupportedObject method.Read()) Dim i As Integer For i = 0 To Count .Write(m_array(i)) Next The array can now be initialized correctly in the Load method.Add(Stream. } [VB. See the following code example: [C#] int count = (int)Stream. i++) { m_array. for (int i = 0.NET] Dim Count As Integer = CInt(Stream. as this class is persistable. you should also consider that those objects also need to deal with the document version correctly. Maximum efficiency is gained during a save if you already use the PropertySet to internally store your class data.Read().Write(Count) Dim i As Integer For i = 0 To Count .Read()). Document versions and ObjectStreams The Version compatibility and Coding Save A Copy functionality sections describe how to deal with the persistence of your object at different versions of ArcGIS.Add(Stream. If during your component's persistence code you persist object references.

By coding your persistence methods to be adaptable from the beginning of your development cycle. this means you need to change the persistence signature of your class. and therefore. the next value cannot be read correctly. ArcMap continues to load the remainder of the document but without the failed layer. from ArcGIS 9. The saved documents can then be opened with a version of ArcGIS prior to that with which the document was created. For example. The effects of the error may vary according to the type of component.If you encounter an error when you attempt to read a stream. Because streams are sequential. you can ensure your component is compatible with other versions of itself when persisted. you must propagate the error to the client. your code should not attempt to continue reading. At ArcGIS 9. Forward compatibility—It is possible to write forward-compatible components. However.1 onward it is possible for users to save their documents as specific previous ArcGIS versions using the Save A Copy command. it is likely that you need to persist additional state information. complex persistence code. a client can load and save a component with a more recent version than that with which it was originally compiled. you should always be particularly careful when writing and testing persistence code. if ArcGIS attempts to load a layer from a document and fails. Unregistered classes You are responsible for ensuring that your component is registered on a machine that can open a document with a persisted version of your component.1. ArcGIS may be able to continue loading a document despite an error in your code. for example. ArcGIS 9. your component can still maintain binary compatibility and have the same ClassID. Compatibility in ArcGIS Custom components should be coded with the following version compatibility model of ArcGIS in mind: • Backward compatibility—ArcGIS document files work on the principle of backward compatibility. You can avoid many errors in your persistence code if you correctly create backward-compatible components. You should code your component regardless of this functionality and raise an error to the calling function if you cannot complete the load before exiting the Load function. as the stream pointer is not positioned correctly. Version compatibility If you develop a new version of a persistable component. Implementing forward compatibility requires much care and can give rise to long.3 or ArcGIS 9 and 9. This means that ArcGIS clients can open documents created with an earlier version of ArcGIS.2. you can save to ArcGIS 8. This allows you to fully utilize the ability when using COM to upgrade a component without needing to recompile the component's clients. probably the most common form of persistence version compatibility. • Although ArcGIS does not implement general forward compatibility (this is not generally a requirement for your components). Safe loading In some cases. See the following illustration: . Review the following Version compatibility section. so there is no option to save them to version 9 specifically. through the use of safe loading techniques. For this reason.1 map documents are directly compatible with ArcGIS 9.

esriCenterPoint. you need to store the member variables in the following code example: [C#] private double m_size = 20. private esriAnchorPointEnum m_anchorPointType = esriAnchorPointEnum.If your component works without recompilation with both the current and previous ArcGIS versions. This interface allows you to provide an alternative object. Version 1 For the first version of your triangle element. The following code example is built step by step. If your object can be saved to a previous version of ArcGIS but you may need to account for this in your persistence code. For more information. if your object cannot be persisted to a previous version of ArcGIS. Coding backward compatibility in persistence You will now look at an example of creating a backward compatible class by creating two different versions of the class. showing how to code the persistence methods each time. you should implement IDocumentVersionSupportGEN. You will create a custom graphic element that draws an equilateral triangle element on the map. see Identifying the document version. private double m_scaleRef = 0. adapt your implementation of IPersistVariant to identify the version being persisted to and make any necessary changes. private bool m_autoTrans = true. . see Coding Save A Copy functionality. For more information. see Triangle graphic element.0. For more information. then you do not need to adapt your component to ensure Save A Copy functionality.0. However. private string m_elementType = "TriangleElement".

code the Save and Load methods dependent on this number. See the following code example: [C#] public void Save(IVariantStream Stream) { Stream. . private IPoint m_pointGeometry = null. See the following code example: [C#] private const int c_Version = 1. [VB.0 Private m_anchorPointType As esriAnchorPointEnum = esriAnchorPointEnum.NET] Private Const c_Version As Integer = 1 The use of this value is the key to version compatibility. Use this constant throughout the persistence code to store the version of the class.esriCenterPoint Private m_autoTrans As Boolean = True Private m_elementType As String = "TriangleElement" Private m_elementName As String = String. private IPolygon m_triangle = null. add the c_Version private constant. The first thing written to the stream in the Save method is this persistence version value. [VB. as a graphic element must be persistable.private string m_elementName = string. Set the constant to 1.NET] Private m_size As Double = 20. private ISpatialReference m_nativeSR = null.Write(c_Version). private ISimpleFillSymbol m_fillSymbol = null.Empty.0 Private m_scaleRef As Double = 0. as this is the first version of the class.Empty Private m_nativeSR As ISpatialReference = Nothing Private m_fillSymbol As ISimpleFillSymbol = Nothing Private m_pointGeometry As IPoint = Nothing Private m_triangle As IPolygon = Nothing Now implement IPersistVariant. Before coding the persistence members of the TriangleElement class.

Write(m_pointGeometry).NET] Public Sub Save(ByVal Stream As IVariantStream) Implements IPersistVariant. } [VB. Stream. Stream.Write(m_elementName).Write(m_pointGeometry) Stream.Write(m_size). Stream.Write(m_anchorPointType) Stream. Stream.NET] Stream.Write(m_scaleRef) Stream.Write(m_autoTrans) Stream.[VB.Write(m_fillSymbol).Write(m_nativeSR).Write(m_autoTrans). See the following code example: [C#] Stream. Stream.Write(m_triangle) End Sub In the Load method. If this version number indicates a version of the persisted class that is newer than the current version of the class (c_Version).Write(m_elementType) Stream. the class does not know how to load the persisted . read the version number of the persisted class and store this value in the ver local variable.Write(m_anchorPointType).Save Stream.Write(m_nativeSR) Stream. Stream. Stream.Write(m_triangle).Write(m_elementName) Stream. Stream. Stream.Write(m_size) Stream.Write(m_fillSymbol) Stream.Write(c_Version) Then you can write the class state to the stream.Write(m_elementType).Write(m_scaleRef).

See the following code example: [C#] public void Load(IVariantStream Stream) { int ver = (int)Stream. you should ensure initialize default values for the class at the start of the Load.NET] Public Sub Load(ByVal Stream As IVariantStream) Implements IPersistVariant. In these cases.Read(). raise an exception back to the calling object. [VB. m_scaleRef = (double)Stream. [VB.Read().NET] InitMembers() Now you can read the persisted class state and set the members of the current object after checking that the saved persistence version is the version you expect (this check is useful later when you produce a new version of your component). as the minimum expected value is 1. See the following code example: [C#] InitMembers(). if (ver > c_Version || ver <= 0) throw new Exception("Wrong version!"). See the following code example: [C#] if (ver == 1) { m_size = (double)Stream.information correctly. there is an error somewhere.Read(). .Load Dim ver As Integer = CInt(Fix(Stream.Read())) If ver > c_Version OrElse ver <= 0 Then Throw New Exception("Wrong version!") End If As Load may be called sometime after an object has been instantiated. Both cases are errors and may cause a corrupt stream. m_anchorPointType = (esriAnchorPointEnum)Stream. If ver = 0.Read().

as well as in the implementation of the ITransform2D interface. ISimpleFillSymbol) m_pointGeometry = TryCast(Stream.Read(). ISpatialReference) m_fillSymbol = TryCast(Stream.Read().Read().Read(). Add a class member to keep the element's rotation and apply the rotation to the element when building the triangle.Read()as IPolygon.Read()) m_elementType = CStr(Stream. esriAnchorPointEnum) m_autoTrans = CBool(Stream. m_elementType = (string)Stream.Read()as ISpatialReference. users may have map documents that contain persisted TriangleElement graphic elements.NET] If ver = 1 Then m_size = CDbl(Stream.Read().Read()) m_nativeSR = TryCast(Stream.Read()as ISimpleFillSymbol. you can compile and deploy the component.Read()) m_elementName = CStr(Stream. m_triangle = Stream. IPoint) m_triangle = TryCast(Stream. To achieve these requirements. Version 2 You are now asked to add functionality to allow rotatation of the triangle elements. m_nativeSR = Stream.m_autoTrans = (bool)Stream. } [VB.Read()) m_scaleRef = CDbl(Stream. you must adapt your component.Read().Read()) m_anchorPointType = CType(Stream.0. m_fillSymbol = Stream. m_elementName = (string)Stream. . You can use the TriangleElementTool included in the project to add new triangle elements to a document. IPolygon) End If Now that you have the first version of your class. See the following code example: [C#] private double m_rotation = 0.Read(). m_pointGeometry = Stream.Read(). At this point.Read()as IPoint.

Write(c_Version) … 'New addition in version 2.NET] Private m_rotation As Double = 0.Write(c_Version). [VB.NET] Private Const c_Version As Integer = 2 When you change the persistence signature of a component. } [VB. the new component should still read the original data from the first persistence version if the old version is encountered in the Load method. Stream. Stream.NET] Public Sub Save(ByVal Stream As IVariantStream) Implements IPersistVariant.0 As the data that needs to be persisted has now changed. See the following code example: [C#] private const int c_Version = 2. See the following code example: .Write(m_rotation) End Sub Now adapt the Load method to always read from the stream the data saved by both the new and old versions of the component.[VB. � � � //New addition in version 2. increment the persist version number for the class by one.Save Stream. See the following code example: [C#] public void Save(IVariantStream Stream) { Stream.Write(m_rotation). Method InitMembers() set a default rotation value in cases where the object has been read as version 1.

Read().Load Dim ver As Integer = CInt(Fix(Stream.[C#] public void Load(IVariantStream Stream) { int ver = (int)Stream. if (ver > c_Version || ver <= 0) throw new Exception("Wrong version!").Read(). � � � if (ver == 2) { m_rotation = (double)Stream.Read())) If ver > c_Version OrElse ver <= 0 Then Throw New Exception("Wrong version!") End If InitMembers() … If ver = 2 Then m_rotation = CDbl(Stream. InitMembers(). } } [VB.NET] Public Sub Load(ByVal Stream As IVariantStream) Implements IPersistVariant.Read()) End If End Sub .

the stream pointer is left at the wrong location and the next value is not read correctly. if the new version of the component encounters an older persisted version. read the additional members. To make the implementation of persistence versions more straightforward. At this point. Obsolete persisted data In many cases. you should always read the obsolete data values. If you have loaded the second version of the persistence pattern.If you have loaded the first version of the persistence pattern. . In these cases. set the second version member variables to default values. Sometimes a new version of a component may no longer need to save certain data to the stream. This enables old components to load the persisted object. the persisted version will be version 2. Once the document is saved again (by the version 2 component). otherwise. Each version of your component can add more or different properties as required. you may want to consider the use of a PropertySet. you should make sure that all your class members are set to their default values at the beginning of a Load event in case the values of certain class members cannot be found in the current PropertySet. The persistence version number written at the beginning of the Save method should account for the persistence pattern used. You can discard the obsolete values once read and save only the required data in the new Save method. it can load from the persisted data. a new component version requires adding data to a persistence pattern. if your new component encounters the older version persisted to a stream. See the following illustration: Another possibility is to create a class that does not update to the new persistence pattern if saved by a new version of the component. If you choose this approach. then the Save and Load events only need to persist the current PropertySet. Now compile and deploy version 2 of the TriangleElement class.

ArcGIS calls the persistence methods of the symbol as normal. in a situation where a custom symbol is applied to a layer in a document at ArcGIS 9. When ArcMap attempts to persist the symbol object.2 and the user chooses to save a copy of the document to an ArcGIS 8. ArcGIS calls the IDocumentVersionSupportGEN. • • If IsSupportedAtVersion returns true. If the cast fails. you may want to implement IDocumentVersionSupportGEN. passing in a value indicating the ArcGIS version required. ArcMap then calls the IDocumentVersionSupportGEN. the persistence process for the symbol follows these general steps to create the version-specific document: 1.3 document.3 document. ArcMap calls the persistence methods of the symbol as normal. If the cast succeeds. This alternative symbol object is then returned from ConvertToSupportedObject. The following illustration shows this last situation: . This interface specifies the following pieces of functionality: • • Allows a component to indicate whether it can be persisted to a particular version of an ArcGIS document Allows a component to provide a suitable alternative object instead of itself if that component cannot be persisted to the specified version If ArcGIS cannot cast to IDocumentVersionSupportGEN for a given persistable object. it attempts to cast to IDocumentVersionSupportGEN to determine if the symbol can be saved to an 8.IsSupportedAtVersion method. and ArcGIS replaces object references to the original symbol with references to this alternative symbol. it assumes that the object can be persisted and unchanged to any version of an ArcGIS document.ConvertToSupportedObject method on the symbol. for example.Coding Save A Copy functionality To allow your component to persist to a document at a particular version of ArcGIS. For example. The symbol then creates a suitable alternative symbol object that can be persisted to the ArcGIS document version specified. when a user chooses the Save A Copy command in ArcMap. assuming the symbol can be persisted to any version of ArcGIS. 2. If IsSupportedAtVersion returns false. 3.

although in this case. If. You can prevent the element from being saved as it is to an 8.esriArcGISVersion83 == docVersion) return false. your component can be used equally well at all versions of ArcGIS. The parameter is an esriArcGISVersion enumeration value. return false to indicate that the object cannot be used in a previous version's document (for example. however. for example. you can return true from IsSupportedAtVersion.3 document using code like that shown in the following code example: [C#] public bool IsSupportedAtVersion(esriArcGISVersion docVersion) { //Support all versions except 8. you do not need to implement the interface at all. your component relies on the presence of core objects or functionality that exist only from a certain version onward. } .Implementing IDocumentVersionSupportGEN The IsSupportedAtVersion method is where you determine to which ArcGIS document versions your component can be persisted. else return true. the triangle element example previously explained).3. Return true or false from this method depending on the document version indicated by the parameter passed in to this method. if (esriArcGISVersion. If.

(This may not be an appropriate solution for every custom graphics element.NET] Public Function IsSupportedAtVersion(ByVal docVersion As esriArcGISVersion) As Boolean Implements IDocumentVersionSupportGEN.Color = m_fillSymbol.Converter. charMarkerSymbol.3 document.ArcGIS. IPoint point = ((IClone)m_pointGeometry). charMarkerSymbol. IMarkerElement markerElement = new MarkerElementClass(). but the principle can be applied to similar code to create an object appropriate to your own customizations). . FontStyle.Color.IsSupportedAtVersion 'Support all versions except 8. markerElement.esriArcGISVersion83 = docVersion Then Return False Else Return True End If End Function To allow users to save a copy of a document with the custom triangle element as an 8. you can use code like that shown in the following example to create a basic marker element that uses a triangular character marker symbol as an alternative to the custom triangle element and return this from ConvertToSupportedObject. If esriArcGISVersion.Size = m_size.3.ADF. (float)m_size. charMarkerSymbol.ToStdFont(new Font( "ESRI Default Marker". [C#] public object ConvertToSupportedObject(esriArcGISVersion docVersion) { //In case of 8.CharacterIndex = 184.Font = ESRI.Regular)). charMarkerSymbol. create a character marker element and use a triangle marker. ICharacterMarkerSymbol charMarkerSymbol = new CharacterMarkerSymbolClass().Symbol = (IMarkerSymbol)charMarkerSymbol.[VB. charMarkerSymbol.3.Angle = m_rotation.Clone()as IPoint.

IClone)). return element.Symbol = CType(charMarkerSymbol. and the user may not receive any information about why their attempt to save a copy failed. If you do not do this. } [VB. IPoint) Dim element As IElement = CType(markerElement. which may cause the saved document to become corrupted. IGeometry) Return element End Function For every esriArcGISVersion for which you return false from IsSupportedAtVersion.ADF.Angle = m_rotation charMarkerSymbol.Geometry = (IGeometry)point. element.Geometry = CType(point.IElement element = (IElement)markerElement. Do not return a null reference either. Dim charMarkerSymbol As ICharacterMarkerSymbol = New CharacterMarkerSymbolClass() charMarkerSymbol.Converter.Font = ESRI.Color charMarkerSymbol.NET] Public Function ConvertToSupportedObject(ByVal docVersion As esriArcGISVersion) As Object Implements IDocumentVersionSupportGEN.ToStdFont(New Font("ESRI Default Marker".ArcGIS. as ArcGIS may attempt to apply this null reference to a property. you prevent users from saving a copy of a document to that version.Clone().ConvertToSupportedObject 'In case of 8. IElement) element.Color = m_fillSymbol.CharacterIndex = 184 Dim markerElement As IMarkerElement = New MarkerElementClass() markerElement. create a character marker element and use a triangle marker.Size = m_size charMarkerSymbol. CSng(m_size).Regular)) charMarkerSymbol. IMarkerSymbol) Dim point As IPoint = TryCast((CType(m_pointGeometry. you should implement a suitable alternative in ConvertToSupportedObject. . FontStyle.3.

you have the following two options: • • Implement persistence on the other custom class and persist that class as you would any other object. ObjectStreams in extensions As previously mentioned. Controlling the data to persist Remember that you control what is written to the stream. For each ArcGIS version that you return false from IsSupportedAtVersion. You must take care when writing persistence code to ensure you preserve the integrity of storage files. Ensure as a minimum that your components are backward compatible—that a client (for example. therefore. Although this option may be simpler.NET uses casting to jump from one interface to another in the same class. If you persist an object reference that is already persisted elsewhere in the document and. the current . How to cast between interfaces Summary.NET and C# cast differently. Error handling and file integrity If you raise errors in a stream Load event. See the following code example: [VB. You should also consider implementing IDocumentVersionSupportGEN. In a Component Object Model (COM). Casting in VB . this may cause the current structured storage. this results in two separate objects being persisted. a separate ObjectStream is created for each extension. you should ensure you return a valid alternative object from ConvertToSupportedObject. the first method is recommended. as it is more maintainable and scalable. whereas explicit casts require cast operators. This situation can lead to problems for components that do not account for this difference. for example.NET There are two types of casts: implicit and explicit. the object you persisted is no longer the same object that was persisted in the main document ObjectStream. geometry = point 'Explicit cast.Responsibilities when implementing persistence Review the following for a recap of your responsibilities when creating persistable components.NET] 'Implicit cast. If your class needs to persist a reference to another custom object. You may want to initialize such objects in the extension startup rather than in persistence code. ArcMap) can open a document created with an earlier version of your component. this is a query interface (QI). if required. Version compatibility consistency You must ensure that your components are consistent with the version compatibility used by ArcGIS. Alternatively. . write the members of the secondary class to your persist stream for the primary class. When the document is reloaded. to a separate ObjectStream.mxd file. to be unreadable. to ensure that your component can be opened in a document that is saved as a previous version of ArcGIS. Implicit casts do not require additional syntax. Visual Basic (VB) .

IGeometry) If geometry IsNot Nothing Then Console. The following code example performs an implicit conversion from an IPoint to an IGeometry only if it is determined at runtime that the Point class implements IGeometry: [VB.WriteLine(geometry.ToString()) End If . The following code example adds an explicit cast to the previous code example: [VB. IGeometry) End If Alternatively. it is best to test if the object implements both interfaces beforehand. it is acceptable to use implicit casts.GeometryType. However. use the CType or DirectCast function to make the cast explicit. such as an interface. IGeometry) geometry = TryCast(point.NET] Dim point As New PointClass Dim geometry As IGeometry If TypeOf point Is IGeometry Then geometry = point End If If you prefer using the Option Strict On statement to restrict implicit conversions. To avoid handling unnecessary exceptions. you can skip the TypeOf comparison and use TryCast.InvalidCastException) is thrown. an exception (System.NET] Dim point As New PointClass Dim geometry As IGeometry If TypeOf point Is IGeometry Then geometry = CType(point. IGeometry) geometry = DirectCast(point. when casts fail.geometry = CType(point. One of the recommended techniques is to use the TypeOf keyword. See the following code example: [VB. because there is no chance of data loss as when casting between numeric types. it returns Nothing when conversion fails.NET] Dim point As New PointClass Dim geometry As IGeometry = TryCast(point. IGeometry) When casting between interfaces. which is a comparison clause that tests whether an object is derived from or implements a particular type.

} Alternatively. if (point is IGeometry) { IGeometry geometry = (IGeometry)point.WriteLine(geometry. which returns a null if the object cannot return a reference to the desired interface.GeometryType.Carto.olb corresponds to the assembly ESRI. Using this convention. Using the as operator is a better coding strategy than a straight cast. //Straight cast. because it yields a null on a conversion failure rather than raising an exception. for example. Console.ArcGIS. you can test if an object implements a certain interface at run time using the is keyword before performing a straight cast.ToString()).ToString()). If the object does not implement the interface you are attempting to get a handle to. IGeometry geometry = point as IGeometry. The interop assemblies are named after the object library name minus the "esri" prefix. IGeometry geometry = point as IGeometry. The first line in the following code example is a straight cast. the object library esriCarto. The following code example shows how to manage the possibility of a returned null interface handle after an explicit cast: [C#] IPoint point = new PointClass(). For example.dll.GeometryType.NET throws an exception. [C#] IGeometry geometry = (IGeometry)point. the best method for casting between interfaces is to use the as operator. .NET) generally replicate the assembly name. all the items defined in the . A safer model to use is the as operator. See the following code example: [C#] IPoint point = new PointClass(). The classes in the interop assemblies (provided as part of the ArcObjects Developer Kit for . if (geometry != null) { Console.Casting in C# In C#.NET that allows objects to be organized hierarchically into logical categories of related functionality regardless of the assembly in which they are defined. } About namespaces Namespaces are a concept in . //As operator.WriteLine(geometry. This is an acceptable practice if you are certain the object in question implements both interfaces.

ESRI.ArcGIS.Carto.dll assembly belong to the namespace ESRI.ArcGIS.Carto. One notable exception is the ESRI.ArcGIS.System assembly, which corresponds to the esriSystem.olb object library. To avoid a conflict with the Microsoft System root namespace, the items in this assembly belong to the ESRI.ArcGIS.esriSystem namespace. To find out more about the contents of the ArcGIS interop assemblies, use the Contents tab and navigate to the ArcObjects Library Reference section. In addition to the assemblies that provide Component Object Model (COM) interop functionality for the objects in the standard object libraries, there are also helper classes specifically designed for use with ArcObjects in .NET. These helper classes are defined in the assemblies ESRI.ArcGIS.ADF and ESRI.ArcGIS.ADF.Connection. They have a number of namespaces, the most commonly used being ESRI.ArcGIS.ADF.BaseClasses, ESRI.ArcGIS.ADF.CATIDs, and ESRI.ArcGIS.ADF.COMSupport. Using namespaces After you have added a reference to an assembly, you can make your code simpler and more readable by adding directives that act as shortcuts when you declare or reference items specified in the namespaces contained in the referenced assemblies. This allows you to use classes and other types defined within the imported namespaces without having to fully qualify the class and type names.


In C#, use the "using" directive. See the following code example:

using ESRI.ArcGIS.Carto;

In VB.NET, use the "Imports" statement. See the following code example:


Imports ESRI.ArcGIS.Carto Defining namespaces You can organize your objects in namespaces as well. By default, the namespace is set to the name of the project; in VB.NET, it is the Root Namespace. In C#, you can set the default namespace in the project properties dialog box. All new items added to the project will use the default namespace as shown in the following screen shot below:

C# class files have Namespace statements by default. This namespace declares the same namespace as the Default Namespace. If you want a class in your project to have a different namespace from the one defined by the Default Namespace, you can simply change the Namespace statement in that class. See the following code example:


namespace EsriSamples.ArcMapCommands { public class ZoomIn{} } In VB.NET, you can set the Root Namespace property to your namespace. All items in the project will use this namespace. You can set the Root Namespace in the project properties dialog box as shown in the following screen shot:

By default, Visual Basic (VB) class files do not have a Namespace statement to declare a namespace; you have to add this manually if you want it. Be aware that if you have the Root Namespace set and you also use a Namespace statement in a code file, the namespace defined in the file gets appended to the Root Namespace. In the following code example, the Root Namespace is set to EsriSamples and the namespace declared in the VB class code file is ArcMapCommands, so the namespace for the ZoomIn class will be EsriSamples.ArcMapCommands.

Namespace ArcMapCommands ' RootNamespace is set to EsriSamples. <ComClass(ZoomIn.ClassId, ZoomIn.InterfaceId, ZoomIn.EventsId)> _ Public Class ZoomIn ... End Class

End Namespace The VB.NET ArcObjects samples do not use the Namespace statement; only the Root Namespace is used.

About ProgIDs

ZoomIn: [C#] namespace EsriSamples { [Guid("97FDB5D7-41D8-4260-BF72-3EB2CDD36747")] [ProgID("ZoomInCmd. _ ProgID("ZoomInCmd. your ProgID will work when you need to use it for ArcObjects methods that take a ProgID such as IUID.ZoomIn")] public class ZoomIn: ICommand { . Using ProgIDs If you have used the ProgID attribute and your class is registered in a component category.NET classes are made available to the Component Object Model (COM) runtime by registering the components in the COM registry.ZoomIn as the ProgID when you need to use a ProgID programmatically.NET] ' RootNamespace is set to EsriSamples.NET. the <namespace>. It may be necessary to do this if your namespace is long. ZoomIn. ProgIDs are limited to 39 characters and cannot contain punctuation other than a period.ZoomIn")> _ Public Class ZoomIn .EventsId). a ProgID (program identifier) is automatically generated for a class in the following format: <namespace>.ID.Value and IItemDef. The three ways to perform this task are outlined in this topic. However. EsriSamples. In addition. <ComClass(ZoomIn. How to register .<class name>.NET components with COM SummaryExtending the ArcGIS applications with custom .NET components requires that the .In .ZoomIn is the identifier that appears in the Component Category Manager. Building the project on a development machine • Use: On a development machine . You can override this and explicitly set the ProgID of your object by using the ProgID attribute. if the component is to be used from a COM development environment. The following code example explicitly sets the ProgID to ZoomInCmd..<class name> is the identifier you will see in the Component Category Manager instead of the ProgID.. [VB.ClassId. you may also want to export a type library.. ZoomIn. In the previous code example. but you would use ZoomInCmd. which is also outlined in this topic..InterfaceId.

start the command prompt as an administrator by right-clicking the Command Prompt icon and clicking the Run as Administrator option.NET Framework SDK installed. therefore.50727. which allows a COM (unmanaged) application to consume .exe utility. then Microsoft .dll /unregister Using an installation program .dll /codebase The /codebase parameter is an optional parameter that adds information to the registry specifying the path on disk of the assembly. for example. the option is not required. if the component is installed to the GAC. Machines with Visual Studio 2005 or the freely available . this is not a viable solution for general deployment of your components. as shown in the following example: regasm EditTools.NET Framework Software Development Kit (SDK) or Visual Studio 2005. Registering by command.NET\Framework\v2. this option will be required for ArcGIS to find your component successfully. when first testing your component or if you do not have access to the assemblies source code. and select the Register for COM Interop check box. you can use the assembly registration utility Regasm that ships with the .NET Framework 2.0.0.NET Framework SDK will have this utility installed and other machines may not.0 default installation path is C:\Windows\Microsoft. To check a project's settings. using the Regasm utility • Use: When testing on a different machine and you do not have an installation program Sometimes you may want to register an assembly for COM interop without building the project. type regasm /? or refer to the Microsoft Developer Network (MSDN) Web site. If the target machine does not have Visual Studio installed but does have the . Regasm has many other options.NET) page. the . ensure the project-level Register for COM Interop property is set to true. If the component is not to be deployed to the global assembly cache (GAC). use the SDK command prompt by choosing Programs. Give the full path to the assembly unless the current directory of the command prompt is the directory where the assembly is located: regasm EditTools.NET Framework SDK v2. For example. This allows the Windows Registry to be written to when you use the Regasm. Regasm adds entries to the registry. select the Build (C#) or Compile (VB. then build the project. open a command prompt at the path where Regasm is located. open the Visual Studio 2005 command prompt (the ordinary command prompt will not have the appropriate environment variables set to use this tool). click Project Properties from the Project menu.NET classes via COM interop. the simplest way is to open the project in Visual Studio 2005. Regasm can also be used to unregister assemblies from COM interop. Using the Regasm command line utility Do the following to use the Regasm command line utility: • If the target machine has Visual Studio installed. for a full list. The following example shows the command used to register the assembly (EditTools) with COM. If you cannot find it. In this case.When you want to register an assembly for COM interop on a development machine. • For Windows Vista.

for example. See the following: regasm EditTools.NET component in a particular category. Type libraries for managed components A type library (. Visual Studio generates the .tlb for an existing component. . Registering classes in COM component categories There are a few different ways you can register a .tlb) can also be exported that contains information describing the types in the assembly. tools. If you do not have a .ADF assembly to help perform the registrations.ArcGIS. and toolbars. you can quickly add code to your classes.• Use: When deploying to your user Create an installation program that deploys your component and adds to the installation program an automatic registration step that registers your component for COM interop. o By using the ArcGIS Component Category Registrar dialog box.tlb) file created for your customization and open it. This dialog box uses classes from the ESRI. • The Customize dialog box in ArcGIS applications can be used to add commands. Create installation programs by using third-party installation software or a Visual Studio setup project. generate a type library. For an example of how to create a setup project for a component using WISE.dll /tlb:EditTools. at which point the ArcGIS framework automatically adds the classes you select in the type library to the appropriate component category.tlb How to register classes in COM component categories SummaryMuch of the extensibility of ArcGIS relies on component object model (COM) categories. all ArcMap commands and tools are registered in the ESRI Mx Commands component category. which automatically registers them to component categories. such as within the Visual Basic for Applications (VBA) environment embedded with the ArcGIS applications. By clicking the Add From File button on this dialog box. This article provides an overview of the different ways you can register a . You may want to generate a type library for a component if that component will be used from another development environment. Additional Requirements • Components must be registered with COM before they can be registered to component categories.tlb file automatically if you have selected the Register for COM Interop setting. see How to deploy an application. which is part of the ArcGIS Visual Studio IDE Integration Framework. you can browse for the type library (.NET classes that automatically registers the classes in a particular component category when the component is registered with COM.NET component in a particular category: • The simplest and recommended method is to add code to your . one can be generated by using the Regasm utility and the /tlb option. If you want to add custom commands and tools using the Customize dialog box within the ArcGIS applications.

ArcGIS.InteropServices). Registering your component becomes as easy as adding COM registration methods with the appropriate attributes and passing the received CLSID to the appropriate static method.Runtime. Registering a component in a component category requires that you also know the component category’s unique ID (CATID).ArcGIS. The reverse case is also true: unregistering the DLL removes the component category registry entry. rather than the . you select the desired component category in the utility.ADF.NET can have a self-registration process.ADF About working with component categories using ESRI. all ArcMap commands and tools are registered in the ESRI Mx Commands component category.BaseTool { [ComRegisterFunction()] static void Reg(string regKey) . which contains command items (commands.ArcGIS.CATIDs. Working with component categories using ESRI.NET components. you must always select the type library.CATIDs.dll). ComRegisterFunctionAttribute and ComUnregisterFunctionAttribute (under System. Component Object Model (COM) components created in . With this information.dll file. This method may be most useful if you want to use an existing compiled component that contains custom classes but does not have automatic registration code. For example. Both methods are passed through the class identifier (CLSID) of the class currently being registered. you can write code inside the methods to make the appropriate registry entries or deletions.ADF.ArcGIS. Each class knows its CATID and exposes static methods (Register and Unregister) for adding and removing components. public sealed class PanTool: ESRI. and toolbars) but does not have automatic registration code.ArcGIS. • Another option is to use the Component Categories Manager (Categories.NET (ESRI. that allow you to specify methods that are called whenever your component is being registered or unregistered. In this case. This namespace contains classes representing each of the ArcGIS component categories.ADF. regardless of the method of category registration you choose. browse for your type library.exe).ADF.BaseClasses. To help the registration process.ArcGIS. and choose the appropriate class.ADF All custom ArcGIS components must be registered in the component category appropriate to their intended context and function so that the host application can make their functionality available. ESRI provides the Application Developer Framework (ADF) assembly for . They are then automatically registered in the correct component category whenever the dynamic-link library (DLL) is registered. The following code example shows a custom Pan tool that registers itself in the ESRI Mx Commands component category: [C#] using ESRI. tools. For .o This method may be most useful if you want to use an existing compiled component. which contains the namespace ESRI.NET Framework contains two attribute classes. The . • The actual actions performed are essentially the same.

} [ComUnregisterFunction()] static void Unreg(string regKey) { MxCommands.ADF.BaseClasses.Unregister(regKey) End Sub … End Class Writing multithreaded ArcObjects code .CATIDs Public NotInheritable Class PanTool Inherits ESRI. } � � � } [VB.ADF.Register(regKey).Unregister(regKey).{ MxCommands.ArcGIS.ArcGIS.Register(regKey) End Sub <ComUnregisterFunction()> _ Public Shared Sub Unreg(ByVal regKey As [String]) MxCommands.BaseTool <ComRegisterFunction()> _ Public Shared Sub Reg(ByVal regKey As [String]) MxCommands.NET] Imports ESRI.

This topic details what multithreading means in the context of the ArcObjects . By using multiple threads of execution in your code. Threads of an application share the same memory space. For that reason. When to use multithreading There are two items to consider when building multithreaded applications: thread safety and scalability. STAs are limited to one thread each. so you must make sure that accessing shared data structures is synchronized to prevent the application from entering into an invalid state or crashing. but simply having thread-safe objects does not automatically mean that creating multithreaded applications is straightforward or that the resulting application will provide improved performance. and every method call that it receives will arrive on the same thread. It is important for all objects to be thread safe. writing multithreading ArcObjects applications requires an understanding of both .NET Software Developer Kit (SDK) as well as the rules that must be followed to properly integrate threading into ArcObjects applications. . A general rule of thumb is that a task is suited to multithreading if it can be broken into different independent tasks. Be careful when using locks. It can be achieved at the object level when the shared object is thread safe. Finding a balance between performance and protection requires careful consideration of your data structures and the intended usage pattern for your extra threads. This model works by eliminating cross-thread communication. in many cases it will add extra overhead and complexity that would eventually reduce the execution speed of your code. All ArcObjects references within a thread should only communicate with objects in the same thread. however. When a method call enters an STA. The performance advantages of multithreading come at the cost of increased complexity in the design and maintenance of your code. writing multithreaded ArcObjects code should be done carefully.NET Framework allows you to easily generate threads in your application. but rather to give practical solutions for daily ArcObjects programming problems that involve multithreading. Consequently. Concurrency control can be done at the application level by acquiring an exclusive lock on the shared object. This topic does not attempt to give an introduction to multithreading or to teach multithreading concepts. and developers can use them in multithreaded environments.SummaryMultithreading allows an application to do more than one thing at a time within a single process. This will prevent any long data processing operations from reducing the responsiveness of your UI. it is transferred to the STA's one and only thread. Introduction to multithreading Multithreading is generally used to improve the responsiveness of applications. but COM places no limit on the number of STAs per process. Threads in Isolation. The . Multithreading will not always make your code run faster. ArcObjects components are thread safe. This synchronization is often called concurrency control. Multithreading should only be used when the added complexity is worth the cost. Concurrency control can be achieved at two levels: the object level and the application level.NET multithreading and the threading model for COM. must be considered. an object in an STA will only receive and process one method call at a time. This responsiveness can be the result of real performance improvements or the perception of improved performance. as overuse of them will protect your data but can also lead to decreased performance. allowing one thread at a time to modify the object’s state. meaning that the object forces all threads trying to access it to wait until the current thread accessing the object is finished modifying the object’s state. the apartment threading model used by ArcObjects. ArcObjects threading model All ArcObjects components are marked as single threaded apartment (STA). you can separate data processing and input/output (I/O) operations from the management of your program's user interface (UI). The underlying architecture of ArcObjects is Component Object Model (COM). For ArcObjects applications to run efficiently in a multithreaded environment.

While running the background thread. you must ensure that these objects are singletons per thread. referenced in the following code example. you can use XmlSerializerClass to serialize an object. in some you will find that the multithreading is just an architectural aspect of a broader picture. all objects. In cases where you must pass ArcObjects components from the main thread into a worker thread. you cannot share ArcObjects components between threads. then accessed from the other threads. the singleton objects at ArcGIS 9. and deserialize the connection properties on the worker thread using the XmlSerializerClass. into a string.NET SDK includes several threading samples. This keeps the application free to run without waiting for the table to be populated. and deserialize the object back. Instead. If you are creating singleton objects as part of your development. To be successful using ArcObjects in a multithreaded environment. and other unexpected behavior. programmers must follow the Threads in Isolation model while writing their multithreaded code in such a way as to avoid application failures such as deadlock situations. the following are a few of the more common scenarios that developers encounter. The resource overhead of hosting multiple singletons in a process is outweighed by the performance gain of stopping the cross-thread communication that would occur if the singleton were created in one thread. As a developer of the extensible ArcGIS system. pass the string with the connection properties to the worker thread. To accomplish this task. it is convenient to allow the operation to run on a background thread while leaving the application free to handle other tasks and keep the UI responsive. you can report the task progress onto a UI dialog box. instantiate all factories required to open FeatureClasses. you will need to take advantage of the fact that singleton objects are "per thread" and. not per process. All information passed to the thread must be in the form of simple types or managed types. must adhere to this rule for the Threads in Isolation model to work. demonstrates a background thread that is used to iterate through a FeatureCursor and populate a DataTable that will is used later in the application. set spatial references. This is covered in more detail in the Updating the UI from a background thread section of this topic. For example. Multithreading scenarios Although there are a number of ways to implement multithreading applications. pass the string to the target thread. negative performance due to marshalling. and cross-apartment calls are avoided. The ArcObjects . that cover the scenarios described in this topic. Some examples of such operations include iterating through a FeatureCursor to load information into a DataTable and performing a complex topological operation while writing the result into a new FeatureClass. in the background thread. keep in mind the following points: • According to the Thread in Isolation model. [C#] . and so on. the connection properties object are created on the background thread. create new FeatureClasses. • • • The following code example. While these samples use multithreading as part of a solution for a given problem. an excerpt from the RSS weather layer sample. such as workspace connection properties (an IPropertySet). including those you write.x were designed to be singletons per thread and not singletons per process. serialize the object into a string. The samples demonstrate solutions for real-life problems while showing the best programming practices. This way.For this model to work. Running lengthy operations on a background thread When you are required to run a lengthy operation.

/// </summary> private void PopulateLocationsTableProc() { //Get the ArcGIS path from the registry.ToString(key. string path = Convert. /// <summary> /// Load the information from the MajorCities feature class to the locations table. IWorkspaceFactory wf = new ShapefileWorkspaceFactoryClass()as IWorkspaceFactory.Start(). //Map the name and ZIP code fields.SetApartmentState(ApartmentState. long zip.OpenSubKey(@"SOFTWARE\ESRI\ArcGIS"). IFeatureWorkspace fw = ws as IFeatureWorkspace. // Mark the thread as a single threaded apartment (STA) to efficiently run ArcObjects.FindField("ZIP").LocalMachine.IO.OpenFeatureClass(m_sShapefileName).OpenFromFile(System. The workspace factory must be instantiated since it is a singleton per-thread.STA). int zipIndex = featureClass. RegistryKey key = Registry. t. t.Combine(path.GetValue("InstallDir")). 0). string cityName. int nameIndex = featureClass. . Thread t = new Thread(new ThreadStart(PopulateLocationsTableProc)).// Generate the thread that populates the locations table. // Start the thread. //Open the feature class.FindField("NAME"). IFeatureClass featureClass = fw.Path. IWorkspace ws = wf. @ "Samples Net\Data\USZipCodeData").

Parse(Convert.Rows. if (obj == null) continue. if (zip <= 0) continue.Search(null. . fCursor = featureClass.ToString(obj). if (null == r) { r = m_locations.ToString(obj)). if (obj == null) continue. DataRow r = m_locations.get_Value(zipIndex). IFeatureCursor fCursor = null.get_Value(nameIndex).NextFeature(). IFeature feature = fCursor. r[1] = zip.try { //Iterate through the features and add the information to the table. obj = feature. cityName = Convert.NewRow().Find(zip). zip = long. //Add the current location to the location table. int index = 0. while (null != feature) { object obj = feature. //m_locations is a DataTable that contains the cities and ZIP //It is defined in the full code before this excerpt starts. true). codes.

"In the .0. and eventually it may be about 50 times slower for a call to the COM component. The following code example marks a console application as STA: [C#] namespace ConsoleApplication1 .NET Framework version 2. Marshal. this means that if your application is not initialized as a single threaded application. Fortunately.MTA if their apartment state has not been set before they are started. This will cause a thread switch to this thread on each call from the application to ArcObjects.ReleaseComObject(fCursor).Message).r[2] = cityName.Trace. } //Release the feature cursor.NET framework will create a special single threaded apartment (STA) thread for all ArcObjects since they are marked as STA. } catch (Exception ex) { System.Rows. You can no longer set the main application thread to ApartmentState.NextFeature().MTA by default. lock(m_locations) { m_locations." As an ArcObjects developer. the . this can be avoided by simply marking the main function as [STAThread]. In turn.ApartmentState property on the first line of code. } } Implementing stand-alone ArcObjects applications As stated on the Microsoft Developer Network (MSDN) Web site.WriteLine(ex. } } feature = fCursor.Diagnostics.STA by setting the Thread. index++. this forces the ArcObjects components to marshall each call. Use the STAThreadAttribute instead. The main application thread is initialized to ApartmentState. new threads are initialized as ApartmentState.Add(r).

. However. } .. m_doneEvent = doneEvent. } } } If you create a Windows Application using the VS2005 project wizard. which can result in better performance and better system stability.. such as an array of threads marked as STAThread to run ArcObjects. The advantage of using a thread pool over creating a new thread for each task is that thread creation and destruction overhead is negated. Another solution is to use an implementation of a custom STA thread pool. public TaskInfo(int BandID. Using managed ThreadPool and BackGroundWorker threads Thread pool threads are background threads. it automatically puts the [STAThread] on the main function for you. The following code example—an excerpt from the Multithreaded raster subset sample— demonstrates using an array of STAThread threads to subset a RasterDataset. Thread pooling enables you to use threads more efficiently by providing your application with a pool of worker threads that are managed by the system. One is to implement a dedicated ArcObjects thread that is marked as STAThread and to delegate each of the calls from the MTA threads to this dedicated ArcObjects thread. /// </summary> public class TaskInfo { .{ class Program { [STAThread] static void Main(string[] args) { // . using a different thread to subset each raster band: [C#] /// <summary> /// Class used to pass the task information to the working thread. by design all ThreadPool threads are in the multithreaded apartment (MTA) and therefore should not be used to run ArcObjects which are single-threaded apartment. ManualResetEvent doneEvent) { m_bandID = BandID.. To work around this problem you have a few options.

... }

... public override void OnMouseDown(int Button, int Shift, int X, int Y) { ... // Run the subset thread that will spin off separate subset tasks. By default, this thread will run as MTA. // This is needed to use WaitHandle.WaitAll(), the call must be made // from MTA. Thread t = new Thread(new ThreadStart(SubsetProc)); t.Start(); }

/// <summary> /// Main subset method. /// </summary> private void SubsetProc() { ... //Create a dedicated thread for each band of the input raster. //Create the subset threads. Thread[] threadTask = new Thread[m_intBandCount];

//Each thread will subset a different raster band. //All information required for subsetting the raster band will be passed to the task by the user-defined class TaskInfo. for (int i = 0; i < m_intBandCount; i++) { TaskInfo ti = new TaskInfo(i, doneEvents[i]); ... // Assign the subsetting thread for the rasterband.

threadTask[i] = new Thread(new ParameterizedThreadStart(SubsetRasterBand)); // Note the STA apartment that is required to run ArcObjects. threadTask[i].SetApartmentState(ApartmentState.STA); threadTask[i].Name = "Subset_" + (i + 1).ToString(); // Start the task and pass the task information. threadTask[i].Start((object)ti); } ... // Wait for all threads to complete their task… WaitHandle.WaitAll(doneEvents); ... }

/// <summary> /// Subset task method. /// </summary> /// <param name="state"></param> private void SubsetRasterBand(object state) { // The state object must be cast to the correct type, because the // signature of the WaitForTimerCallback delegate specifies type // Object. TaskInfo ti = (TaskInfo)state;

//Deserialize the workspace connection properties. IXMLSerializer xmlSerializer = new XMLSerializerClass(); object obj = xmlSerializer.LoadFromString(ti.InputRasterWSConnectionProps, null, null);

IPropertySet workspaceProperties = (IPropertySet)obj;

... } Synchronizing execution of concurrent running threads In many cases, you have to synchronize the execution of concurrently running threads. Normally, you have to wait for one or more threads to finish their tasks, signal a waiting thread to resume its task when a certain condition is met, test whether a given thread is alive and running, change a thread priority, or give some other indication. In .NET, there are several ways to manage the execution of running threads. The main classes available to help thread management are as follows:

• •

System.Threading.Thread—Used to create and control threads, change priority, and get status System.Threading.WaitHandle—Defines a signaling mechanism to indicate taking or releasing exclusive access to a shared resource, allowing you to restrict access to a block of code

Calling the WaitHandle.WaitAll() method must be done from a multithreaded apartment (MTA) thread. To launch multiple synchronized tasks, you first have to launch a worker thread that, in turn, will run the multiple threads.

• •

System.Threading.Monitor—Similar to System.Threading.WaitHandle, provides a mechanism that synchronizes access to objects System.Threading.AutoResetEvent and System.Threading.ManualResetEvent— Used to notify waiting threads that an event has occurred, allowing threads to communicate with each other by signaling

The following code example, also in the Multithreaded raster subset sample, extends what was covered in the previous section. It uses the ManualResetEvent and the WaitHandle classes to wait for multiple threads to finish their tasks. In addition, it demonstrates using the AutoResetEvent class to block running threads from accessing a block of code and to signal the next available thread when the current thread had completed its task.


/// <summary> /// Class used to pass the task information to the working thread. /// </summary> public class TaskInfo { //Signal the main thread that the thread has finished its task. private ManualResetEvent m_doneEvent; ... public TaskInfo(int BandID, ManualResetEvent doneEvent) { m_bandID = BandID;

.WaitAll().m_doneEvent = doneEvent. int Shift. the call must be // from MTA. /// </summary> private void SubsetProc() ... } /// <summary> /// Main subset method. made // This is needed since to use WaitHandle. int X. public ManualResetEvent DoneEvent { get { return m_doneEvent.. } set { m_doneEvent = value. } } } //Block access to the shared resource (raster dataset). this thread will run as MTA. . By default. } . private static AutoResetEvent m_autoEvent = new AutoResetEvent(false). int Y) { .. public override void OnMouseDown(int Button.. // Run the subset thread that will spin off separate subset tasks.

.{ . //Create ManualResetEvent to notify the main threads that //all ThreadPool threads are done with their tasks. // assign the subsetting thread for the rasterband. // start the task and pass the task information threadTask[i].Set(). m_autoEvent.. i < m_intBandCount.ToString(). //All information required for subsetting the raster band will be passed to the ThreadPool task by the user .defined class TaskInfo. for (int i = 0. threadTask[i] = new Thread(new ParameterizedThreadStart(SubsetRasterBand)).Start((object)ti).STA). //Each task will subset a different raster band. threadTask[i]. } //Set the state of the event to signaled to allow one or more of the waiting threads to proceed. //Create a ThreadPool task for each band of the input raster. doneEvents[i] = new ManualResetEvent(false). .Name = "Subset_" + (i + 1). TaskInfo ti = new TaskInfo(i. ManualResetEvent[] doneEvents = new ManualResetEvent[m_intBandCount]. i ++) { //Create the ManualResetEvent field for the task to // signal the waiting thread that the task had been completed.SetApartmentState(ApartmentState.. // Note the STA apartment which is required to run ArcObjects threadTask[i]. .. doneEvents[i]).

m_autoEvent. . /// </summary> /// <param name="state"></param> private void SubsetRasterBand(object state) { // The state object must be cast to the correct type. } /// <summary> /// Subset task method.Set(). TaskInfo ti = (TaskInfo)state. //Insert code containing your threading logic here. you should . //Lock all other threads to get exclusive access.NET application’s underlying data structure will be a managed object such as a DataTable or HashTable.. //Signal the main thread that the thread has finished its task. //Signal the next available thread to get write access.. . because the // signature of the WaitOrTimerCallback delegate specifies type // Object. ti.WaitAll(doneEvents).. m_autoEvent..NET managed objects allow you to share them across multiple threads such as a data fetching thread and a main rendering thread. However.WaitOne().// Wait for all threads to complete their task… WaitHandle.Set().DoneEvent. These . } Sharing a managed type across multiple threads Sometimes your .

consult the MSDN Web site to verify whether an item is thread safe. In many cases, an object is thread safe for reading but not for writing. Some collections implement a Synchronized method, which provides a synchronized wrapper around the underlying collection. In cases where your object is being accessed from more than one thread, you should acquire an exclusive lock according to the Thread Safety section in MSDN regarding this particular object. Acquiring such an exclusive lock can be done using one of the synchronization methods described in the previous section or by using a lock statement, which marks a block as a critical section by obtaining a mutual-exclusive lock for a given object. It ensures that if another thread attempts to access the object, it will be blocked until the object is released (exits the lock). The following screen shot demonstrates sharing a DataTable by multiple threads. First, check the DataTable Class on the MSDN Web site to verify if it is thread safe.

On that page, check the section on Thread Safety, where it says, "This type is safe for multithreaded read operations. You must synchronize any write operations." This means that reading information out of the DataTable is not a problem, but you must prevent other threads access to the table when you are about to write to it. The following code example shows how to block those other threads:

private DataTable m_locations = null; ...

DataRow rec = m_locations.NewRow(); rec["ZIPCODE"] = zipCode; //ZIP Code rec["CITYNAME"] = cityName; //City name

//Lock the table and add the new record. lock(m_locations)

{ m_locations.Rows.Add(rec); } Updating the UI from a background thread In most cases where you are using a background thread to perform lengthy operations, you want to report to the user the progress, status, errors, or other information related to the task performed by the thread. This can be done by updating a control on the application's UI. However, in Windows, forms controls are bound to a specific thread (generally the main thread) and are not thread safe. As a result, you must delegate, and thus marshal, any call to a UI control to the thread to which the control belongs. The delegation is done through calling the Control.Invoke method, which executes the delegate on the thread that owns the control's underlying window handle. To verify whether a caller must call an invoke method, you can use the property Control.InvokeRequired. You must make sure that the control's handle was created before attempting to call Control.Invoke or Control.InvokeRequired. The following code example, an excerpt from the RSS weather layer sample, demonstrates reporting a background task's progress into a user form.


In the user form, declare a delegate through which you will pass the information to the control.


public class WeatherItemSelectionDlg: System.Windows.Forms.Form { private delegate void AddListItmCallback(string item); � � � 2. In the user form, set the method to update the UI control. Notice the call to Invoke. The method must have the same signature as the previously declared delegate:


//Make thread-safe calls to Windows Forms Controls. private void AddListItemString(string item) { // InvokeRequired compares the thread ID of the //calling thread to the thread ID of the creating thread. // If these threads are different, it returns true. if (this.lstWeatherItemNames.InvokeRequired) { //Call itself on the main thread. AddListItmCallback d = new AddListItmCallback(AddListItemString);

this.Invoke(d, new object[] { item } ); } else { //Guaranteed to run on the main UI thread. this.lstWeatherItemNames.Items.Add(item); } } 3. On the background thread, implement the method that will use the delegate and pass over the message to be displayed on the user form.


private void PopulateSubListProc() { //Insert code containing your threading logic here.

//Add the item to the list box. frm.AddListItemString(data needed to update the UI control, string in this case ) ; } 4. Write the call that launches the background thread itself, passing in the method written in Step 3.


//Generate a thread to populate the list with items that match the selection criteria. Thread t = new Thread(new ThreadStart(PopulateSubListProc)); t.Start(); Calling ArcObjects from a thread other than the main thread

In many multithreading applications, you will need to make calls to ArcObjects from different running threads. For example, you might have a background thread that gets a response from a Web service, which, in turn, should add a new item to the map display, change the map extent, or run a geoprocessing (GP) tool to perform some type of analysis. A very common case is calling ArcObjects from a timer event handler method. Timer's Elapsed event is raised on a ThreadPool task, a thread that is not the main thread. Yet it needs to use ArcObjects, which looks like it would require cross-apartment calls. However, this can be avoided by treating the ArcObjects component as if it were a UI control and using Invoke to delegate the call to the main thread where the ArcObjects component is created. Thus, no crossapartment calls are made. The ISynchronizeInvoke interface includes methods Invoke, BeginInvoke, and EndInvoke. Implementing these methods yourself can be a daunting task. Instead, you should either have your class directly inherit from System.Windows.Forms.Control or you should have a helper class that inherits Control. Either option will provide a simple and efficient solution for invoking methods. The following code example employs a user-defined InvokeHelper class to invoke a timer’s elapsed event handler to recenter the map's visible bounds and set the map's rotation. Note that some of the application logic must be done on the InvokeHelper class in addition to the user-defined structure that is being passed by the method delegate.

/// <summary> /// A helper method used to delegate calls to the main thread. /// </summary> public sealed class InvokeHelper: Control { //Delegate used to pass the invoked method to the main thread. public delegate void MessageHandler(NavigationData navigationData);

//Class members. private IActiveView m_activeView; private IPoint m_point = null;

/// <summary> /// Class constructor. /// </summary> /// <param name="activeView"></param> public InvokeHelper(IActiveView activeView) { handle. //Make sure that the control was created and that it has a valid

IsDisposed && this. /// </summary> /// <param name="navigationData"></param> public void InvokeMethod(NavigationData navigationData) { // Invoke HandleMessage through its delegate. //Get the active view.ScreenDisplay. } /// <summary> /// Delegate the required method onto the main thread.DisplayTransformation. IEnvelope envelope = m_activeView.CreateControl(). /// </summary> /// <param name="navigationData"></param> public void CenterMap(NavigationData navigationData) { //Get the current map visible extent.this. .IsHandleCreated) Invoke(new MessageHandler(CenterMap). if (!this. this. m_activeView = activeView. } /// <summary> /// The method that gets executed by the delegate.CreateHandle().VisibleBounds. new object[] { navigationData } ).

} /// <summary> /// Control initialization. .ScreenDisplay.DisplayTransformation. //Rotate the map to the new rotation angle.Rotation = navigationData. navigationData. m_point. envelope. /// </summary> private void InitializeComponent(){} /// <summary> /// A user defined data structure used to pass information to the invoke method. public double Azimuth. m_activeView.if (null == m_point) { m_point = new PointClass().CenterAt(m_point).VisibleBounds = envelope.PutCoords(navigationData.X.Azimuth. //Center the map around the new coordinate. m_activeView. public double Y. } //Set the new map center coordinate.ScreenDisplay. /// </summary> public struct NavigationData { public double X.Y).DisplayTransformation.

/// </summary> /// <param name="x">map x coordinate</param> /// <param name="y">map x coordinate</param> /// <param name="azimuth">the new map azimuth</param> public NavigationData(double x.Timers. double azimuth) { X = x. � � � private InvokeHelper m_invokeHelper = null. private System. /// </summary> /// <param name="hook">Instance of the application</param> public override void OnCreate(object hook) { � � � . double y. Azimuth = azimuth. } /// <summary> /// This command triggers the tracking functionality.Timer m_timer = null./// <summary> /// Struct constructor. � � � /// <summary> /// Occurs when this command is created. /// </summary> public sealed class TrackObject: BaseCommand { //Class members. private IHookHelper m_hookHelper = null. Y = y.

Enabled = false.//Instantiate the timer..Enabled = true. if (!m_bIsRunning) m_timer. /// </summary> public override void OnClick() { //Create the InvokeHelper class.ActiveView).Enabled = false... m_timer. if (null == m_invokeHelper) m_invokeHelper = new InvokeHelper(m_hookHelper. /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void OnTimerElapsed(object sender... } /// <summary> /// Timer elapsed event handler. //Set the timer's elapsed event handler. .Elapsed += new ElapsedEventHandler(OnTimerElapsed). .. //Start the timer. m_timer = new System. else m_timer. ElapsedEventArgs e) { .Timer(60). } /// <summary> /// Occurs when this command is clicked. m_timer.Timers. .

//Create the navigation data structure.Size = 20.Name = "Arial".Font = fnt.StdFontClass().NET..ADF.ArcGIS. m_invokeHelper. or icon class that you want to convert to use as a font or picture in an ESRI method.X.Display.ArcGIS. To use these with . .TextSymbol = New ESRI. fnt. [VB.IFontDisp fnt = (stdole.0 Dim textSym As ESRI.Display. you may have an existing .StdFontClass() fnt. This pattern enables the desktop working with GP in an asynchronous execution mode.ArcGIS.Y.TextSymbolClass(). The ESRI.TextSymbol textSym = new ESRI.ArcGIS. however. bitmap.NET support during an ArcGIS installation. textSym. use the asynchronous execution pattern exposed in ArcGIS Server 9. currentPoint.IFontDisp)new stdole.ADF) About working with OLE StdFont and StdPicture classes (ESRI. //Update the map extent and rotation.dll primary interop assembly (PIA).Font = fnt Sometimes.NET font. ESRI.NET] Dim fnt As stdole. which is included as part of the .Display.2 using GP Services.ArcGIS. NavigationData navigationData = new NavigationData(currentPoint..Size = 20. This PIA allows you to define StdFont and StdPicture classes.ArcGIS.COMSupport .IFontDisp = New stdole. Working with OLE StdFont and StdPicture classes (ESRI. you should add to your project a reference to the Stdole. See the following code example: [C#] stdole.Display. azimuth).0F.InvokeMethod(navigationData).TextSymbolClass() textSym. fnt. } Multithreading with geoprocessing To use GP in an asynchronous/multithreaded application.Name = "Arial" fnt.ArcGIS.ADF) Some ArcObjects libraries make use of classes and interfaces defined within the standard Object Linking and Embedding (OLE) libraries from Microsoft.

[C#] public static object GetIPictureDispFromBitmap(System.OLE class.NET] Public Shared GetIPictureDispFromBitmap (ByVal bitmap As System.Utility. These are static (shared in VB. which is part of the ESRI.NET) members and therefore can be called without the need to instantiate the OLE class.Drawing.Windows.Bitmap bitmap) [VB. • GetIFontDispFromFont—This method can be used to convert an existing .dll assembly.dll assembly. Syntax information The following shows syntax information for the members of the ESRI.namespace.Drawing. These members depend on the System.NET System.NET System.Icon) As Object Using members of the OLE class The following are some code examples of using the members of the OLE class: [C#] .Forms.Drawing.ArcGIS.ADF.AxHost class and as such are only suitable for use within a project that has a reference to the System.Drawing.Forms.ArcGIS.NET] Public Shared GetIPictureDispFromIcon (ByVal icon As System. [C#] public static object GetIPictureDispFromIcon(System.StdPicture object.Icon object into a Stdole.Drawing.COMSupport. provides the OLE class that can help you to perform such conversions.Drawing.NET System.NET] Public Shared GetIFontDispFromFont (ByVal font As System.Drawing.StdPicture object.Windows. [C#] public static object GetIFontDispFromFont(System.Drawing.Font) As Object • GetIPictureDispFromBitmap—This method can be used to convert an existing .StdFont object.Icon icon) [VB.Drawing.Font font) [VB.Bitmap) As Object • GetIPictureDispFromIcon—This method can be used to convert an existing .Font object into a Stdole.Bitmap object into a Stdole.

ESRI.bmp") .Display.Font = ESRI.IMapControlDefault map = this.IFontDisp.ITextSymbol = New ESRI.ArcGIS.Drawing.ArcGIS.COMSupport.0F).IMapControlDefault. [VB.ArcGIS.COMSupport.bmp").Icon dotNetIcon = new System.ArcGIS.MouseIcon = ESRI.ArcGIS.Display.SystemUI. 25.MapControl.MousePointer = ESRI.IPictureMarkerSymbol bmpSym = new ESRI.esriControlsMousePointer.ADF.Object as ESRI. ESRI.ArcGIS.MapControl. textSym.PictureMarkerSymbolClass()as ESRI.ITextSymbol.ico").Drawing.esriPointerCustom. map.ADF.Drawing.Bitmap("C:\Temp\MyBitmap.Display.ADF.OLE.0F) Dim textSym As ESRI.Bitmap = New System.Drawing. bmpSym.ADF.Picture = ESRI.ArcGIS. map.OLE.Font = ESRI.COMSupport.ArcGIS.Drawing.GetIFontDispFromFont(dotNetFont)as stdole.System.Drawing.IPictureDisp.Font("Castellar".ArcGIS.NET] Dim dotNetFont As New System.axMapControl1.Display.Icon(@"C:\Temp\MyIcon.Bitmap dotNetBmp = new System.Bitmap(@"C:\Temp\MyBitmap. System.IPictureMarkerSymbol.TextSymbolClass() as ESRI.ArcGIS.Drawing. System.GetIFontDispFromFont(dotNetFont) Dim dotNetBmp As System.Drawing.OLE.GetIPictureDispFromIcon(dotNetIcon)as stdole.ArcGIS.Display.OLE.ArcGIS.ArcGIS. ESRI.Drawing.TextSymbolClass textSym.ITextSymbol textSym = new ESRI. 25.ArcGIS.COMSupport.ArcGIS.Display.Display.Display.Font dotNetFont = new System.Font("Castellar".GetIPictureDispFromBitmap(dotNetBmp) as stdole.IPictureDisp.

COMSupport.Display. Since ArcObjects geometries are Component Object Model (COM) components marked as a single threaded apartment (STA). Also.ArcGIS.OLE.Drawing.GetIPictureDispFromIcon(dotNetIcon) map.IMapControlDefault = Me. you may have to share geometries across multiple threads (for an instance in an architecture where there is one rendering thread and other update threads).esriControlsMousePointer.Icon("C:\Temp\MyIcon. when it comes to geometries such as polylines and polygons.PictureMarkerSymbolClass bmpSym. if you call these geometries from any non-main thread in your application.IPictureMarkerSymbol = New ESRI.ArcGIS.MousePointer = ESRI.NET platform—you may need to work in multiple threads. However. When it comes to managing points data.ArcGIS.Icon = New System.ArcGIS. you pay the penalty of cross apartment calls. which significantly affects the performance of your application.NET environment—by design or due to the nature of the . the problem becomes trickier to solve.ico") Dim map As ESRI.Object map.MapControl.OLE. Development licensing Engine Developer Kit ArcView ArcEditor ArcInfo Deployment licensing Engine Runtime ArcView ArcEditor ArcInfo About using IGeometry to update dynamic geometries When it comes to implementing a dynamic geographic information system (GIS) in the .GetIPictureDispFromBitmap(dotNetBmp) Dim dotNetIcon As System. .MouseIcon = ESRI.Dim bmpSym As ESRI.ArcGIS.ADF. there is a straightforward solution—manage your point as raw x. then update the point geometry on the main thread as required.ArcGIS.y coordinates.esriPointerCustom How to use IGeometryBridge to update dynamic geometries SummaryIGeometryBridge is a powerful interface that allows you to overcome most of the issues for using ArcObjects geometries in a highly-dynamic environment and for using geometries across multiple threads.Picture = ESRI.SystemUI.ADF.Drawing. such as timer events or other asynchronous callbacks.COMSupport.Display.AxMapControl1. You may be required to update geometries on different events.

you repeatedly have the delay from crossing the interop layer. an empty one-dimensional array of WKSPoints.NET application—the overhead of the COM interop when there is extensive allocation of new ArcObjects. as well as reducing the issue of the interop overhead to an absolute minimum. IGeometryBridge comes in handy. In most cases. you have to constantly update the entire ArcObjects geometry each time you get a new geometry from the external feed. as well as the overall number of vertices beforehand When only known vertices change on each update cycle In the following sections. This is where the WKSPoint structure becomes beneficial. if you are creating ArcObjects in a tight loop. 1. if you are creating new ArcObjects geometries or update existing ones.NET] Private m_polygon As IPointCollection4 = Nothing Private m_wksPoints() As WKSPoint = Nothing . In particular. For more information on the issues interacting with the COM interop. In your class.NET environment). which means that sharing these geometries across multiple threads is no longer an issue. private IGeometryBridge2 m_geometryBridge = null. To address the previous issues. you can safely share them across multiple threads and avoid the overhead of allocating COM objects. For example. Any geometry is different from the other. the technique involves keeping a class member of one geometry together with an instance of a geometry bridge (GeometryEnvironement). consider another factor that can affect the performance of your . It allows you to create and update your ArcObjects geometries using simple types. Update an entire geometry on each update cycle This scenario is possible when you are receiving geometries from an external feed and have to analyze or draw these geometries using ArcObjects. which means it is treated by the .NET framework as a simple type (using it across multiple threads is permissible and there is almost no overhead for using it in a . [VB. and a geometry bridge. you will find step-by-step instructions for implementing these two scenarios. In such cases. there are two scenarios where you may be required to update geometries: • • When the entire geometry constantly changes and you do not know which of the vertices changed. WKSPoint is defined as a simple C++ structure. By managing your geometries as arrays of WKSPoints. you do not know any information on the geometry in advance. see Performance of ArcObjects. Since most ArcObjects analysis and drawing methods require a standard ArcObjects geometry. which drastically slows your application. private WKSPoint[] m_wksPoints = null. See the following code example: [C#] private IPointCollection4 m_polygon = null.Before getting into the solution for these geometries. there are a number of calls across the interop layer. add three additional class members to handle the geometry—a point collection that stores the ArcObjects geometry. therefore.

double Y. [C#] int count = 0. as long as you synchronize the threads. foreach (DataRow r in m_table. m_geometryBridge = new GeometryEnvironmentClass(). populate the WKSPoint array and use the geometry bridge to replace all existing vertices of the ArcObjects geometry with the vertices for the WKSPoint array. .Rows. m_geomeTryBridge = New GeomeTryEnvironmentClass() • To update the ArcObjects geometry. Make sure the instantiation is done on the application's main thread (must be defined as STA thread). X = Convert.Private m_geomeTryBridge As IGeomeTryBridge2 = Nothing 2. [VB. See the following code example: 3.NET] m_polygon = New PolygonClass() ' Create the geometry environment class instance. Populate the WKSPoint array. You can update the WKSPoint array from anywhere in your code. lock(m_wksPoints) { m_wksPoints = new WKSPoint[m_table. double X.Rows) { //Get the item's coordinate. // Create the geometry environment class instance. See the following code example: [C#] m_polygon = new PolygonClass(). You can also do this on a different thread.ToDouble(r["X"]). Instantiate the ArcObjects geometry and the geometry bridge.Count].

count++. } } [VB.SetWKSPoints replaces all existing vertices with the one specified by the .Count .X = X m_wksPoints(Count).X = X.Rows ' Get the item's coordinate. // Update the geometry's array.ToDouble(r["Y"). m_wksPoints(Count).Rows. m_wksPoints[count].NET] Dim Count As Integer = 0 Dim X As Double Dim Y As Double SyncLock m_wksPoints m_wksPoints = New WKSPoint(m_table. X = Convert. Update the ArcObjects geometry using the geometry bridge.Y = Y. Calling IGeometryBridge2.ToDouble(r("Y")) ' Update the geometry's array.Y = Y Count + = 1 Next r End SyncLock 4. m_wksPoints[count].ToDouble(r("X")) Y = Convert.Y = Convert.1){} For Each r As DataRow In m_table.

InsertWKSPoints or IGeometryBridge2. When you are done updating the geometry.AddWKSPoints.WKSPoint array.DrawPolygon(m_polygon) Add a single vertex to an existing geometry on each update cycle In other cases. lock the array before. } [VB. you will only need to update a single vertex at a time. either adding an additional vertex to your geometry (for an instance when drawing the trail of a moving object) or updating vertices at a known index inside the geometry. if you are updating the WKSPoint array on a different thread. private WKSPoint[] m_wksPoints = new WKSPoint[1]. [VB. A point collection that stores the ArcObjects geometry. SyncLock m_wksPoints m_geometryBridge. 1. Again. a one-dimensional array of WKSPoints with one element in it. In your class. See the following code example: [C#] DynamicDisplay.NET] DynamicDisplay.SetWKSPoints(m_polygon. m_wksPoints) End SyncLock 5.NET] ' Update the ArcObjects geometry as needed. and a geometry bridge. see the sample Dynamic biking. ref m_wksPoints). . use it as needed. [C#] // Update the ArcObjects geometry as needed.DrawPolygon(m_polygon). lock(m_wksPoints) { m_geometryBridge. For more information. private IGeometryBridge2 m_geometryBridge = null. add three additional class members to handle the geometry. you only need to manage a single WKSPoint and use IGeometryBridge2. In such cases. See the following code example: [C#] private IPointCollection4 m_bikeRouteGeometry = null.SetWKSPoints(m_polygon.

as long as you synchronize the threads. Instantiate the ArcObjects geometry and the geometry bridge.NET] m_bikeRouteGeometry = New PolylineClass() ' Create the geometry environment class instance. You can also do this on a different thread.[VB. lock(m_wksPoints) { m_wksPoints[0]. See the following code example: [C#] m_bikeRouteGeometry = new PolylineClass(). Make sure the instantiation is done on the application's main thread (must be defined as STA thread).NET] Private m_bikeRouteGeometry As IPointCollection4 = Nothing Private m_geometryBridge As IGeometryBridge2 = Nothing Private m_wksPoints As WKSPoint() = New WKSPoint(0){} 2.NET] ' Update the bike trail. // Create the geometry environment class instance.Y. [VB. m_wksPoints[0].position. You can update the WKSPoint array from anywhere in your code.position.X. See the following code example: [C#] // Update the bike trail.X = m_bikePositionInfo. m_geometryBridge = New GeometryEnvironmentClass() 3. } [VB.Y = m_bikePositionInfo. m_geometryBridge = new GeometryEnvironmentClass(). Populate the WKSPoint inside the array (array with one element). SyncLock m_wksPoints .

Update the ArcObjects geometry using the geometry bridge.AddWKSPoints adds the additional point at the end of the geometry. See the following code example: [C#] dynamicDisplay. if you are updating the WKSPoint array on a different thread. } [VB.2 do not always appear in Visual Basic (VB) .DrawPolyline(m_bikeRouteGeometry) Hidden managed classes not appearing in VB. lock(m_wksPoints) { m_geometryBridge.NET] dynamicDisplay. All Component Object Model (COM) coclasses are converted to managed classes. lock the array before.Y End SyncLock 4.X = m_bikePositionInfo. Again. the managed classes have the same name as the original with "Class" appended.m_wksPoints(0).position.AddWKSPoints(m_bikeRouteGeometry.position.DrawPolyline(m_bikeRouteGeometry). This conversion is part of ESRI's . These managed classes show as hidden types in the Object Browser of Visual Studio. [VB.Y = m_bikePositionInfo.NET] ' Update the ArcObjects geometry. ref m_wksPoints). Calling IGeometryBridge2.NET IntelliSense About hidden managed classes not appearing in VB. m_wksPoints) End SyncLock 5. See the following code example: [C#] // Update the ArcObjects geometry.NET IntelliSense Hidden managed classes for the primary interop assemblies (PIAs) of ArcObjects 9.AddWKSPoints(m_bikeRouteGeometry. SyncLock m_wksPoints m_geometryBridge.NET IntelliSense for Visual Studio 2005. use it as needed.X m_wksPoints(0). When you are done updating the geometry.

See the following screen shot: .NET.provided PIAs for ArcObjects in . typing the following code in Visual Studio will show the hidden types. See the following screen shot: On other computers. The managed classes that are available are shown as hidden types in the Visual Studio 2005 Object Browser. IntelliSense in Visual Studio 2005 for VB. For example.NET will not display these hidden types. so typing the following code in Visual Studio will not show the hidden types. See the following screen shot: On some computers. the runtime-callable wrapper for the Point coclass is PointClass.

NET Framework. The behavior of IntelliSense regarding hidden managed classes in C# of Visual Studio 2005 has not been observed. and Finally construct.NET is known as structured exception handling. An instance of an exception is created and thrown when the . you can use those classes in your project and compile solutions. See the following code example: [C#] try . although this model is not discussed in this topic. Catch. Using exceptions Exceptions are used to handle error conditions in Visual Studio . Try. An attempt is made to rotate an envelope. which throws an error.NET or C#.Exception by allowing further access to information about the specific type of error that has occurred. Many different types of exceptions are provided by the . You can handle exceptions by using the Try.NET allows backward compatibility by also providing unstructured exception handling via the familiar OnError GoTo statement and Err object. Each type extends the basic functionality of the System. Exception handling Structured exception handling implementation is straightforward. How to implement error handling SummaryThe error handling construct in Visual Studio .NET Framework encounters an error condition. and the same concepts are applicable to VB. The constructs used may be new to Visual Basic users but should be familiar to users of C++ or Java. and it is also possible to create your own exceptions.While IntelliSense may not display the hidden managed classes.NET and provide information about the error condition. An example of this construct is shown below.Exception base class. An exception is an instance of a class that inherits from the System. and Finally construct This construct allows you to catch errors that are thrown within your code. Catch. VB.

LowerLeft.Show("Error: " + ex.Message). } [VB. env. 1D). 10D.Message) ' Clean up the code.Show("Error: " + ex. 0D. The Catch block handles a thrown error. } { // Clean up the code. 1D) Catch ex As System. } catch (System.PutCoords(0D.PutCoords(0D. 10D). 10D) Dim trans As ITransform2D = env trans.LowerLeft.Exception ex) { MessageBox.Rotate(env. You can have more than one Catch block to handle different kinds of errors.Exception MessageBox. The application executes the Catch block when the Type of a thrown error matches the error Type specified by the Catch block. trans. If the application throws an error within the Try block.{ IEnvelope env = new EnvelopeClass(). The following code example verifies if the exception thrown is a DivideByZeroException: [C#] . the point of execution switches to the first Catch block.Rotate(env. End Try Place a Try block around code that can fail. ITransform2D trans = (ITransform2D)env. 10D. 0D.NET] Try Dim env As IEnvelope = New EnvelopeClass() env.

Exception ex) { // Perform general error handling. C++ developers can easily ignore an error condition in an HRESULT. Finally construct for all entry points to a program. Errors from COM components The structured exception handling model differs from the HRESULT model used by the component object model (COM).Exception ' Perform general error handling. continuing up the call stack until a Catch block is found. If you do have more than one Catch block. you do not need to include a Finally block.. or after a Catch block if an error was thrown. Therefore. Catch.. to clean up resources such as file handles or database connections.NET runtime. which will always succeed the type check. } catch (System. The application always executes the Finally block after the Try block completes. [VB..NET] . the Finally block should contain code that must always be executed.. the exact outcome can depend on the location of the executed code and the configuration of the .catch (DivideByZeroException divEx) { // Perform divide by zero error handling..NET runtime searches for a Catch block in the calling function.Exception. Catch divEx As DivideByZeroException ' Perform divide by zero error handling. it is advisable to at least include a Try.. Catch ex As System. Therefore. the more specific exception Types should precede the general System. Code without exception handling If a line of code that is not contained in a Try block throws an error. } . the . If a Catch block is not specified in the call stack. . If you do not have any cleanup code. for example. .

NET runtime. } catch (COMException COMex) { if (COMex. it is advisable to enclose all code that can raise an error in a COM component within a Try block. } catch (System.LowerLeft.NET runtime handling of errors from COM components is similar to the way COM errors were handled at Visual Basic 6. ITransform2D trans = (ITransform2D)env.However.. } .Exception ex) { MessageBox.. If a .Show("Error: " + ex. [VB. and returns an error condition as the HRESULT.ErrorCode == .NET] . the HRESULT is used to populate an instance of the COMException.ToString() + ": " + COMex. env. where it can be handled in the usual way by using a Try. 10D).Show("You cannot rotate an Envelope").2147220984) MessageBox. in Visual Basic 6. Catch.PutCoords(0D. an error condition in an HRESULT populates the Err object and raises an error. 10D.NET program calls a function in a COM component (through the COM interop services). 1D). This is then thrown by the .Message).Message). Finally block. trans.Rotate(env. Therefore. with a corresponding Catch block to catch a COMException.Show("Error " + COMex.ErrorCode. MessageBox. The following code example is the first example rewritten to check for an error from a COM component: [C#] { IEnvelope env = new EnvelopeClass(). The . 0D.

See the following code example: [C#] catch (System.Show("Error: " + ex.ToString() + ": " + COMex.Exception ex) { throw. You can make use of the message property of the exception to identify the problem. Alternatively. you may want to report the error to users to let them decide the course of action to take.. which you can test to determine the error condition that occurred. 1D) Catch COMex As COMException If (COMex. } . The COMException belongs to the System.Runtime.PutCoords(0D.Rotate(env.Exception MessageBox. if you are writing a function that is only called from other code.Message) .Message) End If Catch ex As System.Dim env As IEnvelope = New EnvelopeClass() env.Show("You cannot rotate an Envelope") MessageBox. You can do this using the Throw keyword. you may want to deal with an error by creating a specific error condition and propagating this error to the caller..ErrorCode = -2147220984) Then MessageBox. To throw the existing error to the caller function.Show _ ("Error " + COMex. you may want to correct the error condition in the code and try the call again. 10D) Dim trans As ITransform2D = env trans. write your error handler using the Throw keyword. 10D. Throwing errors and the exception hierarchy If you are coding a user interface.InteropServices namespace.LowerLeft. However. 0D.ErrorCode. It provides access to the value of the original HRESULT via the ErrorCode property.

Exception Throw New ApplicationException _ ("You had an error in your application") .. This creates an error hierarchy.. However.NET] Catch ex As System. } . [VB... [VB.. ex). This property should be set to equal the caught exception before the new exception is thrown. the exception includes the InnerException property. The following code example uses the ApplicationException constructor to set the message property: [C#] catch (System. . populate it appropriately.. and throw this exception back to the caller. the original exception is lost. Again.NET] Catch ex As System.Exception . if you do this...Exception ex) { System. To allow complete error information to be propagated. the following code example uses the ApplicationException constructor to set the InnerException and message properties: [C#] catch (System. ex). If you want to propagate a different or more specific error back to the caller. create a new instance of an exception..ApplicationException appEx = new ApplicationException( "You had an error in your application".Exception ex) { throw new ApplicationException("You had an error in your application".

the HRESULT is used to populate an instance of the COMException. ex) Throw appEx .Exception Dim appEx As System. see Best Practices for Handling Exceptions on the MSDN Web site. For more information. Catch.NET runtime. Finally blocks and the COMException. and returns an error condition as the HRESULT. Detailed instructions for the number conversions that are often necessary when working with HRESULTs is also provided. which is available as part of your SDK installed help or in the Documentation Library on the ESRI Developer Network (EDN) Web site. [VB.NET. Writing your error handler The best approach to handling an error depends on what error is thrown and in what context. . } .NET software development kits (SDKs). the function that eventually deals with the error condition can access all the information about the cause of the condition and its context.NET program calls a function in a COM component. Catch. receive errors from COM components. where it can be handled by using a Try. see How to implement error handling. Most HRESULT error constants for ArcObjects libraries are documented throughout the ArcGIS ..ApplicationException = _ New ApplicationException("You had an error in your application". This topic also shows what an HRESULT is and how to locate information on these error codes in the ArcGIS .NET SDK help system (see ArcObjects HRESULT error codes in this topic). ArcObjects error codes SummaryThis topic gives an overview of HRESULTs as they relate to ArcObjects and helps you decipher HRESULT error codes you receive while developing and programming. If a .throw appEx. The . All COM methods return an unsigned integer (HRESULT). If you throw an error.. HRESULT error codes When programming with ArcObjects in . HRESULT values are often used to return error information that is not specifically considered error codes. Finally block.NET runtime handling of errors from COM components is similar to the way Visual Basic 6 handled COM errors. This exception is thrown by the . HRESULT is essentially a structure that shows whether the method succeeded or failed and contains additional detailed information about the outcome of the operation... the application executes the current function's Finally clause before control is returned to the calling function.NET] Catch ex As System. In this way. For more information on implementing Try. you can make calls to Component Object Model (COM) components and therefore.

If you have received an eight-digit hexadecimal error code. The majority of the ESRI ArcObject's error code enumerations are in the 10-digit decimal format. These libraries are ESRI.ArcGIS.ArcGIS. Instead. Locating HRESULTs HRESULTs are returned in an eight-digit hexadecimal form (80004005) or a 10-digit decimal form (–2147467259). you receive a general COM error. there are currently no specific HRESULT error codes for that library. ArcObjects HRESULT error codes The following table shows HRESULT error codes for ArcObjects: Enumeration esriDataConverterError esriGeometryError esriSpatialReferenceError cadastralError dimError annoError esriRasterLayerError esriImageServerError esriNETCDFError esriRasterLoaderError esriRasterError esriRasterEncoderError esriRepresentationDrawingError Library Geodatabase Geometry Geometry Cadastral Carto Carto Carto Carto DataSourcesNetCDF DataSourcesRaster DataSourcesRaster DataSourcesRasterUI Display Min value 513 513 514 –2147221247 –2147220991 –2147220991 –2147217152 –2147216896 –2147217408 –2147217407 –2147217408 –2147217408 –2147218431 Max value 532 602 524 –2147220732 –2147220989 –2147220985 –2147217152 –2147216895 –2147217401 –2147217408 –2147217370 –2147217403 –2147218425 . If a library is not listed.Geodatabase (esriDataConverterError enumeration). see Common HRESULT Values on MSDN. For a list of common MSDN HRESULT values. convert the eight-digit hexadecimal to a 10-digit decimal. This conversion is not difficult and can be accomplished with the Windows Calculator.HRESULTs that are not specific to ArcObjects can be found on the Microsoft Developer Network (MSDN) Web site. see Converting 10-digit decimal value to three-digit enumeration value in this topic. For information on how to convert a 10-digit decimal value to a three-digit enumeration value. Some of the ArcObjects libraries utilize a three-digit value instead of a 10-digit decimal value.Geometry (esriGeometryError and esriSpatialReferenceError enumerations) and ESRI. The following ArcObjects HRESULT error codes table lists all available error enumeration pages and the range of HRESULT values each library contains.

click View and click Scientific. . Select the Hex and Dword radio buttons. If you do not see the Hex.esriEditorError esriSpatialAnalystError esriXYEventError esriNetworkErrors fdoError esriTinError esriRepresentationError esriGeoDataServerError esriTerrainError esriGeocodingError esriRouteEventError esriUtilityNetworkErrors esriExportErrorReturnCodes esriOutputErrorReturnCodes esriSchematicErrors esriCoreErrorReturnCodes messageSupportError tascTMSWorkspaceError tascGSDCoreError tascTemporalLayerError Editor GeoAnalyst Geodatabase Geodatabase Geodatabase Geodatabase Geodatabase GeoDatabaseDistributed GeoDatabaseExtensions Location Location NetworkAnalysis Output Output Schematic esriSystem esriSystem TrackingAnalyst TrackingAnalyst TrackingAnalyst –2147220990 –2147217408 –2147220991 –2147220991 –2147220991 –2147219456 –2147218687 –2147208551 –2147205120 –2147220991 –2147220991 –2147220911 –2147220735 –2147220686 –2147218943 –2147221247 –2147220991 –2147220480 –2147219456 –2147219199 –2147220990 –2147217248 –2147220983 –2147220879 –2147212030 –2147219388 –2147218670 –2147208540 –2147219396 –2147220969 –2147220956 –2147220225 –2147220719 –2147220670 –2147218923 –2147221243 –2147220967 –2147220352 –2147219392 –2147219135 Converting eight-digit hexadecimal value to 10-digit decimal value Do the following to convert an eight-digit hexadecimal value to a 10-digit decimal value: 1. and Bin radio buttons. Open Windows Calculator. 2. Dec. Oct.

and type or paste the eight-digit hexadecimal value in the text field (exclude 0x if it precedes the eight-digit number). Click the + button. See the following screen shot: 4. See the following screen shot: 5. Click the Bin radio button. 6. Click the Not button. click the number 1 button. then click the = button to add one. Ensure the Num Lock is on. .3.

External Tools. Open Windows Explorer. 1. In Visual Studio. click Open. Type or paste the 10-digit decimal HRESULT referenced in Step 2 in the Value field. and click Add. Click the Dec radio button.button. Navigate to Program Files\Microsoft Visual Studio 8\Common7\Tools to use errlook. The tool now resides under the Tools menu in Visual Studio. Click the Ellipsis button and navigate to Program Files\Microsoft Visual Studio 8\Common7\Tools. add a title for the tool and click OK.7. Access Microsoft's error code lookup utility (ErrLook. Click the +/. –2147220984). 3. including the minus (-) sign in. In the Open File dialog box. -orb.exe from this location. then click errlook. In the External Tools dialog box. do the following steps to obtain the three-digit value from your 10-digit error value.exe) with Microsoft Visual Studio by doing one of the following: a. Converting 10-digit decimal value to three-digit enumeration value If you have obtained a 10-digit error from the Geometry (esriGeometry and esriSpatialReference) or Geodatabase (esriDataConverter) libraries.exe utility (or access it from the Visual Studio Command Prompt). Open the ErrLook. Locate the 10-digit decimal HRESULT you obtained in Step 8 of the preceding section (that is. click Tools. See the following screen shot: 8. 2. See the following screen shot: .exe.

Alternatively. such as No error found. open the Visual Studio Command Prompt. See the following screen shot: 4. and press Enter to access the Error Lookup utility. Click Lookup in the Error Lookup dialog box to get the converted value and ignore error messages that may be returned. type errlook at the prompt. See the following screen shot: . The hexadecimal conversion of your HRESULT appears in the Value field.

the same error code values can be used across libraries and between systems (that is. you do not want the system error message. Although it may be confusing at first. Open the Windows Calculator and select the Hex radio button. 5. See the following screen shot: If you do not see the Hex radio button in the Windows Calculator. you need the ESRI error message.exe utility. Microsoft and ESRI).There is a system error message associated with this HRESULT error code. Copy and paste the last three digits (208) from the hexadecimal code you received in the errlook. click the View menu and click Scientific (instead of Standard). In this case. Select the Dec radio button to convert to the three-digit value for the error code. 6. instead. See the following screen shot: .

7. Converting error codes without the Error Lookup utility If you do not have the Error Lookup utility. Use the three-digit code (520) to find the error on the library documentation page. do the following steps to convert from the 10-digit code to the eight-digit hexadecimal value. then convert to the three-digit decimal value: 1. . Open the Windows Calculator and select the Dec radio button. See the following screen shot: 2. Paste the 10-digit code (–2147220984) into the calculator.

Subtract 1. Click the Not button (verify the Dword radio button is selected). See the following screen shot: 4. .3. Select the Bin radio button. Click the +/. 6.button to get the positive number. 5. See the following screen shot: 7. Select the Hex radio button to get the hexadecimal form of the error code.

See the following screen shot: 9. Error reports contain all the important information about the application that was running when it failed.8. you must download the symbol files from Microsoft and ESRI and have Visual Studio 2005 (minimum) installed. This excludes ArcEngine. make note of the last three digits (208) in the number. ArcGIS Developers are able to open the dump file within Visual Studio and go directly to the function call where the error occurred. developers can leverage the information contained in the ESRI error report. With the Hex radio button selected. Select the Dec radio button to get the three-digit (520) value you need to look up the error Debugging crashes using the ESRI Symbol Server SummaryAt ArcGIS 9. 10. In order to view and debug an error report. and information for opening and debugging the error report in Visual Studio.3 Desktop Applications and Extensions. At ArcGIS 9. With the Hex and Dword radio buttons selected. the user is prompted to send in the error report (via a web service) to ESRI. code in the documentation. Error Reports . The minimum requirements for working with an error report are Microsoft Visual Studio 2005 and access to the ESRI ArcGIS Symbol Server. ArcGIS Desktop applications provide technologies that capture software crash data that developers can utilize as an additional debugging tool. In the event that ArcMap crashes due to a custom application command or extension. symbol files. If the ArcGIS application crashes. the ArcGIS Desktop applications will be able to generate user-mode error reports when ArcGIS applications fail. clear the calculator and add the three digits from Step 8. these error reports are created for ArcGIS 9. The following sections detail error reports.3.3. Introduction ESRI is dedicated to continually improving the quality of its suite of ArcGIS Products.

com/downloads/symbols for Microsoft symbols and http://downloads2. Launch Microsoft Visual Studio 2005 or 2008. 4.3. 3. Since you are using the Microsoft public symbol server.com/Support/symbols/ for ESRI symbols (As shown below) These servers are for downloading symbols only. Since you are using symbols on a remote symbol server. To view an error report. click Options. (For example: C:\SymbolsCache) Click OK.pdb) locations and add http://msdl. Symbol files provide a footprint of the functions that are contained in executable files and dynamic-link libraries (DLLs). and then open the error report file in Visual Studio as outlined in the following sections. To do this. Ensure ‘Search the above locations only when symbols are loaded manually’ is not checked. open the Debugging node.microsoft. By default. 7. Click ‘Yes’ to accept the agreement and download symbols to your local cache. 8. Symbol Files To debug an ArcGIS Desktop error report. the ArcGIS Desktop applications and extension will produce an error report if the application fails. Edit the Symbol file (. .esri. When the number of error reports in the directory exceeds 10. you can improve performance by creating and specifying a local directory to which the symbols can be copied. you must have access to the Symbol files for both ArcGIS and Microsoft. enter a path in the Cache symbols from symbol server to this directory box.dmp" file extension. 6. the most recent 10 error reports are saved to the following local user directory: %APPDATA%\ESRI\ErrorReports and have a ". 2. they are not browseable. Regardless of whether the user decides to send the error report. 1. you may encounter an End User License Agreement dialog box as you initially access the symbols. download the appropriate symbol files first. and click Symbols. On the Tools menu. In the Options dialog box. An error report contains important information about where the failure occurred. 5. Additionally. See the Microsoft article on How to use a Symbol server for more information. the oldest error reports are automatically deleted as new error reports are saved. the error dialog that appears allows the user to send the error report to ESRI via a web service.Beginning with ArcGIS 9. symbol files can present a roadmap of the function calls that lead to the point of failure. The initial downloading of symbols from the symbol file location may take a substantial amount of time.

In the Open Project dialog box. Start debugging by pressing F5. If it is not already open. On the File menu. Please see the following article for more information on downloading and using SOS: http://msdn2.Opening an Error Report and Finding the Exception Although Visual Studio can read error reports that contain information about both managed code and unmanaged code. see Microsoft’s article on How to Save and Open Dump Files. 3.com/en-us/library/yy6d2sxs. and then click Project. This will take you to the location where the exception occurred. It will usually have a .dmp extension. 4. 5. Error Report Registry Settings ESRI recommends that developers only modify these settings when in the midst of internal development of their own customization prior to deployment to end-users. For additional information on registry settings. For more information on opening an error report. see Error Report Registry Settings. for managed code you must use a tool called SOS from Microsoft. locate and select the dump file. launch Visual Studio 2005 or 2008. Under no circumstances should developers have users of their deployed customization redirect the error reporting mechanism so that crash information is no longer sent to ESRI.aspx 1. click Open.microsoft. Click OK. . 2.

By default. The error report is saved to your local disk only (not sent to ESRI). Mouse Move. many events are occurring at a particular instant—for example. This document explains how to wire ArcObjects . and EnableWebService are set to True. you must hook events through delegates.NET events SummaryAn event is the way a Windows application receives notification.Registry Settings ArcGIS administrators and developers can configure how the error reports operate for their organization by customizing the local machine registry. The email address that appears in the dialog when EnableWebService is set to False (default ArcGISErrorReport@esri. Working with events . you can then follow the steps in the "Restore the registry" section to restore the registry to its previous state. Registry Key Name EnableErrorReport Value Action DWORD (0 or 1) If set to 0. In a Windows application. no dialog will be shown. The error report is saved to your local disk. An error report (. Before you edit the registry. and Mouse Click. EnableErrorReport.NET. These problems might require that you reinstall the operating system. If set to 1 send the error reports to the web service. ShowErrorDialog DWORD (0 or 1) String EmailAddress YourEmailAddress String EnableWebService DWORD (0 or 1) DWORD (0 to 100) CacheSize How to wire ArcObjects .NET events. Mouse Out. The number of error reports to save on your disk. export the keys in the registry that you plan to edit. The email address that will be sent to the web service.com). Warning: Serious problems might occur if you modify the registry incorrectly by using Registry Editor or by using another method. which are function pointers (hold references to functions). If set to 0. The error report is saved to your local disk. If a problem occurs. Use the registry keys in the following table to customize the error reporting. You will see a dialog letting you know that an error has occurred. or back up the whole registry. The error report is saved to your local disk. navigate to HKCU\Software\ESRI\Settings\ErrorReports and create keys as necessary.dmp) file is saved to disk. The error report is saved to your local disk. In your computer’s registry. ShowErrorDialog. error reporting is disabled. In .

NET Framework defines a special type (delegate) that provides the functionality of a function pointer. Cast the relevant event interface (you can also do this in line). See the following code example: [C#] delegate int SomeDelegate(string s. Saying that this delegate has a signature means it returns an int type and takes two parameters: string and bool. When you create an instance of a delegate. the event handler method must match the delegate signature declared by the event interface delegate. you must provide an event handler (an event handling method) that executes program logic in response to the event and register the event handler with the event source. the event sender class does not know which object or method receives (handles) the event it raises.AfterDraw). IGlobeDisplayEvents. The object that raises (triggers) the event is the event sender. In ArcObjects. Unlike other classes. you pass in the function name (as a parameter for the delegate's constructor) that this delegate references. To consume an event in an application. The action can be caused by user interaction. because they are automatically suffixed with _Event by the type library importer. event interfaces (also known as outbound interfaces) have an _Event suffix. Delegates A delegate is a class that holds a reference to a method.NET] Dim globeDisplayEvents As IGlobeDisplayEvents_Event = CType(m_globeDisplay. An intermediary or pointer mechanism is necessary between the source and the receiver. In event communication. This process is event wiring. IGlobeDisplayEvents_Event) . such as a mouse click. bool b). This process is event wiring. See the following code example: [C#] IGlobeDisplayEvents_Event globeDisplayEvents = (IGlobeDisplayEvents_Event) m_globeDisplay. //A delegate declaration.NET] Delegate Function SomeDelegate(ByVal s As String. you must provide an event handler (an event handling method) that executes program logic in response to the event and register the event handler with the event source. Required steps to start listening to an ArcObjects event 1. The object that captures the event and responds to it is the event receiver.NET. [VB. ByVal b As Boolean) As Integer 'A delegate declaration. when you are about to hook an event (for example. or it can be triggered by some other program logic. [VB. To consume an event in an application. The event handler must have the same signature as the event delegate.An event is a message sent by an object to signal the occurrence of an action. A delegate is equivalent to a type-safe function pointer or a callback. The . a delegate class has a signature and can hold references only to methods that match its signature. For that reason.

AddressOf OnAfterDraw Alternatively.AfterDraw -= new IGlobeDisplayEvents_AfterDrawEventHandler(OnAfterDraw).NET] Private Sub OnAfterDraw(ByVal pViewer As ISceneViewer) 'Your event handler logic.NET] RemoveHandler (CType(m_globeDisplay. See the following code example: [C#] globeDisplayEvents. Implement the event handler method specified by the signature of the delegate. [VB. AddressOf OnAfterDraw 3. End Sub 4. [VB.AfterDraw.AfterDraw += new IGlobeDisplayEvents_AfterDrawEventHandler (OnAfterDraw). IGlobeDisplayEvents_Event)).AfterDraw. See the following code example: [C#] ((IGlobeDisplayEvents_Event)m_globeDisplay). Register the event handler method. See the following code example: [C#] private void OnAfterDraw(ISceneViewer pViewer) { //Your event handler logic. Cast the relevant event interface. . Unwire the event from your application once you no longer need to listen to it.2. } [VB.NET] AddHandler globeDisplayEvents. let Visual Studio do the work so you don't have to add the delegate signature: 1.

Wiring events of member objects In many cases.AfterDraw += new . Use the Visual Studio integrated development environment (IDE) Intellisense capabilities to do most of the work for you. Once you add the += operator. AfterDraw). private IGlobeHookHelper m_globeHookHelper = null. you will need to wire events of objects that are members of container objects and are accessed through a property. Visual Studio automatically adds the event handler method. you can modify the default event handler method name by using the arrow keys to scroll and change the name. if (m_globeHookHelper == null) m_globeHookHelper = new GlobeHookHelper(). For example. Press the Tab key twice. Visual Studio adds the event handler method and automatically shows the event handler implementation code. the GlobeHookHelper is a class member. Start the registration of the requested event. ((IGlobeDisplayEvents_Event)m_globeHookHelper. See the following code example: [C#] //Class members. Do notuse the mouse to accomplish this.Hook = hook. In this case. m_globeHookHelper. See the following screen shot: Before pressing the Tab key the second time. and Visual Studio registers the event handler and adds the event handler method.GlobeDisplay). //Set the hook. Whether or not you use the IDE integration or write the command yourself. public override void OnCreate(object hook) { //Initialize the hook helper.2. Visual Studio allows you to add the delegate by pressing the Tab key. which allows your command to work in ArcGlobe as well. it is almost the obvious choice to use GlobeHookHelper class. it is possible that you will need to implement a command for a GlobeControl application and listen to one of the GlobeDisplayEvents (for example.

events stop firing. When you run the previous code. m_globeHookHelper. the event stops and fires without any exception or warning. If m_globeHookHelper Is Nothing Then m_globeHookHelper = New GlobeHookHelper() End If 'Set the hook. . Eventually. Private m_globeHookHelper As IGlobeHookHelper = Nothing Public Overrides Sub OnCreate(ByVal hook As Object) 'Initialize the hook helper. IGlobeDisplayEvents_Events)). you are guaranteed the event you are listening to continues to fire throughout the lifetime of your class (do not forget to unwire the event once you are done listening).NET] 'Class members. AddressOf OnAfterDraw . when you use the GlobeDisplay property of the GlobeHookHelper to wire the event. This way. However. Once this happens. the local IGlobeDisplay variable that was created by the . This is because internally.GlobeDisplay. End Sub Wiring of the AfterDraw event is done against the GlobeHookHelper.. once method OnCreate is ended. after awhile. � � � } [VB. the hook helper gets a reference to the actual GlobeDisplay and calls AddRef() before passing it back to the client.IGlobeDisplayEvents_AfterDrawEventHandler(OnAfterDraw). The correct way to program this situation is to keep a class member referencing the contained object and thus prevent the garbage collector from disposing of it.AfterDraw. The .Hook = hook AddHandler (CType(m_globeHookHelper.. your code looks something like the following example: [C#] //Class members.NET Framework creates a local variable for the returning object and wires the event.NET Framework gets out of scope and is collected by the garbage collector at some point (meaning that it will eventually call the Release() method).GlobeDisplay property.

m_globeDisplay = m_globeHookHelper. Private m_globeHookHelper As IGlobeHookHelper = Nothing Private m_globeDisplay As IGlobeDisplay = Nothing Public Overrides Sub OnCreate(ByVal hook As Object) 'Initialize the hook helper. � � � } [VB. if (m_globeHookHelper == null) m_globeHookHelper = new GlobeHookHelper().private IGlobeHookHelper m_globeHookHelper = null.GlobeDisplay. public override void OnCreate(object hook) { //Initialize the hook helper.NET] 'Class members. ((IGlobeDisplayEvents_Event)m_globeDisplay). //Set the hook.Hook = hook.AfterDraw += new IGlobeDisplayEvents_AfterDrawEventHandler(OnAfterDraw). . //Get the GlobeDisplay from the hook helper. If m_globeHookHelper Is Nothing Then m_globeHookHelper = New GlobeHookHelper() End If 'Set the hook. m_globeHookHelper. private IGlobeDisplay m_globeDisplay = null.

m_globeHookHelper. AddressOf OnAfterDraw . Once you get a reference to the document. End Sub How to listen to document events SummaryArcGIS Desktop applications are all document based. See the following table: Application ArcMap ArcScene ArcGlobe Default event interface ESRI.ArcScene.ArcGIS.GMxDocument . or closed. IGlobeDisplayEvents_Events)).OnCreate() hook parameter IExtension. To listen to document events of an ArcGIS Desktop application.GlobeDisplay AddHandler (CType(m_globeDisplay...Startup() initializationData parameter 2.ArcGIS.SxDocument ESRI.AfterDraw. opened.ArcMapUI. (Code) The following table shows example entry points to get a reference to the application object: Implementation Command Extension Method and parameter ICommand.ArcGIS.MxDocument ESRI. get a reference to the document object from the application.NET components. Get a reference to the document object 1. This article shows you how to hook up these events in your custom . m_globeDisplay = m_globeHookHelper.ArcGlobe.Hook = hook 'Get the GlobeDisplay from the hook helper. Document events are fired when a document is created. cast it to its event interface.

This handler method is called when the event is fired. Add code in the event handler method to complete this task.ArcGIS. define a method to handle the event. an autocomplete ToolTip is shown and code is automatically stubbed out when the Tab key is pressed twice.ArcGlobe Event interface IDocumentEvents_Event ISxDocumentEvents_Event IGMxDocumentEvents_Event Wire the document event in the event interface When wiring events. (Code) • C#—The Visual Studio 2005 autocomplete feature for C# is very helpful. The NewDocument event is wired to listen to document creation.ArcGIS. m_docEvents.ArcMapUI assembly to your project and declare a modular variable. the handler method signature is simple. (Code) You can also reference the document by the event interfaces in the following table: Application ArcMap ArcScene ArcGlobe Namespace ESRI. (Code) Register handler method to the event 1.3.ArcMapUI. The handler method signature must match the event you are listening to. namespace ArcGISProject { public class howToClass . (Code) • 2. to hold on to the document object.ArcGIS. For the NewDocument event. declare the type of the variable as the common IDocumentEvents_Event interface.NET. Although the default event interface for each application is different. use the += operator in C# and the AddHandler keyword in Visual Basic (VB) . Add a reference to the ESRI.Framework.NET—You have to write the handler method and use the AddressOf keyword to reference the method to complete the AddHandler statement. Once you type the += operator.ArcGIS. Next.ArcScene ESRI. using ESRI. Complete code See the following code example of the complete code: [C#] using ESRI.ArcMapUI ESRI. (Code) VB .ArcGIS.ArcGIS.

Private m_docEvents As IDocumentEvents_Event . private IDocumentEvents_Event m_docEvents = null.ArcGIS. private void SetUpDocumentEvent(IDocument myDocument) { m_docEvents = myDocument as IDocumentEvents_Event.Now.ArcGIS.Windows. m_docEvents.ToLongTimeString()).MessageBox. } //Event handler method.ArcMapUI Public Class howToClass 'Event member variable. } } } [VB.Framework Imports ESRI. void howToClass_NewDocument() { System.Show("New document at: " + DateTime.NET] Imports ESRI.NewDocument += new IDocumentEvents_NewDocumentEventHandler (howToClass_NewDocument). //Wiring.{ //Event member variable.

Sub OnNewDocument() MsgBox("New document at: " + DateTime.ToLongTimeString()) End Sub End Class Code fragments • Code 1 : Getting document object from application—This code requires referencing ESRI.Document • Code 2 : Declare an IDocumentEvents_Event variable—This code requires referencing ESRI.ArcGIS. IDocument appDocument = app.Framework … Dim app As IApplication = CType(hook.Framework and ESRI. IDocumentEvents_Event) AddHandler m_docEvents.Document. � � � IApplication app = hook as IApplication. AddressOf OnNewDocument End Sub 'Event handler method. IApplication) Dim appDocument As IDocument = app.ArcGIS. See the following: [C#] .Framework.ArcMapUI in the project.'Wiring.ArcGIS.ArcGIS.ArcGIS.Now. See the following: [C#] using ESRI.NET] Imports ESRI. Sub SetUpDocumentEvent(myDocument As IDocument) m_docEvents = CType(myDocument.NewDocument.Framework assembly in the project. [VB.

ArcGIS.using ESRI. public class howToClass { private IDocumentEvents_Event m_docEvents = null.Framework Imports ESRI.ArcGIS.ArcGIS.ArcMapUI Public Class howToClass Private m_docEvents As IDocumentEvents_Event Sub SetUpDocumentEvent(myDocument As IDocument) m_docEvents = CType(myDocument. IDocumentEvents_Event) … End Sub End Class • Code 3 : Wiring NewDocument event—This code requires referencing ESRI.ArcGIS.ArcMapUI in the project. .NET] Imports ESRI.ArcGIS.Framework and ESRI.ArcGIS.Framework.ArcMapUI. using ESRI. private void SetUpDocumentEvent(IDocument myDocument) { m_docEvents = myDocument as IDocumentEvents_Event. See the following: [C#] private IDocumentEvents_Event m_docEvents = null. � � � } } [VB.

m_docEvents.ArcGIS.ArcGIS.ArcGIS.ArcMapUI in the project. See the following: [VB.NewDocument. AddressOf OnNewDocument . End Sub • [C#] Code 4 : NewDocument event signature—See the following: void OnNewDocument() [VB.NewDocument. .ArcGIS.NET] Sub OnNewDocument() • Code 5 : Wiring NewDocument event to the handling method (C#)—This code requires referencing ESRI..ArcMapUI in the project. See the following: [C#] m_docEvents. � � � void howToClass_NewDocument(){} • Code 6 : Wiring NewDocument event to the handling method (VB .NET)—This code requires referencing ESRI.NewDocument += � � � } [VB.NET] Private m_docEvents As IDocumentEvents_Event Sub SetUpDocumentEvent(myDocument As IDocument) m_docEvents = CType(myDocument.Framework and ESRI.private void SetUpDocumentEvent(IDocument myDocument) { m_docEvents = myDocument as IDocumentEvents_Event.NewDocument += new IDocumentEvents_NewDocumentEventHandler (howToClass_NewDocument).NET] AddHandler m_docEvents..Framework and ESRI. IDocumentEvents_Event) AddHandler m_docEvents.

NET events Delegates and the AddressOf Operator AddressOf Operator Events and Event Handlers Interoperating with COM .NET) using the IActiveViewEvents interface to demonstrate how it works. and the flow of using custom events can be difficult to follow when embedded in an application. This concept topic pulls from various other sources to more fully explain and tie together wiring events in Visual Basic . There are several terms that are new to .NET.NET (VB. Development licensing ArcView ArcEditor ArcInfo Engine Developer Kit Deployment licensing ArcView ArcEditor ArcInfo Engine Runtime In this topic • Flow of working with events • • • • Step 1: Declare a delegate object variable that handles the specific event Step 2: Create an instance of the delegate using the AddressOf operator Step 3: Dynamically associate an event handler to the delegate object Step 4: Dynamically remove an event handler It is recommended that you first familiarize yourself with the following primer documents to get a general understanding of working with events: • • • • • How to wire ArcObjects .… Sub OnNewDocument() End Sub Wiring custom events using IActiveViewEvents SummaryWiring custom events requires multiple steps that can be confusing.

IActiveViewEvents_AfterDrawEventHandler In VB.Flow of working with events The following code mosaic screen shot shows the flow of working with events: The four steps to wiring and using an event.) while coding. see the names of the hidden types in the Visual Studio 2005 Object Browser and in the ArcObjects help system. they are considered hidden types and do not display as a drop-down option when typing the dot (.NET. however. This member variable is the delegate object that will later be used to hold a type-safe function pointer for the AddHandler and RemoveHandler statements. See the following screen shot: . shown in the previous mosaic screen shot are described in the following sections: Step 1: Declare a delegate object variable that will handle the specific event A member variable ( m_ActiveViewEventsAfterDraw ) is declared that is global in scope for the Class Form1. You can.NET] Private m_ActiveViewEventsAfterDraw As ESRI.ArcGIS.Carto. the various IActiveViewEvents handlers are not displayed in IntelliSense in Visual Studio 2005. use the following specific line of code: [VB. For this step.

ArcGIS. These event interfaces (also known as outbound interfaces) are automatically suffixed with _Event by the type library importer.Step 2: Create an instance of the delegate using the AddressOf operator The delegate object member variable (m_ActiveViewEventsAfterDraw) is set to an instance of the IActiveViewEvents_AfterDrawEventHandler interface.AfterDraw event of the IActiveViewEvents_Event interface. Again. a sub or function) to accomplish the work of your event.) while coding. the delegate object member variable ( m_ActiveViewEventsAfterDraw ) is used for the . When programming with ArcObjects .Carto. See the following screen shot: . the event interfaces all have an _Event suffix. the various _Event handlers are not displayed in IntelliSense in Visual Studio 2005. use the following specific line of code: [VB. This is a type-safe pointer to the procedure (that is. In ArcObjects for . use the event interfaces with the appended _Event suffix as shown in the Visual Studio 2005 Object Browser. In the code mosaic screen shot shown previously.NET. The sub.IActiveViewEvents_AfterDrawEventHandler(AddressOf OnActiveViewEventsAfterDraw) Step 3: Dynamically associate an event handler to the delegate object Using the AddHandler statement. is the procedure that does the work whenever this event is fired. in VB. they are considered hidden types and do not display as a drop-down option when typing the dot (.NET.NET] m_ActiveViewEventsAfterDraw = New ESRI. For this step. you specify which ArcObjects event uses your delegate.NET. OnActiveViewEventsAfterDraw.

inserting balloon text boxes with a mouse click) in one part of your application and a different behavior in another part of your application (for example.NET allows for powerful and flexible applications. performing hyperlinks to other documents with a mouse click) by dynamically wiring events to custom procedures. For this step. m_ActiveViewEventsAfterDraw Dynamically adding and removing event handlers in ArcObjects for . Delegates are . you can code one type of behavior (for example.IActiveViewEvents_Event). the delegate object member variable (m_ActiveViewEventsAfterDraw) is removed as the delegate event of the .Carto. ESRI.ArcGIS. you specify which ArcObjects event you want to remove from a particular delegate. Depending on your application.NET] RemoveHandler CType(map.AfterDraw. In the code mosaic screen shot shown previously. use the following specific line of code: [VB.AfterDraw event of the IActiveViewEvents_Event interface.Step 4: Dynamically remove an event handler Using the RemoveHandler statement.

IVersionEvents_Event publishes the following events: • • • • OnConflictsDetected OnReconcile OnRedefineVersion OnRefreshVersion IVersionEvents2_Event publishes these additional events: • • • • OnArchiveUpdated OnBeginReconcile OnDeleteVersion OnPost .esriSystem Versioned events overview Listeners can subscribe to events from the event interfaces of a version.ArcGIS.NET. IVersionEvents_Event and IVersionEvents2_Event.Geodatabase ESRI.ArcGIS.NET) statements. It is also necessary to add the corresponding references to the project in order to gain access to these APIs. This article explains how to create event listeners and handlers for these events in C# and VB. These interfaces can be accessed by casting an IVersion reference.the mechanism for which a type-safe pointer can be utilized to use the AddHandler and RemoveHandler statements. Development licensing ArcView ArcEditor ArcInfo Engine Developer Kit Deployment licensing ArcView ArcEditor ArcInfo Engine Runtime To use the code in this article. How to listen to versioned events SummaryThe IVersionEvents and the IVersionEvents2 interfaces expose several events that event handlers can listen to. allowing custom applications and extensions to respond to versioning events such as reconciliation and posting. the following namespaces must be referenced via the using (C#) or Imports (VB. • • ESRI.

while in VB.NET the AddHandler statement is used. which instantiates a delegate and adds it to the IVersionEvents. In VB. the AddressOf operator is used to create a procedure delegate for the method. but can be given any name. then cast it to the event interface. The delegate is instantiated much like a class. the += operator is used for this.NET. In C# an instance of the event's delegate type must be created to encapsulate the event handler. The following code example is an event listener's constructor. Creating an event handler An event handler is a method within the event listener that defines the application's behavior in response to an event. ByVal hasConflicts As Boolean) ' TODO: Implement the event handling.OnReconcile event.NET] Public Sub OnReconcile(ByVal targetVersionName As String. . } [VB. [C#] public EventListener(IVersion version) { // Cast the version to the event interface. A common practice for implementing versioned event handlers is to create a class dedicated to listening to events. To tightly couple an event listener with a version. Boolean hasConflicts) { // TODO: Implement the handler. In C#.IVersionEvents2_Event does not extend IVersionEvents_Event. End Sub Adding handlers to events Adding a handler to an event varies slightly between C# and VB.NET. an event listener. by using the new keyword. IVersionEvents_Event versionEvent = (IVersionEvents_Event)version. Event handlers must match the return type and parameter types of the published events. The delegate instance can then be added to the appropriate event on the event interface. The following code shows an event handler for the OnReconcile event without any behavior: [C#] public void OnReconcile(String targetVersionName. the event listener's constructor can accept an IVersion parameter.

versionEvent. EventListener eventListener = new EventListener(version). Instantiating a listener is simple. AddHandler versionEvent.OnReconcile. [VB.OnReconcile += new IVersionEvents_OnReconcileEventHandler (OnReconcile).NET] ' Get the version to be listened to. as shown below: [C#] // Get the version to be listened to. Dim Version As IVersion = versionedWorkspace. Dim eventListener As EventListener = New EventListener(Version) Complete listener implementation The following shows the full implementation of the event listener described above: [C#] . it can be used within custom applications. IVersionEvents_Event) ' Create a procedure delegate and add it to the event. } [VB.FindVersion("QA"). Dim versionEvent As IVersionEvents_Event = CType(Version.FindVersion("QA") ' Create a listener. AddressOf OnReconcile End Sub Instantiating a listener Once the listener is completed.NET] Public Sub New(ByVal Version As IVersion) ' Cast the version to the event interface. IVersion version = versionedWorkspace.// Instantiate the delegate type and add it to the event. // Create a listener.

} public void OnReconcile(String targetVersionName. } } [VB. versionEvent.NET] Public Class EventListener Public Sub New(ByVal Version As IVersion) ' Cast the version to the event interface. AddressOf OnReconcile End Sub Public Sub OnReconcile(ByVal targetVersionName As String. IVersionEvents_Event) ' Create a procedure delegate and add it to the event. ByVal hasConflicts As Boolean) . Dim versionEvent As IVersionEvents_Event = CType(Version.OnReconcile += new IVersionEvents_OnReconcileEventHandler (OnReconcile). Boolean hasConflicts) { // TODO: Implement the event handling. AddHandler versionEvent.public class EventListener { public EventListener(IVersion version) { // Cast the version to the event interface.OnReconcile. // Instantiate the delegate type and add it to the event. IVersionEvents_Event versionEvent = (IVersionEvents_Event)version.

9. setting up the debugger.vb and contains the hook into ArcGIS Desktop. Choose the Tools and Customize menus in ArcMap to open the Customize dialog box. This is because the resulting dialog is not modal allowing you to interact with ArcMap and see the events being fired as they occur. VB. End Sub End Class Wiring events in ArcGIS Desktop using IActiveViewEvents PurposeThe purpose of this sample is to give the ArcGIS Desktop developer the experience of using an ArcGIS Base Command template to launch a Windows form and show how to wire up events for the IActiveViewEvents interface. Open the solution (.cs Launches Form1.mxd). Click the newly added button to launch the Windows form demonstrating how the wiring of events works.vb (view code) CommandWiringEvents. (view code) Form1. You may need to resize and reposition ArcMap on your screen to see the Wiring Events dialog in action. 2. and running the sample (either an exe or dll). 1. 6. Drag the Wiring Events icon from the Commands list onto the graphical user interface (GUI) to test the sample. 8. Developers can learn how the process works for building the basic infrastructure for user interaction with an ArcGIS Desktop client. 4. Click the Commands tab of the Customize dialog box and select Samples ArcGIS in the Categories list box. When the application starts. The open form guides you through the process of using IActiveViewEvents.NETC# CommandWiringEvents. open any existing ArcGIS map document (.' TODO: Implement the event handling. 7. 5.sln) file.exe set as your debugger in Visual Studio. Ensure that you have <your ArcGIS install location>\Program Files\ArcMap\bin\ArcMap. Close the Customize dialog box. 3. . Additional information Note: If your ArcMap session is full screen you may not see the dialog that opens as a result of clicking the newly added button.vb Launches Form1. (view code) Shows the workings of AddHandler and RemoveHandler for event wiring. See Using the samples for help on compiling.cs and contains the hook into ArcGIS Desktop.

txt.InteropServices Imports System.Drawing Imports ESRI. Depending on what products you have installed and whether you installed the samples feature. ' ' See use restrictions at <your ArcGIS install location>/developerkit/userestrictions. CommandWiringEvents.Framework Imports ESRI. Wiring events in ArcGIS Desktop using IActiveViewEvents CommandWiringEvents.ClassId.ADF.CATIDs Imports ESRI.Form1.ArcMapUI <ComClass(CommandWiringEvents. ' Imports System.ADF.InterfaceId. you will find the files associated with this sample in <Your ArcGIS install location>\DeveloperKit\SamplesNET in the Desktop folder in the DesktopWiringEvents folder. treaties. CommandWiringEvents. and conventions.Runtime.cs (view code) Shows the workings of += and -= for event wiring. ' ' You may freely redistribute and use this sample code.BaseClasses Imports ESRI.ArcGIS.EventsId)> _ Public NotInheritable Class CommandWiringEvents Inherits BaseCommand #Region "COM GUIDs" . provided you include the original copyright ' notice and use restrictions.vb ' Copyright 2008 ESRI ' ' All rights reserved under the copyright laws of the United States ' and applicable international laws.ArcGIS.ArcGIS.ArcGIS. with or ' without modification.

ComVisibleAttribute(False)> _ Public Shared Sub RegisterFunction(ByVal registerType As Type) ' Required for ArcGIS Component Category Registrar support ArcGISCategoryRegistration(registerType) 'Add any COM registration code after the ArcGISCategoryRegistration() call End Sub <ComUnregisterFunction(). existing ' clients will no longer be able to access the class. ComVisibleAttribute(False)> _ Public Shared Sub UnregisterFunction(ByVal registerType As Type) ' Required for ArcGIS Component Category Registrar support ArcGISCategoryUnregistration(registerType) 'Add any COM unregistration code after the ArcGISCategoryUnregistration() call End Sub #Region "ArcGIS Component Category Registrar generated code" Private Shared Sub ArcGISCategoryRegistration(ByVal registerType As Type) Dim regKey As String = String. registerType.GUID) .' These GUIDs provide the COM identity for this class ' and its COM interfaces.Format("HKEY_CLASSES_ROOT\CLSID\{{{0}}}". If you change them. Public Const ClassId As String = "baa8a8b3-b6fd-4af3-ad4c-741565178897" Public Const InterfaceId As String = "53e299c3-1be2-4248-bf0b-aeabdb9fb9b0" Public Const EventsId As String = "64efdfbc-bdbe-4fd9-a409-0f993543fb6d" #End Region #Region "COM Registration Function(s)" <ComRegisterFunction().

the class will not be ' registered in the COM registry and cannot be created ' via CreateObject.ArcGIS.NET Wiring Events" MyBase.NET Wiring Events" Try .Framework.NET Wiring Events" MyBase. registerType. otherwise.ArcGIS.m_category = "Samples ArcGIS" MyBase.m_toolTip = "VB.NET Wiring Events" MyBase.GUID) MxCommands.m_message = "VB.New() ' TODO: Define values for the public properties MyBase. Public Sub New() MyBase.MxCommands.IMxApplication Public Shared m_application As ESRI.ArcMapUI.Format("HKEY_CLASSES_ROOT\CLSID\{{{0}}}".Register(regKey) End Sub Private Shared Sub ArcGISCategoryUnregistration(ByVal registerType As Type) Dim regKey As String = String.Unregister(regKey) End Sub #End Region #End Region Public Shared m_mxApplication As ESRI.m_name = "VB.m_caption = "VB.IApplication ' A creatable COM class must have a Public Sub New() ' with no parameters.

ESRI. IMxApplication) End If End If m_application = CType(m_mxApplication.Name + ".bmp" MyBase.'TODO: change bitmap name if necessary Dim bitmapResourceName As String = Me.GetType().m_bitmap = New Bitmap(Me.Message.Trace. "Invalid Bitmap") End Try End Sub Public Overrides Sub OnCreate(ByVal hook As Object) If Not (hook Is Nothing) Then If TypeOf (hook) Is IMxApplication Then m_mxApplication = CType(hook.WriteLine(ex.IApplication) End Sub Public Overrides Sub OnClick() Dim vbForm1 As New Form1 vbForm1. bitmapResourceName) Catch ex As Exception System.Show() End Sub End Class Wiring events in ArcGIS Desktop using IActiveViewEvents Form1.ArcGIS.Framework.vb .Diagnostics.GetType().

with or ' without modification.ArcGIS.ArcMapUI Imports ESRI.Click . ' This member variable will hold the map's IActiveView Event Handler Private m_activeViewEvents As ESRI.EventArgs) Handles btnStep1.' Copyright 2008 ESRI ' ' All rights reserved under the copyright laws of the United States ' and applicable international laws. ' This means they are global in scope for this class.Carto Imports ESRI. ByVal e As System.Int32 Private Sub btnStep1_Click(ByVal sender As System. treaties.ArcGIS. and conventions.Object.IActiveViewEvents_Event ' A member variable to count how many events have fired Private count As System. ' ' You may freely redistribute and use this sample code. ' ' See use restrictions at <your ArcGIS install location>/developerkit/userestrictions.ArcGIS.ArcGIS. ' Imports ESRI.Framework Public Class Form1 ' Notes: ' Variables are prefixed with an 'm_' denoting that they are member variables.Carto. provided you include the original copyright ' notice and use restrictions.txt.

IMxDocument) Dim activeView As IActiveView = mxDocument.XXXXXXXXX EventHandlers ' for the specific events you want to capture. add it to AfterDraw event AddHandler m_activeViewEvents. AddressOf OnActiveViewEventsAfterDraw ' Create an instance of the delegate.NET uses the AddressOf ' operator to emulate 'C++' pointer like functionality. This is one way to pass variables between classes in an application. VB.FocusMap m_activeViewEvents = CType(map.m_application ' Now drill into the ArcObjects to get set the map's IActiveView Event Handler Dim mxDocument As IMxDocument = TryCast(application. Dim application As IApplication = CommandWiringEvents.ActiveView Dim map As IMap = activeView. add it to AfterItemDraw event . ' Create an instance of the delegate.Clear() ' Reset to the counter to display how many events have fired count = 0 ' ***** SECTION (Start): Set up the event handlers for all of the IActiveViewEvents ' Notes: ' You set the member m_ActiveViewEvents.Document. IActiveViewEvents_Event) 'Clear out any displayed event names that get fired TextBox3.' The appliocation variable is set by referencing the public shared member variable in the ' CommandWiringEvents class.AfterDraw.

AddressOf OnActiveViewEventsSelectionChanged ' Create an instance of the delegate. AddressOf OnActiveViewEventsItemReordered ' Create an instance of the delegate.ItemAdded. add it to ContentsCleared event AddHandler m_activeViewEvents. AddressOf OnActiveViewEventsFocusMapChanged ' Create an instance of the delegate. add it to ContentsChanged event AddHandler m_activeViewEvents.SpatialReferenceChanged. add it to SelectionChanged event AddHandler m_activeViewEvents. AddressOf OnActiveViewEventsContentsCleared 'Create an instance of the delegate.ContentsChanged. add it to ItemAdded event AddHandler m_activeViewEvents.SelectionChanged. AddressOf OnActiveViewEventsContentsChanged ' Create an instance of the delegate.ContentsCleared. AddressOf OnActiveViewEventsItemAdded ' Create an instance of the delegate. add it to FocusMapChanged event AddHandler m_activeViewEvents. add it to SpatialReferenceChanged event AddHandler m_activeViewEvents.ItemDeleted. add it to ItemDeleted event AddHandler m_activeViewEvents.AddHandler m_activeViewEvents.ItemReordered.AfterItemDraw. AddressOf OnActiveViewEventsItemDeleted 'Create an instance of the delegate. AddressOf OnActiveViewEventsItemDraw ' Create an instance of the delegate. add it to ItemReordered event AddHandler m_activeViewEvents. AddressOf OnActiveViewEventsSpatialReferenceChanged .FocusMapChanged.

EventArgs) Handles btnStep3.SelectionChanged.ContentsChanged. AddressOf OnActiveViewEventsViewRefreshed ' ***** SECTION (End): Set up the event handlers for all of the IActiveViewEvents End Sub Private Sub btnStep3_Click(ByVal sender As System. AddressOf OnActiveViewEventsItemDraw RemoveHandler m_activeViewEvents.AfterDraw. add it to ViewRefreshed event AddHandler m_activeViewEvents. AddressOf OnActiveViewEventsContentsChanged RemoveHandler m_activeViewEvents.Click ' ***** SECTION (Start): Remove Event Handlers RemoveHandler m_activeViewEvents.ItemAdded.ItemReordered.' Create an instance of the delegate.ContentsCleared.FocusMapChanged. AddressOf OnActiveViewEventsItemAdded RemoveHandler m_activeViewEvents. AddressOf OnActiveViewEventsItemDeleted RemoveHandler m_activeViewEvents.ViewRefreshed.ItemDeleted.Object. ByVal e As System. AddressOf OnActiveViewEventsContentsCleared RemoveHandler m_activeViewEvents. AddressOf OnActiveViewEventsFocusMapChanged RemoveHandler m_activeViewEvents.SpatialReferenceChanged.ViewRefreshed.AfterItemDraw. AddressOf OnActiveViewEventsSelectionChanged RemoveHandler m_activeViewEvents. AddressOf OnActiveViewEventsAfterDraw RemoveHandler m_activeViewEvents. AddressOf OnActiveViewEventsViewRefreshed ' ***** SECTION (End): Remove Event Handlers End Sub . AddressOf OnActiveViewEventsItemReordered RemoveHandler m_activeViewEvents. AddressOf OnActiveViewEventsSpatialReferenceChanged RemoveHandler m_activeViewEvents.

IDisplay.Show("AfterDraw") ' Tell which event fired TextBox3.esriSystem.ArcGIS.Windows.Text + vbCrLf + "ItemDraw " + count.Windows.Forms.Display.' ***** SECTION (Start): Custom Functions that you write to add additionaly functionality for the events ' Event handler Private Sub OnActiveViewEventsAfterDraw(ByVal Display As ESRI.IDisplay.Text = TextBox3.Forms.MessageBox.ArcGIS.Text + vbCrLf + "AfterDraw " + count.MessageBox.ArcGIS. ByVal phase As ESRI.ToString DoTextBoxMaintenance() End Sub ' Event handler Private Sub OnActiveViewEventsContentsChanged() . ByVal phase As ESRI.Show("ItemDraw") ' Tell which event fired TextBox3.Text = TextBox3.esriViewDrawPhase) ' Add your code here ' System.Display. ByVal Display As ESRI.ArcGIS.ToString DoTextBoxMaintenance() End Sub ' Event handler Private Sub OnActiveViewEventsItemDraw(ByVal Index As Short.esriDrawPhase) ' Add your code here ' System.Carto.

Text = TextBox3.ToString DoTextBoxMaintenance() End Sub ' Event handler Private Sub OnActiveViewEventsFocusMapChanged() ' Add your code here ' System.Windows.Show("FocusMapChanged") .Windows.' Add your code here ' System.Forms.Show("ContentsCleared") ' Tell which event fired TextBox3.Show("ContentsChanged") ' Tell which event fired TextBox3.Forms.ToString DoTextBoxMaintenance() End Sub ' Event handler Private Sub OnActiveViewEventsContentsCleared() ' Add your code here ' System.MessageBox.Text + vbCrLf + "ContentsChanged " + count.Windows.MessageBox.Forms.MessageBox.Text + vbCrLf + "ContentsCleared " + count.Text = TextBox3.

Windows.ToString DoTextBoxMaintenance() End Sub ' Event handler Private Sub OnActiveViewEventsItemAdded(ByVal Item As Object) ' Add your code here ' System.Text = TextBox3.Forms.Text = TextBox3.ToString DoTextBoxMaintenance() End Sub ' Event handler Private Sub OnActiveViewEventsItemDeleted(ByVal Item As Object) ' Add your code here ' System.Windows.Text + vbCrLf + "ItemDeleted " + count.Show("ItemDeleted") ' Tell which event fired TextBox3.Text + vbCrLf + "FocusMapChanged " + count.Text + vbCrLf + "ItemAdded " + count.Forms.' Tell which event fired TextBox3.Show("ItemAdded") ' Tell which event fired TextBox3.MessageBox.Text = TextBox3.MessageBox.ToString DoTextBoxMaintenance() .

Text + vbCrLf + "ItemReordered " + count.Show("SelectionChanged") ' Tell which event fired TextBox3.Forms.ToString DoTextBoxMaintenance() End Sub ' Event handler Private Sub OnActiveViewEventsSpatialReferenceChanged() .Text + vbCrLf + "SelectionChanged " + count.Windows.Forms.MessageBox.End Sub ' Event handler Private Sub OnActiveViewEventsItemReordered(ByVal Item As Object.Show("ItemReordered") ' Tell which event fired TextBox3.Text = TextBox3.ToString DoTextBoxMaintenance() End Sub ' Event handler Private Sub OnActiveViewEventsSelectionChanged() ' Add your code here ' System.Text = TextBox3. ByVal toIndex As Integer) ' Add your code here ' System.MessageBox.Windows.

Carto.ArcGIS. ByVal data As Object.Carto.Forms.Text = TextBox3.Geometry. ByVal envelope As ESRI.esriViewDrawPhase.IEnvelope) ' Add your code here ' System.' Add your code here ' System.Forms.MessageBox.ToString DoTextBoxMaintenance() End Sub ' ***** SECTION (End): Custom Functions that you write to add additionaly functionality for the events Private Sub DoTextBoxMaintenance() .Text = TextBox3.ArcGIS.Text + vbCrLf + "SpatialReferenceChanged " + count. ByVal phase As ESRI.ToString DoTextBoxMaintenance() End Sub ' Event handler Private Sub OnActiveViewEventsViewRefreshed(ByVal view As ESRI.MessageBox.Windows.Windows.IActiveView.Text + vbCrLf + "ViewRefreshed " + count.ArcGIS.Show("ViewRefreshed") ' Tell which event fired TextBox3.Show("SpatialReferenceChanged") ' Tell which event fired TextBox3.

create a custom extension that implements persistence.vb Class implementing an extension that listens to document events. This sample works in ArcMap. and running the sample (either an exe or dll). 2. C#VB. or ArcGlobe. no event is fired when a document is saved. To catch the time when a document is saved. setting up the debugger. However.SelectionStart = TextBox3. set up the debug application if needed.Length TextBox3. pay attention to the status bar for a message with the user name and time information of the event. start any of the applications. This sample shows how to implement an application extension that logs user and date time information when a document is opened or saved.NET LogExtension. Open and compile the sample project in Visual Studio. 3. When a document is opened or saved. or closed. Class implementing an extension that listens to document events.cs (view code) LogExtension. . ArcScene.' Scroll the textbox to the last entry TextBox3.ScrollToCaret() ' Increment the counter count = count + 1 End Sub End Class Extension to listen to document open and save events PurposeA component can listen to the events fired by the application when a document is created and opened before closing.Text. Development licensing ArcView ArcEditor ArcInfo Deployment licensing ArcView ArcEditor ArcInfo How to use See Using the samples for help on compiling. 1.

and conventions.InterfaceId.ArcGIS. LogExtension. provided you include the original copyright ' notice and use restrictions.ArcGIS. you will find the files associated with this sample in <Your ArcGIS install location>\DeveloperKit\SamplesNET in the Desktop folder in the OpenSaveLogExtension folder.ArcMapUI <ComClass(LogExtension.(view code) Depending on what products you have installed and whether you installed the samples feature. ' Imports ESRI. LogExtension. _ ProgId("OpenSaveLogExtensionVB.ClassId.EventsId). with or ' without modification.esriSystem Imports System. Extension to listen to document open and save events LogExtension.ArcGIS.Runtime.txt. ' ' You may freely redistribute and use this sample code.ADF. ' ' See use restrictions at <your ArcGIS install location>/developerkit/userestrictions.vb ' Copyright 2008 ESRI ' ' All rights reserved under the copyright laws of the United States ' and applicable international laws.ArcGIS.CATIDs Imports ESRI.LogExtension")> _ Public Class LogExtension Implements IExtension Implements IPersistVariant #Region "COM Registration Function(s)" .InteropServices Imports ESRI.Framework Imports ESRI. treaties.

Register(regKey) MxExtension. registerType. ''' </summary> Private Shared Sub ArcGISCategoryRegistration(ByVal registerType As Type) Dim regKey As String = String. ComVisibleAttribute(False)> _ Public Shared Sub UnregisterFunction(ByVal registerType As Type) ' Required for ArcGIS Component Category Registrar support ArcGISCategoryUnregistration(registerType) 'Add any COM unregistration code after the ArcGISCategoryUnregistration() call End Sub #Region "ArcGIS Component Category Registrar generated code" ''' <summary> ''' Required method for ArcGIS Component Category registration ''' Do not modify the contents of this method with the code editor.Register(regKey) End Sub .GUID) GMxExtensions.<ComRegisterFunction().Format("HKEY_CLASSES_ROOT\CLSID\{{{0}}}".Register(regKey) SxExtensions. ComVisibleAttribute(False)> _ Public Shared Sub RegisterFunction(ByVal registerType As Type) ' Required for ArcGIS Component Category Registrar support ArcGISCategoryRegistration(registerType) 'Add any COM registration code after the ArcGISCategoryRegistration() call End Sub <ComUnregisterFunction().

Public Sub New() . ''' </summary> Private Shared Sub ArcGISCategoryUnregistration(ByVal registerType As Type) Dim regKey As String = String.Format("HKEY_CLASSES_ROOT\CLSID\{{{0}}}". existing ' clients will no longer be able to access the class. otherwise. registerType. the class will not be ' registered in the COM registry and cannot be created ' via CreateObject. If you change them.GUID) GMxExtensions.Unregister(regKey) MxExtension. Public Const ClassId As String = "8545752a-d04c-4d92-84cd-65a5dd5f8de8" Public Const InterfaceId As String = "fdd2bdab-568d-490c-9ca3-916195a88ad2" Public Const EventsId As String = "07ba5ee7-1796-446f-947f-ad342153d3d4" #End Region Private m_application As IApplication ' A creatable COM class must have a Public Sub New() ' with no parameters.''' <summary> ''' Required method for ArcGIS Component Category unregistration ''' Do not modify the contents of this method with the code editor.Unregister(regKey) SxExtensions.Unregister(regKey) End Sub #End Region #End Region #Region "COM GUIDs" ' These GUIDs provide the COM identity for this class ' and its COM interfaces.

Now. "Sample Extension (VB.Document.ToLongTimeString() LogMessage(logText) End Sub Private Sub OnNewDocument() Debug. new and close events AddHandler m_docEvents.NewDocument.UserName _ + " at " + DateTime. AddressOf OnOpenDocument 'Optional. "Sample Extension (VB.Net)") Dim logText As String = "Document '" + m_application.New() End Sub #Region "Add Event Wiring for Open Documents" 'Event member variables Private m_docEvents As IDocumentEvents_Event 'Wiring Private Sub SetUpDocumentEvent(ByVal myDocument As IDocument) m_docEvents = CType(myDocument.WriteLine("New document". AddressOf OnNewDocument AddHandler m_docEvents. AddressOf OnCloseDocument End Sub Private Sub OnOpenDocument() Debug.CloseDocument.MyBase.Title + "'" _ + " opened by " + Environment.OpenDocument.WriteLine("Open document".Net)") End Sub Private Sub OnCloseDocument() . IDocumentEvents_Event) AddHandler m_docEvents.

ArcGIS.IPersistVariant.Startup m_application = TryCast(initializationData.ArcGIS.ArcGIS.UID Implements ESRI.Debug.esriSystem.IExtension.Name Get Return "OpenSaveLogExtensionVB" End Get End Property Public Sub Shutdown() Implements ESRI.ToString("B") Return extUID End Get End Property . "Sample Extension (VB.esriSystem.Net)") End Sub #End Region #Region "IExtension Implementations" Public ReadOnly Property Name() As String Implements ESRI.esriSystem.Document) End Sub #End Region #Region "IPersistVariant Implementations" Public ReadOnly Property ID() As ESRI.IExtension.IExtension. IApplication) SetUpDocumentEvent(m_application.GUID.WriteLine("Close document".ID Get Dim extUID As New UIDClass() extUID.ArcGIS.GetType().esriSystem.Value = Me.Shutdown m_docEvents = Nothing m_application = Nothing End Sub Public Sub Startup(ByRef initializationData As Object) Implements ESRI.ArcGIS.esriSystem.

IPersistVariant.Save Debug. "Sample Extension (VB.ReleaseComObject(Stream) End Sub #End Region Private Sub LogMessage(ByVal message As String) m_application.Load Marshal.UserName _ + " at " + DateTime.Message(0) = message End Sub End Class .ReleaseComObject(Stream) End Sub Public Sub Save(ByVal Stream As ESRI.Title + "'" _ + " saved by " + Environment.IVariantStream) Implements ESRI.Public Sub Load(ByVal Stream As ESRI.Now.esriSystem.ArcGIS.esriSystem.IVariantStream) Implements ESRI.esriSystem.StatusBar.Document.WriteLine("Save document".Net)") LogMessage("Document '" + m_application.esriSystem.ArcGIS.ArcGIS.ArcGIS.IPersistVariant.ToLongTimeString()) Marshal.

Sign up to vote on this title
UsefulNot useful