This topic is relevant for the following:
Product(s): ArcGIS Desktop: All
Version(s): 9.0, 9.1, 9.2
Language(s): VB6, VC++
Experience level(s): Intermediate to advanced
In this section
Many ArcGIS components, and also many of the examples presented throughout this book and your own custom components, will revisit the same concepts of object oriented programming. They will also re-use the same design patterns. In this section a number of common issues of class design and implementation are reviewed. The following sections give further help on specific issues of interface implementation.
For more help on design patterns, you should read Design Patterns: Elements of Reusable Object-Oriented Software. Although the Design Patterns book uses examples in C++ and Smalltalk, it takes a generally language-neutral approach and is relevant to all developers of object-oriented software. VB programmers may also find it useful to refer to Microsoft Visual Basic Design Patterns, which discusses implementing many of these design patterns specifically in VB. Full reference details can be found in the bibliography.
Containment is a simple form of binary reuse, where an outer object contains an instance of an inner object. Containment allows modification of the original object's method behavior, but not the method's signature. With containment, the contained object (inner) has no knowledge that it is contained within another object (outer). The outer object must implement all the interfaces supported by the inner to perform the same duties in the system. When requests are made on these interfaces, the outer object simply delegates them to the inner. To support new functionality, the outer object can either implement one of the interfaces without passing the calls on or implement an entirely new interface in addition to those interfaces from the inner object.
Containment is a useful technique for implementing a custom version of an existing class by instantiating one (or more) coclasses inside the new outer class and passing most requests straight to the contained object. However, particular functions you want to override can be dealt with in the containing class. See the ClippableIndexGrid sample for an example of containment.
COM aggregation involves an outer object that controls which interfaces it chooses to expose from an inner object. Aggregation is useful when the outer object wants to delegate every call to one of its interfaces to the same interface in the inner object. Aggregation does not allow modification of the original object's method behavior. The inner object is aware that it is being aggregated into another object and forwards any QueryInterface calls to the outer (controlling) object so that the object as a whole obeys the laws of COM. To the clients of an object using aggregation, there is no way to distinguish which interfaces the outer object implements and which interfaces the inner object implements.
One benefit of aggregation is that you can pass an instance of the new class to clients that are expecting the original, aggregated class. In this way, you can add functionality without needing to know all the inner workings of the aggregated class. Some of the examples described in 'Customizing the Geodatabase' demonstrate the use of this technique; for instance, the technique is used to create custom features. Visual Basic 6 does not support aggregation, so VB developers cannot create custom features.
Singletons are found throughout the ArcGIS object model. A singleton is a class that can only have one instance per process or thread. ArcGIS uses the Singleton-per-thread model. Singletons are useful when many clients require a reference to the same data. They can be used instead of class-level methods to provide a meeting point for client code. Implementation of a singleton, however, can be tricky to achieve.
Although there are no examples of customization that include a singleton in this book, it is possible you may include a class of this nature in a customization of your own design. With VC++ you can use an ATL macro to make your class a singleton. However, there are some issues with singletons implemented by this method; you should investigate the issues thoroughly via other sources, such as VC++ documentation, before attempting to create a Singleton, being careful to account for the singleton-per-thread model. There is no inherent support for VB developers to create a singleton object.
Some ArcObjects cannot be created using CoCreateInstance or by using the New keyword in VB, as they are non-creatable. Non-creatable classes are typically instantiated by the component itself and returned through a helper function on a creatable object. This is sometimes referred to as the factory design patternit gives the component some control over the circumstances in which the object is created and initialized. For example, ArcObjects uses this model extensively throughout the GeodatabaseCursor, SelectionSet, and FeatureClass are all examples of non-creatable classes.
Although you can define non-creatable classes as shown below, think carefully about your reasons for doing so. Your class cannot be cocreated by any client, and this may cause errors in methods that expect to be able to create your class. You may experience problems with persistence, or if you register the class to a component category.
In VB, create your class as usual, but set the class modules Instancing property to PublicNotCreatable. Add a public class to act as a factory with a public method to return an instance of the non-creatable object.
Public FunctionGetClass()AsMyClassSetGetClass =NewMyClassEnd Function
DimpFactoryAs NewMyLibrary.MyFactoryDimpClassAsMyLibrary.MyClassSetpClass = pFactory.GetClass
A VC++ developer can implement a non-creatable class using the same design principles as a VB developer. Follow the steps described below.
[
uuid(2C612928-9912-47E3-B2C0-8F0FD1C1A68D),
helpstring("My non-creatable class"),
noncreatable
]
coclass NonCreate
{
[default] interface IUnknown;
interface IMyInterface;
};CComObject<CNONCREATE>* pNonCreate = 0; IMyInterfacePtr ipMyInterface;// Class is noncreatable - so create locallyhr = CComObject<CNONCREATE>::CreateInstance(&ipMyInterface);// Note object created on heap with 0 ref countif(SUCCEEDED(hr)) { pNonCreate ->AddRef();// Call any C++ class initialization e.g. using pNonCreate ->Init();hr = pNonCreate->QueryInterface(IID_IMyInterface, (void**) &ipMyInterface);// Keep object while smart pointer in scopepNonCreate->Release(); }// Use C++ class via pNonCreate while smart pointer is liveVC++ programmers should be aware of the issues with noncreatable classes that are registered to component categoriessee the ATL Internals book referenced in the bibliography for more information.
It is also possible to remove the class entirely from the registryonly do this if you are sure that the registry entry can be removed safely. Consider issues such as helpfile linking, and any method call that needs to cocreate your class. If you do need to remove the registry entry entirely, remove the registration file (.rgs) for the class from the VC++ project, and change the registration in the class header file from DECLARE_REGISTRY_RESOURCEID(IDR_NONCREATE) to DECLARE_NO_REGISTRY().
Enumerators
Enumerators are classes that provide a collection of references to other objects; for example, the IColorRamp::Colors property returns an enumeration of Color objects. In some of the examples in this book, enumerator classes are created to return a value that needs to be an enumerator. This is done by implementing the required enumerator interface (enumerator interfaces generally begin with IEnum). See the ClippableIndexGrid, SimplePointLayer, and ConnectionLog topics for examples of custom enumerator classes.
When using enumerations in client code, you do not know how the object has been implemented. The object may create and fill a new enumeration each time one is requested, or it may, for efficiency, have been implemented to, return a reference to a previously created enumerator, in which case the position of the enumerator may not be at the first position. You should always, therefore, call the Reset method of an enumerator after you receive the reference, before using it in your code.
Coding Interfaces
From your experience of programming with ArcObjects, you should be familiar with the basic concepts of the COM interface-based programming model.
When you begin to create custom components for a COM system, you may find you need to dig a little deeper into the concepts of how interfaces are defined and used, particularly if you are developing in VB or developing in one development environment with your components being consumed in another environment.
You may find it useful to begin by reviewing the brief definitions of key concepts, such as the IUnknown interface and how to implement existing inbound interfaces, before moving on to the issues of outbound interfaces and defining new interfaces.
If you require introductory information about COM, and about how to program with an interface-based model, see the Introduction to COM in the ArcGIS Developer Help system, as this basic information is not covered in detail in this book. You may also want to refer to the books listed in the bibliography section for more detailed information.
Concepts of Interface-based programming
In COM, all communication between COM clients and servers is via interfacesabstract definitions, which contain no implementation code. Programming with interfaces hides the details of a COM server implementation from a COM client. Objects can therefore be reused at a binary level, which means you do not require access to source code, header files, or object libraries in order to extend the system even at the lowest level.
All COM interfaces inherit from the IUnknown interface, therefore all COM objects indirectly implement IUnknown. Interfaces that inherit directly from IUnknown are sometimes known as custom interfaces. The AddRef and Release methods are used together to control object lifetime. If you are programming in VB, AddRef and Release are called automatically by the VB garbage collector as required. VC++ programmers can avoid much use of AddRef and Release by using smart pointers (see the Smart Types section of the Visual C++ section of the ArcGIS Developer Help system.
The QueryInterface method provides the functionality to access any interface, and therefore any interface member, available on a class from any existing interface reference. This process is sometimes known as a QI. VB programmers do not need to directly access IUnknown to perform a QI.
Inbound Interfaces
It is likely that the majority of the interfaces you implement on your class are existing ArcObjects inbound interfaces, particularly if you are creating a subtype of an existing ArcObjects class. The ArcGIS client knows about these interfaces and can use them to make calls to your class. Below is a brief review of how to implement inbound interfaces.
Implementing inbound interfaces in VB
In VB, indicate that a class implements an inbound interface by using the Implements keyword.
[Visual Basic 6.0]ImplementsICommandNote that the method for implementing, or sinking, an outbound interface is considerably different from implementing an inbound interface in VB and is discussed later in this section. Remember:
- You can only implement interfaces whose definition is supported by VB. This includes parameter attributes, data types, and other issues. See the later section, Creating type libraries with IDL, for more information.
- All members of every inbound interface must be stubbed out.
- Members with no actual implementation should return the appropriate error code, in this case E_NOTIMPL. For more information on error codes, see the Error Handling topic.
Implementing inbound interfaces in VC++
In VC++, you declare you are implementing an interface by including it in the list of base classes from which your class will inherit.
[Visual C++]classATL_NO_VTABLE CMyClass :publicCComObjectRootEx<CCOMSINGLETHREADMODEL>,publicCComCoClass<CMYCLASS, &CLSID_MyClass>,publicICommand { ... }This works because an interface in C++ is defined as a structure, so you can derive a class from an interface in the same way as deriving a class from a structure.
You should also add the interface to the ATL COM Map section of your class declaration. The COM Map macros expand to provide an implementation of QueryInterface() for you:
BEGIN_COM_MAP(CMyClass) COM_INTERFACE_ENTRY(ICommand) END_COM_MAP()Your class declaration must also contain a prototype for each member of the interface:
[Visual C++]STDMETHOD(get_Enabled)(VARIANT_BOOL* Enabled); STDMETHOD(get_Checked)(VARIANT_BOOL* Checked); STDMETHOD(get_Name)(BSTR* Name); ....Implement each member of the interface in the implementation file of your class:
[Visual C++]STDMETHODIMP CMyTool::get_Name(BSTR* Name) {if(0 == Name)returnE_POINTER;// Set the internal name of this command. By convention, this // name string contains the category and caption of the command.*Name = ::SysAllocString(L"DeveloperSamples_MyTool");returnS_OK; }Outbound interfaces
So far we have dealt with inbound interfaces, in which the client calls the server component. For outbound interfaces however, the server object calls the client. Outbound interfaces are analogous to callbacksa mechanism that should be familiar to VC++ developers. The methods on an outbound interface will be familiar to VB developers as events.
An object that calls the members of an outbound interface is said to be a source; an object that receives the calls from the source is said to be a sink.
![]()
Outbound interfaces are defined in the same way as inbound interfaces, but its members are coded to present information to a client, which it may need to know as certain events occur. For example, IActiveViewEvents::AfterDraw has parameters specifying the display and the current phase that is being drawn. Outbound interfaces are also implemented in a different way.
This model is inherently more complex than the inbound interface model, and the difference between using an outbound interface in VB versus VC++ is significant. As an experienced ArcObjects programmer, you should already be familiar with sinking outbound interfaces, but brief details of how to sink an outbound interface are described below for both VB and VC++ before descriptions of sourcing outbound interfaces.
- Sinking outbound interfaces (responding to events) in VB
In VB, outbound interfaces are sinked by using the WithEvents keyword. This mechanism should be familiar to any ArcObjects programmer. Sinking an outbound interface may be required in any ArcGIS customization, and is not specific to creating custom ArcObjects components. If you are unsure of how to sink an outbound interface, refer to the Visual Basic documentation in the ArcGIS Developer Help system.
- Sourcing outbound interfaces (raising events) in VB
Unfortunately, due to VBs event handling mechanism, you cannot be the source of an existing outbound interface. There is no facility in VB for sourcing existing events interfaces, as the VB compiler creates an outbound interface 'behind the scenes' and adds all events defined in the class to that interface as methods.
This means you cannot create a class that raises events from any existing ArcObjects outbound interfaces such as ILayerEvents, IMapFrameEvents, and so forth.
You can define new events that your class may raise using the Event keyword. In your class methods, you then raise the event as required using RaiseEvent. VB creates a hidden outbound interface for you at compile time. All the events you declare are placed in this hidden interface; the name of the interface is the class module name preceded by two underscore characters. If you investigate your DLL with OLE View, you will how the outbound interface is defined using the [source] attribute.
VB clients can sink your event by using the WithEvents keyword. The sink method the client has defined will be called when your class raises the event.
- Sinking outbound interfaces in VC++
In VC++, outbound interfaces are typically sinked by using the connection point mechanism to register its interest in the events of a source object. Again, sinking an outbound interface may be required in any ArcGIS customization, and is not specific to creating custom ArcObjects components. If you are unsure of how to sink an outbound interface, refer to the Visual C++ documentation in the ArcGIS Developer Help system, in particular the 'Handling COM Events in ATL' topic.
- Sourcing outbound interfaces in VC++
In VC++, for an object to be a source of events (that is, to implement an outbound interface) it will need to provide an implementation of IConnectionPointContainer and a mechanism to track which sinks are listening to which IConnectionPoint interfaces. ATL provides this through the IConnectionPointContainerImpl template. Additionally, ATL provides a wizard to generate code to fire IDispatch events for all members of a given dispatch events interface.
Details of this process can be found in the topic 'Handling COM Events in ATL' in the Visual C++ documentation section of the ArcGIS Developer Help system.
Defining New Interfaces
In many of the examples in this book, coclasses require additional public properties and methods, in addition to those available on the implemented ArcObjects interfaces. For example, the ConnectLog example requires a method to allow a client to enumerate current connections. In such cases, a new interface has been defined and implemented by the new class. This gives the custom component the familiar benefits of the interface-based programming modelflexibility to adapt components being a major advantage.
Defining interfaces in VC++ using IDL
If you are developing in VC++, you should be familiar with the process of creating new interfaces explicitly using IDL, as this is the only way to add COM functions to your classes.
VC++ developers may, in any case, find it useful to review the information in the 'Creating Type Libraries with IDL' topic. This section gives advice on IDL standards, helping you define interfaces suitable for use by a variety of clients, particularly those written in VB. Note also that the syntax you use to define your interface is dependent upon the choice of a dispatch or custom interface.
Defining interfaces in VB using a class module
The VB compiler automatically creates a new interface for each class in an ActiveX projectthis interface will contain all the public members you defined on your class. The interface is hidden by the VB environment when you use the class in another component, and the public members appear as if they are directly implemented on the class.
![]()
You can also use VB to define a new interface explicitly by using a new class module.
- Add a new class module to your component and set the Instancing property to PublicNotCreatable, as you do not want clients to be able to directly instantiate your interface.
- Following the convention for interface names, set the name to begin with I, for example, IMyInterface.
- Add public methods and properties as required to the module.
- In another class module, implement the interface as you would any other interface by using the Implements keyword.
- Ensure all members of the new interface are stubbed out in the implementing class.
The class module you defined does not actually define a proper COM interface, instead you take advantage of the fact that the VB compiler automatically creates an interface for each class.
![]()
As you can see from the diagram above, the actual interface names differ from the names you use in VB. If you intend the interface to be used from any other environment apart from VB, you should review the details in the 'Creating interfaces with IDL' section. This describes how you can use VB and IDL to define COM interfaces in a separate type library. Note that you cannot use the information contained in an IDL file to define an object (for example, its name and the interfaces it implements) for a VB class. However, a VB component can make use of enumerations defined in IDL, as long as they use a VB-compatible data type.
Defining new outbound interfaces
You can define new outbound interfaces in IDL exactly the same way as you define inbound interfaces, as the difference lies in the way the interface is used. However, you will need to think about the kind of information the sink objects will need to knowwhat changes may occur to your class, what sink objects will need to know about those changes, and which other objects your changes will affect. For example, IActiveViewEvents::AfterDraw is called many times in succession as a view is refreshed, each time a different phase of the refresh is indicated by the phase parameter, making this a flexible event to implement and use.
As noted previously, it is not possible to create a new external outbound interface and implement this in VB, as VB's event model hides outbound event implementation details.
Default interfaces
All COM classes have a default interface specified at the type library level. The default interface is returned when a COM object is instantiated with no interface being specified. The default interface on a class was originally intended to be the interface that most closely represents the underlying class, providing its default functionality. This use of default interfaces may have changed somewhat, in particular for ArcObjects classes that split essential functionality between more than one interface.
VC++ mappings are not affected by the default interface, but VB developers are affected when viewing classes with the VB object browser or dealing with outbound interfaces.
Access to default interfaces in VB
The VB environment hides the name of the default interface of a class, although its members are still accessible. VB developers do not generally need to access the IUnknown interface; therefore, most ArcObjects classes define IUnknown as the default interface. If you are creating an interface for use in VB, you may want to follow this convention.
Default interfaces of components created in VB
When you create a COM class in VB, the VB compiler automatically generates a default interface for your class. This interface contains all the public members you defined on your class and is named after the class with a prefix of an underscore, for example _MyClass. You may want to provide access to your component from other environments, or to gain more control over its definition for use within VB. If so, you might consider defining your interface in IDL, instead of directly in VB. This gives you much more control over interface names and attributes and also over the types and attributes of method parameters.
Default outbound interfaces of components created in VB
If you defined any events on your class, these are added to another automatically generated interface, this time named after the class and prefixed with two underscores, for example __MyClass. As noted previously, you cannot alter the outbound interface definitions due to the way VBs event model is implemented.
Classes with IDispatch as the default interface
A few ArcObjects classes specify the IDispatch interface as default; for example, the default interface of the Application object for ArcMap is IApplication. The reasons for this and why you may want to have your classes implement IDispatch are discussed in the section IUnknown, IDispatch, and Dual Interfaces below.
Optional interfaces
Throughout the ArcObjects object model diagrams, you will find interfaces marked as optional. Interfaces are marked as optional on abstract classes for which some subclasses implement an interface and some do not. This is a diagramming convention and does not affect the implementation of an interface.
Instance interfaces
The term instance interface describes an interface that is available on some instances of a particular class and not on other instances of the same class. This concept does not break the rules of COM, as any particular instance of a class must either always allow a QI or never allow a QI to the instance interface. This technique can simplify an object model somewhat, making the components simpler to use as fewer subtypes are required. Instance interfaces are found in particular throughout the Geocoding and Raster object models.
If an interface is marked as instance, you must always be careful to check the result of a QI before attempting to use the interface.
Early binding, late binding, and IDispatch
If you intend to author a component that can be accessed from scripting languages, such as VB Script, JavaScript, or another similar environment, you will find this section useful.
So far, this section has mostly concentrated on standard COM classes, with interfaces that inherit from IUnknown. These classes can be used from compiled languages, such as VB and VC++, which bind function calls at compile timeknown as early binding. The function calls available on an interface are laid out in memory in a virtual tableit is these functions that the compiler bind method calls. For this reason, early binding is sometimes also known as v-table binding.
However, not all environments are compiled this way. Scripting languages, such as VBScript and JavaScript, are interpreted at run time, and therefore, require to bind method calls at run timeknown as late binding. The IDispatch interface is designed to allow late-bound function calls, as the GetIDsOfNames and Invoke members allow function call identification at run time. For this reason, components for use in scripting environments implement the IDispatch interface.
A third type of binding is able to identify the IDs of methods at compile time using the IDispatch interface. Function calls are bound to these IDs at compile time, so only Invoke must be called at run time. This type of binding is known as dispID binding, and is also considered a type of early binding.
The advantages and disadvantages of the different types of binding are summarized below, indicating reasons why you may want to avoid or choose particular implementations when defining new interfaces for your component.
Most ArcObjects interfaces are custom, inheriting from IUnknown, and cannot be accessed from scripting clients. You should be familiar with the concepts of early binding, v-table binding, and late binding from previous COM experience. This topic is too complex to cover in detail heresee Introduction to COM for introductory information; if you need further information, you should review the books about COM listed in the bibliography.
There is a way to delegate the early binding versus late binding decision to your client, and that is to use dual interfaces on your class. As dual interfaces provide access to both the methods of IUnknown and IDispatch, it is possible for VC++ clients to access the class using early binding and for script languages to access the class via late binding.
The VB compiler automatically creates dual interfaces on VB classes, and VB classes are, in any case, restricted to these variant data types.
One drawback to the dual interface model applies only to VC++ developers. The data types that can be used in a dual or IDispatch interface are limited to the basic data types that can be wrapped as variantsthe full list of the data types can be found in the OAIDL.idl header file in Visual Studio. Mainly, this excludes complex C++ structures. Dual interface classes may be slightly larger in size, but the size increase is generally so small that it makes little difference.
One advantage for VC++ programmers using ATL is that the majority of the work to implement a dual interface is done by the ATL wizards, meaning that little extra effort is required when compared to a custom interface.
Some members will have certain programming issues associated with them, which developers should be aware of.
In client-side storage methods, the client to the component allocates the memory required for the result of the method before the method is invoked. The reason for client-side storage is performance. Where it is anticipated that a particular method may be called in a tight loop, the objects for the method call's parameters need only be created once outside the method, then populated inside each method call, which is faster than creating a new object inside the method each time.
DimpEnvelopeAsIEnvelopeSetpEnvelope = New EnvelopeFori = 0To10000 pPolygon.QueryEnvelope pEnvelope' Do something with the envelopeNexti
Client-side storage methods are named beginning with Query, for example ISymbol::QueryBoundary and IGeometry::QueryEnvelope, whereas methods beginning with Get generally instantiate the object for you.
Implementing a Query method is straightforward in VC++, as the environment is more suited to passing pointers between methods in this way. In general, the same issues apply as just discussed for VB. Since pointers are passed by value in VC++, changing the pointer to point to new memory will have no effect. Instead, use methods that work on the existing object.
STDMETHODIMP CMyElement::QueryBounds(IDisplay *Display, IEnvelope *Bounds)
{
// Return error if object does not already exist
if (!Bounds) return E_POINTER;
// use cached coordinates
Bounds->PutWKSCoords(&m_envelope);
return S_OK;
}As you are relying on the caller to pass a valid object, you should first check to see if the incoming reference is valid, raising an error if you have received a null pointer.
STDMETHODIMP CMyElement::QueryBounds(IDisplay* pDisplay, IEnvelope* pBounds)
{
if (!pDisplay || !pBounds)
return E_POINTER;
...If you're developing in VB, you must be more careful with your object references when implementing a Query method. For example, imagine you needed to create a custom graphic element. You will need to implement the IElement interface. This contains the QueryBounds client-side storage method, defined in IDL as shown.
[in] IEnvelope* bounds
In VB, this appears as the following function.
Private SubIElement_QueryBounds(ByValDisplayAsesriDisplay.IDisplay, _ByValBoundsAsesriGeometry.IEnvelope)
Notice that the Bounds parameter is passed by value. You might expect any changes made to this parameter to only be valid within the context of the procedure, therefore, the variable's actual value remains unchanged once the procedure exits. So how can you change the value of the object so that the caller can see the changes?
Consider what it means to pass something by value to a procedure. The value of the parameter passed ByVal is copied, and the procedure receives the address of this copy to work with. After the procedure exits, the temporary copy is discarded by the Visual Basic Virtual Machine (VBVM), as you would expect.
However, if the parameter is an interface pointer to an object, this may have an unexpected effect. The value of the interface pointer is copied and passed to the procedure ByVal, not the actual value of the underlying object. The new temporary interface pointer in the called function references the same block of memory as the original interface pointer, thus both the caller and procedure have references to the same underlying object in memory. Within the procedure, using this temporary interface pointer to call methods and write data will change the data of the underlying object, as long as you do not change the value of the pointer. When the procedure exits, the temporary pointer is discarded. When control returns to the calling procedure, the changes to the underlying object can be seen.
However, if you change the value of the interface pointer (the object variable Bounds), while within the procedure, to reference another object, then call methods and properties, you will be changing the data of the newly referenced object.
Private SubIElement_QueryBounds(ByValDisplayAsesriDisplay.IDisplay, _ByValBoundsAsesriGeometry.IEnvelope)SetBounds = pOtherGeometry.Envelope' Only changes temporary pointer.
The data of the original underlying object is in this case left unchanged. Again, once the procedure exits, the temporary pointer is discarded, and the calling procedure will still reference the original underlying object.
Begin coding your Query method in VB by defining a constant to represent the standard COM error indicating an invalid pointer.
Const E_POINTER = &H80004003Now in your client-side storage method, check the incoming parametersraise the appropriate error if necessary.
Private SubIElement_QueryBounds(ByValDisplayAsesriDisplay.IDisplay, _ByValBoundsAsesriGeometry.IEnvelope)IfBoundsIs Nothing ThenErr.Raise E_POINTER, Me.Name, "Invalid Pointer"Exit SubEnd If...
The incoming Envelope may contain information already. You can clear this using the SetEmpty method.
Bounds.SetEmpty
Now you are ready to set the properties of the Envelope object. Avoid referencing the object again, using instead methods that work on the existing object, for example, other Query methods.
DimpOutlineGeomAsIGeometrySetpOutlineGeom =NewPolygon' Use pOutlineGeom to setIElement_QueryOutline m_pCachedDisplay, pOutlineGeom' properties of BoundspOutlineGeom.QueryEnvelope Bounds
Note, however, that the code above needs to instantiate a new polygon objectwhich is precisely the situation the Query methods are designed to avoid. You should ideally cache such an object at a class level, reusing it to improve performance.
Custom classes, such as commands, tools, and extensions, gain a reference to the application they are instantiated in by receiving a reference from an interface member. For example, the ICommand::OnCreate receives a reference to the current Application as a parameter.
In many cases, such a convenient reference may not be available; for example, a custom Element has no such reference passed to it. If you are creating a custom class that requires access to the rest of the Application to operate correctly but does not receive such a reference, you can consider the following two options.
You could add a public method to your custom class, which takes a parameter referencing the Application (or other appropriate class). This method would need to be called whenever the class is created in code. If your custom class will only be created in code, and you can specify this requirement, this solution may be suitable.
In some circumstances, your component may be instantiated by code beyond your control; for example, a custom DDE Handler is created by the ArcMap application itself by registering to a component category. In this case, it is not possible to specify that the client must set a reference to the application after instantiating an object. In this case, it may be possible to use the AppRef object, which can be instantiated within an ArcGIS application to get a reference to the application object in that process.
However, in some circumstances, your component may be instantiated outside an ArcGIS application process, for example, in an application which uses the MapControl or PageLayoutControl. In this case, attempting to instantiate AppRef may cause an error, as no ArcGIS application object is running.
AppRef is used to get a reference to the current document. As sometimes a component may be instantiated outside an ArcGIS application process, the component needs to account for this without causing errors.
You should always, therefore, be careful when attempting to instantiate AppRef. The key issue is that your component should always degrade behavior gracefully when using AppRef unsuccessfully. Always ensure that you have an active error handler around code, which attempts to instantiate AppRef. Also, always make sure your code does not assume the presence of the application or associated objects, but checks the references before use each time.