ArcGIS SDK  

Development Environments

The Visual Basic 6 Environment (VB 6 and VBA)

Visual Basic 6 (VB6) Specifics
Visual Basic for Applications (VBA) Specifics

User-interface standards
Variable declaration
Suggested naming standards
Parentheses
Order of conditional determination
Indentation
Default properties
Intermodule referencing
Multiple property operations
Arrays
Bitwise operators
Type suffixes
Ambiguous type matching
Simple image display
Error handling
Event functions
Memory management
While Wend constructs
The Visual Basic Virtual Machine
Interacting with the IUnknown Interface
Accessing ArcObjects
Working With HRESULTs
Working with properties
Working with methods
Working with events
Pointers to valid objects as parameters
Passing data between modules
Using the TypeOf keyword
Using the Is operator
Iterating through a collection

User interface standards

Consider preloading forms to increase the responsiveness of your application. Be careful not to preload too many (preloading three or four forms is fine).

Use resource files (.res) instead of external files when working with bitmap files, icons, and related files.

Make use of constructors and destructors to set variable references that are only set when the class is loaded. These are the VB functions: Class_Initialize and Class_Terminate or Form_Load and Form_Unload. Set all variables to Nothing when the object is destroyed.

Make sure the tab order is set correctly for the form. Do not add scroll bars to the tabbing sequence; it is too confusing.

Add access keys to those labels that identify controls of special importance on the form (use the TabIndex property).

Use system colors where possible instead of hard-coded colors.

Variable declaration

This line causes count to be declared as a Variant, which is likely to be unintended.

  Dim count, max As Long

This line declares both count and max as Long, the intended type.

  Dim count As Long, max As Long

These lines also declare count and max as Long and are more readable.

  Dim count As Long
  Dim max As Long

Suggested naming standards

The tables below summarize suggested naming standards for the various elements of your Visual Basic projects.

Module Type Prefix
Form frm
Class cls
Standard bas
Project prj

Name your modules according to the overall function they provide; do not leave any with default names (such as, "Form1", "Class1", or "Module1"). In addition, prefix the names of forms, classes, and standard modules with three letters that denote the type of module, as shown in the table above.


Control Type Prefix
Check box chk
Combo box cbo
Command button cmd
Common dialog cdl
Form frm
Frame fra
Graph gph
Grid grd
Image img
Image list iml
Label lbl
List box lst
List view lvw
Map control map
Masked edit msk
Menu mnu
OLE client ole
Option button opt
Picture box pic
Progress bar pbr
Rich text box rtf
Scroll bar srl
Slider sld
Status bar sbr
Tab strip tab
Text box txt
Timer tmr
Tool bar tbr
Tree view tvw

As with modules, name your controls according to the function they provide; do not leave them with default names since this leads to decreased maintainability. Use the three-letter prefixes above to identify the type of control.


Use the following notation for naming variables and constants:

[<libraryName.>][<scope_>]<type><name>

<name> describes how the variable is used or what it contains. The <scope> and <type> portions should always be lowercase, and the <name> should use mixed case.

Library Name Library
esriGeometry ESRI Object Library
stdole Standard OLE COM Library
<empty> Simple variable data type

<libraryName>


Prefix Variable scope
c constant within a form or class
g public variable defined in a class form or standard module
m private variable defined in a class or form
<empty> local variable

<scope>


Prefix Data Type
b Boolean
by byte or unsigned char
d double
fn Function
h handle
i int (integer)
l Long
p a pointer
s String

<type>


Parentheses

Use parentheses to make operator precedence and logic comparison statements easier to read.

  Result = ((x * 24) / (y / 12)) + 42
  If ((Not pFoo Is Nothing) And (Counter > 200)) Then

Order of conditional determination

Visual Basic, unlike languages such as C and C++, performs conditional tests on all parts of the condition, even if the first part of the condition is False. This means you must not perform conditional tests on objects and interfaces that had their validity tested in an earlier part of the conditional statement.

  ' The following line will raise a runtime error if pFoo is NULL
  If ((Not pFoo Is Nothing) And (TypeOf pFoo.Thing Is IBar)) then
  End If

  ' The correct way to test this code is
  If (Not pFoo Is Nothing) Then
    If (TypeOf pFoo.Thing Is IBar) Then
      ' Perform action on IBar thing of Foo
    End If
  End If

Indentation

Use two spaces or a tab width of two for indentation. Since there is always only one editor for VB code, formatting is not as critical an issue as it is for C++ code.

Default properties

Avoid using default properties except for the most common cases. They lead to decreased legibility.

Intermodule referencing

When accessing intermodule data or functions, always qualify the reference with the module name. This makes the code more readable and results in more efficient runtime binding.

Multiple property operations

When performing multiple operations against different properties of the same object, use a With … End With statement. It is more efficient than specifying the object each time.

  With frmHello
    .Caption = "Hello world"
    .Font = "Playbill"
    .Left = (Screen.Width - .Width) / 2
    .Top  = (Screen.Height - .Height) / 2
  End With

Arrays

For arrays, never change Option Base to anything other than zero, which is the default. Use LBound and UBound to iterate over all items in an array.

  myArray = GetSomeArray
  For i = LBound(myArray) To UBound(myArray)
    MsgBox cstr(myArray(i))
  Next I

Bitwise operators

Since And, Or, and Not, are bitwise operators, ensure that all conditions using them test only for Boolean values, unless, of course, bitwise semantics are what is intended.

  If (Not pFoo Is Nothing) Then
    ' Valid Foo do something with it
  End If

Type suffixes

Refrain from using type suffixes on variables or function names, such as myString$ or Right$(myString), unless they are needed to distinguish 16-bit from 32-bit numbers.

Ambiguous type matching

For ambiguous type matching, use explicit conversion operators, such as CSng, CDbl, and CStr, instead of relying on VB to pick which one will be used.

Simple image display

Use an ImageControl rather than a PictureBox for simple image display. It is much more efficient.

Error handling

Always use On Error to ensure fault-tolerant code. For each function that does error checking, use On Error to jump to a single error handler for the routine that deals with all exceptional conditions that are likely to be encountered. After the error handler processes the error—usually by displaying a message—it should proceed by issuing one of the recovery statements shown on the table below.

Recovery Statement Frequency Meaning
Exit Sub usually Function failed, pass control back to caller
Raise often Raise a new error code in the caller's scope
Resume rarely Error condition removed, reattempt offending statement
Resume Next very rarely Ignore error and continue with the next statement

Recovery statements issued by error handlers

Error handling in Visual Basic is not the same as general error handling in COM (see the section Working With HRESULTs).

Event functions

Refrain from placing more than a few lines of code in event functions to prevent highly fractured and unorganized code. Event functions should simply dispatch to reusable functions elsewhere.

Memory management

To ensure efficient use of memory resources, the following points should be considered:

While Wend constructs

Avoid While … Wend constructs. Use the Do While … Loop or Do Until ... Loop instead because you can conditionally branch out of this construct.

  pFoos.Reset
  Set pFoo = pFoos.Next
  Do While (Not pFoo Is Nothing)
    If (pFoo.Answer = "Done") Then Exit Loop
    Set pFoo = pFoos.Next
  Loop

The Visual Basic Virtual Machine

The Visual Basic Virtual Machine (VBVM) contains the intrinsic Visual Basic controls and services, such as starting and ending a Visual Basic application, required to successfully execute all Visual Basic developed code.

The VBVM was called the VB Runtime in earlier versions of the software.

The VBVM is packaged as a DLL that must be installed on any machine wanting to execute code written with Visual Basic, even if the code has been compiled to native code. If the dependencies of any Visual Basic compiled file are viewed, the file msvbvm60.dll is listed; this is the DLL housing the Virtual Machine.

For more information on the services provided by the VBVM, see the sections Interacting with the IUnknown Interface and Working With HRESULTs later in this topic.

Interacting with the IUnknown Interface

The topic on COM contains a lengthy overview of the IUnknown interface and how it forms the basis on which all of COM is built. Visual Basic hides this interface from developers and performs the required interactions (QueryInterface, AddRef, and Release function calls) on the developer's behalf. It achieves this because of functionality contained within the VBVM. This simplifies development with COM for many developers, but to work successfully with ArcObjects, you must understand what the VBVM is doing.

Visual Basic developers are accustomed to dimensioning variables as follows:

  Dim pColn as New Collection  'Create a new collection object
  PColn.Add "Foo", "Bar"       'Add element to collection

It is worth considering what is happening at this point. From a quick inspection of the code, it appears that the first line creates a collection object and gives the developer a handle on that object in the form of pColn. The developer then calls a method on the object Add. In the Introduction to COM topic you learned that objects talk via their interfaces, never through a direct handle on the object itself. Remember, objects expose their services via their interfaces. If this is true, something isn't adding up.

What is actually happening is some "VB magic" performed by the VBVM and some trickery by the Visual Basic Editor (VBE) in the way that it presents objects and interfaces. The first line of code instantiates an instance of the collection class, then assigns the default interface for that object, _Collection, to the variable pColn. It is this interface, _Collection, that has the methods defined on it. Visual Basic has hidden the interface-based programming to simplify the developer experience. This is not an issue if all the functionality implemented by the object can be accessed via one interface, but it is an issue when there are multiple interfaces on an object that provides services.

VBE backs this up by hiding default interfaces from the IntelliSense completion list and the object browser. By default, any interfaces that begin with an underscore, "_", are not displayed in the object browser (to display these interfaces, turn Show Hidden Member on, although this will still not display default interfaces).

You have already learned that the majority of ArcObjects have IUnknown as their default interface and that Visual Basic does not expose any of IUnknown's methods, namely QueryInterface, AddRef, and Release. Assume you have a class Foo that supports three interfaces, IUnknown (the default interface), IFoo, and IBar. This means that if you were to dimension the variable pFoo as below, the variable pFoo would point to the IUnknown interfaces.

  Dim pFoo As New Foo     ' Create a new Foo object
  pFoo.??????

Since Visual Basic does not allow direct access to the methods of IUnknown, you would immediately have to QI for an interface with methods on it that you can call. Because of this, the correct way to dimension a variable that will hold pointers to interfaces is as follows:

  Dim pFoo As IFoo   ' Variable will hold pointer to IFoo interface
  Set pFoo = New Foo ' Create Instance of Foo object and QI for IFoo

Now that you have a pointer to one of the object's interfaces, it is an easy matter to request from the object any of its other interfaces.

  Dim pBar as IBar  'Dim variable to hold pointer to interface
  Set pBar = pFoo   'QI for IBar interface

By convention, most classes have an interface with the same name as the class with an "I" prefix; this tends to be the interface most commonly used when working with the object. You are not restricted to which interface you request when instantiating an object. Any supported interface can be requested; hence, the code below is valid.

  Dim pBar as IBar
  Set pBar = New Foo  'CoCreate Object

  Set pFoo = pBar     'QI for interface

Objects control their own lifetime, which requires clients to call AddRef anytime an interface pointer is duplicated by assigning it to another variable and to call Release anytime the interface pointer is no longer required. Ensuring that there are a matching number of AddRefs and Releases is important, and fortunately, Visual Basic performs these calls automatically. This ensures that objects do not "leak". Even when interface pointers are reused, Visual Basic will correctly call release on the old interface before assigning the new interface to the variable. The following code illustrates these concepts; note the reference count on the object at the various stages of code execution.

Private Sub VBMagic()
  ' Dim a variable to the IUnknown interface on the simple object
  Dim pUnk As IUnknown

  ' Co Create simpleobject asking for the IUnknown interface
  Set pUnk = New SimpleObject 'refCount = 1

  ' QI for a useful interface
  ' Define the interface
  Dim pMagic As ISimpleObject

  ' Perform the QI operation
  Set pMagic = punk 'refCount = 2

  ' Dim another variable to hold another interface on the object
  Dim pMagic2 As IAnotherInterface

  ' QI for that interface
  Set pMagic2 = pMagic 'refCount = 3

  ' Release the interface pointer
  Set pMagic2 = Nothing 'refCount = 2

  ' Release the interface
  Set pMagic = Nothing 'refCount = 1

  ' Now reuse the pUnk variable - what will VB do for this?
  Set pUnk = New SimpleObject 'refCount = 1, then 0, then 1

  ' Let the interface variable go out of scope and let VB tidy up
End Sub 'refCount = 0

See Visual Basic Magic sample in the server guide samples on the disk for this code. You are encouraged to run the sample and use the code. This object also uses an ATL C++ project to define the SimpleObject and its interfaces; you are encouraged to look at this code to learn a simple implementation of a C++ ATL object.

Often interfaces have properties that are actually pointers to other interfaces. Visual Basic allows you to access these properties in a shorthand fashion by chaining interfaces together. For instance, assume that you have a pointer to the IFoo interface, and that interface has a property called Gak that is an IGak interface with the method DoSomething(). You have a choice on how to access the DoSomething method. The first method is the long-handed way.

  Dim pGak as IGak
  Set pGak = pFoo      'Assign IGak interface to local variable
  pGak.DoSomething     'Call method on IGak interface

Alternatively, you can chain the interfaces and accomplish the same thing on one line of code.

  pFoo.Gak.DoSomething  'Call method on IGak interface

When looking at the sample code, you will see both methods. Normally, the former method is used on the simpler samples, as it explicitly tells you what interfaces are being worked with. More complex samples use the shorthand method.

This technique of chaining interfaces together can always be used to get the value of a property, but it cannot always be used to set the value of a property. Interface chaining can only be used to set a property if all the interfaces in the chain are set by reference. For instance, the code below would execute successfully.

  MapControl1.Map.Layers(0).Name = "Foo"

The above example works because both the Layer of the Map and the Map of the map control are returned by reference. The lines of code below would not work since the Extent envelope is set by value on the active view.

  MapControl1.ActiveView.Extent.Width = 32

The reason that this does not work is that the VBVM expands the interface chain to get the end property. Because an interface in the chain is dealt with by value, the VBVM has its own copy of the variable, not the one chained. To set the Width property of the extent envelope in the above example, the VBVM must write code similar to this:

  Dim pActiveView as IActiveView
  Set pActiveView = MapControl1.ActiveView

  Dim pEnv as IEnvelope
  Set pEnv = pActiveView.Extent  ' This is a get by value,

  PEnv.Width = 32   ' The VBVM has set its copy of the Extent and not
                     ' the copy inside the ActiveView

For this to work the VBVM requires the extra line below.

  pActiveView.Extent = pEnv  ' This is a set by value

Accessing ArcObjects

You will now see some specific uses of the create instance and query interface operations that involve ArcObjects. To use an ArcGIS object in Visual Basic or VBA, you must first reference the ESRI library that contains that object. If you are using VBA inside ArcMap or ArcCatalog, most of the common ESRI object libraries are already referenced for you. In standalone Visual Basic applications or components, you will have to manually reference the required libraries.

To find out what library an ArcObjects component is in, review the object model diagrams in the developer help or use the LibraryLocator tool in your developer kit tools directory.

You will start by identifying a simple object and an interface that it supports. In this case, you will use a Point object and the IPoint interface. One way to set the coordinates of the point is to invoke the PutCoords method on the IPoint interface and pass in the coordinate values.

  Dim pPt As IPoint
  Set pPt = New Point
  pPt.PutCoords 100, 100

The first line of this simple code fragment illustrates the use of a variable to hold a reference to the interface that the object supports. The line reads the IID for the IPoint interface from the ESRI object library.

IID is short for interface identifier, a GUID.

You may find it less ambiguous (as per the coding guidelines), particularly if you reference other object libraries in the same project, to precede the interface name with the library name, for example:

  Dim pPt As esriGeometry.IPoint

That way, if there happens to be another IPoint referenced in your project, there won't be any ambiguity as to which one you are referring to.

The second line of the fragment creates an instance of the object or coclass, then performs a QI operation for the IPoint interface that it assigns to pPt.

A QI is required since the default interface of the object is IUnknown. Since the pPt variable was declared as type IPoint, the default IUnknown interface was QI'd for the IPoint interface.

Coclass is an abbreviation of component object class.

With a name for the coclass as common as Point, you may want to precede the coclass name with the library name, for example:

  Set pPt = New esrigeometry.Point

The last line of the code fragment invokes the PutCoords method. If a method can't be located on the interface, an error will be shown at compile time.

This is the compilation error message shown when a method or property is not found on an interface.

Working With HRESULTs

So far, you have seen that all COM methods signify success or failure via an HRESULT that is returned from the method; no exceptions are raised outside the interface. You have also learned that Visual Basic raises exceptions when errors are encountered. In Visual Basic, HRESULTs are never returned from method calls, and to confuse you further when errors do occur, Visual Basic throws an exception. How can this be? The answer lies with the Visual Basic Virtual Machine. It is the VBVM that receives the HRESULT; if this is anything other than S_OK, the VBVM throws the exception. If it was able to retrieve any worthwhile error information from the COM error object, it populates the Visual Basic Err object with that information. In this way, the VBVM handles all HRESULTs returned from the client.

When implementing interfaces in Visual Basic, it is good coding practice to raise an HRESULT error to inform the caller that an error has occurred. Normally, this is done when a method has not been implemented.

  ' Defined in Module
  Const E_NOTIMPL = &H80004001  'Constant that represents HRESULT

  'Added to any method not implemented
  On Error GoTo 0
  Err.Raise E_NOTIMPL

You must also write code to handle the possibility that an HRESULT other than S_OK is returned. When this happens, an error handler should be called and the error dealt with. This may mean simply notifying the user, or it may mean automatically dealing with the error and continuing with the function. The choice depends on the circumstances. Below is a simple error handler that will catch any error that occurs within the function and report it to the user. Note the use of the Err object to provide the user with some description of the error.

Private Sub Test()
  On Error GoTo ErrorHandler
  ' Do something here
  Exit Sub    ' Must exit sub here before error handler
ErrorHandler:
  Msgbox "Error In Application – Description " & Err.Description
End Sub

Working with properties

Some properties refer to specific interfaces in the ESRI object library, and other properties have values that are standard data types such as strings, numeric expressions, and Boolean values. For interface references, declare an interface variable and use the Set statement to assign the interface reference to the property. For other values, declare a variable with an explicit data type or use Visual Basic's Variant data type. Then, use a simple assignment statement to assign the value to the variable.

Properties that are interfaces can be set either by reference or by value. Properties that are set by value do not require the Set statement.

  Dim pEnv As IEnvelope
  Set pEnv = pActiveView.Extent   'Get extent property of view
  pEnv.Expand 0.5, 0.5, True      'Shrink envelope
  pActiveView.Extent = pEnv       'Set By Value extent back on IActiveView

  Dim pFeatureLayer as IfeatureLayer
  Set pFeatureLayer = New FeatureLayer    'Create New Layer
  Set pFeatureLayer.FeatureClass = pClass 'Set ByRef a class into layer

As you might expect, some properties are read-only, others are write-only, and still others are read/write. All the object browsers and the ArcObjects Class Help (found in the ArcGIS Developer Help system) provide this information. If you attempt to use a property and either forget or misuse the Set keyword, Visual Basic will fail the compilation of the source code with a "method or data member not found" error message. This error may seem strange since it may be given for trying to assign a value to a read-only property. The reason for the message is that Visual Basic is attempting to find a method in the type library that maps to the property name. In the above examples, the underlying method calls in the type library are put_Extent and putref_FeatureClass.

Working with methods

Methods perform some action and may or may not return a value. In some instances, a method returns a value that's an interface; for example, in the code fragment below, TrackCircle returns an IPolygon interface:

  Dim pCircle as IPolygon
  Set pCircle = MapControl1.TrackCircle

In other instances, a method returns a Boolean value that reflects the success of an operation or writes data to a parameter; for example, the IsActive method of IActiveView returns a value of true if the map is active.

Be careful not to confuse the idea of a Visual Basic return value from a method call with the idea that all COM methods must return an HRESULT. The VBVM is able to read type library information and set up the return value of the VB method call to be the appropriate parameter of the COM method.

Working with events

Events let you know when something has occurred. You can add code to respond to an event. For example, a command button has a Click event. You add code to perform some action when the user clicks the control. You can also add events that certain objects generate. VBA and Visual Basic let you declare a variable with the keyword WithEvents. WithEvents tells the development environment that the object variable will be used to respond to the object's events. This is sometimes referred to as an "event sink". The declaration must be made in a class module or a form. Here's how you declare a variable and expose the events of an object in the Declarations section:

  Private WithEvents m_pViewEvents as Map

Visual Basic only supports one outbound interface (marked as the default outbound interface in the IDL) per coclass. To get around this limitation, the coclasses that implement more than one outbound interface have an associated dummy coclass that allows access to the secondary outbound interface. These coclasses have the same name as the outbound interface they contain, minus the I.

  Private WithEvents m_pMapEvents as MapEvents

Once you've declared the variable, search for its name in the Object combo box at the top left of the Code window. Then, inspect the list of events to which you can attach code in the Procedure/Events combo box at the top right of the code window.

Not all procedures of the outbound event interface need to be stubbed out, as Visual Basic will stub out any unimplemented methods. This is different from inbound interfaces, in which all methods must be stubbed out for compilation to occur.

Before the methods are called, the hookup between the event source and sink must be made. This is done by setting the variable that represents the sink to the event source.

  Set m_pMapEvents = MapControl1.Map

Pointers to valid objects as parameters

Some ArcGIS methods expect interfaces for some of their parameters. The interface pointers passed can point to an instanced object before the method call or after the method call is completed.

For example, if you have a polygon (pPolygon) whose center point you want to find, you can write code as follows:

  Dim pArea As IArea
  Dim pPt As IPoint
  Set pArea = pPolygon ' QI for IArea on pPolygon
  Set pPt = pArea.Center

You don't need to create pPt because the Center method creates a Point object for you and passes back a reference to the object via its IPoint interface. Only methods that use client-side storage require you to create the object prior to the method call.

Passing data between modules

When passing data between modules it is best to use accessor and mutator functions that manipulate some private member variable. This provides data encapsulation, which is a fundamental technique in object-oriented programming. Public variables should never be used.

For instance, you might have decided that a variable has a valid range of 1–100. If you were to allow other developers direct access to that variable, they could set the value to an illegal value. The only way of coping with these illegal values is to check them before they get used. This is both error prone and tiresome to program. The technique of declaring all variables as private member variables of the class and providing accessor and mutator functions for manipulating these variables will solve this problem.

In the example below, these properties are added to the default interface of the class. Notice the technique used to raise an error to the client.

Private m_lPercentage As Long

Public Property Get Percentage() As Long
  Percentage = m_lPercentage
End Property

Public Property Let Percentage(ByVal lNewValue As Long)
  If (lNewValue >= 0) And (lNewValue <= 100) Then
    m_lPercentage = lNewValue
  Else
    Err.Raise vbObjectError + 29566, "MyProj.MyObject", _
    "Invalid Percentage Value. Valid values (0 -> 100)"
  End If
End Property

When you write code to pass an object reference from one form, class, or module to another, for example:

  Private Property Set PointCoord(ByRef pPt As IPoint)
    Set m_pPoint = pPt
  End Property

your code passes a pointer to an instance of the IPoint interface. This means that you are only passing the reference to the interface, not the interface itself; if you add the ByVal keyword (as follows), the interface is passed by value.

  Private Property Let PointCoord(ByVal pPt As IPoint)
    Set m_pPoint = pPt
  End Property

In both of these cases the object pointed to by the interfaces is always passed by reference. To pass the object by value, a clone of the object must be made, and that clone is passed.

Using the TypeOf keyword

To check whether an object supports an interface, you can use Visual Basic's TypeOf keyword. For example, given the first layer in a map control, you can test whether it is a CAD layer using the following code:

  Dim pUnk As IUnknown
  Dim pCadLayer As ICadLayer
    
  Set pUnk = MapControl1.Layer(0)

  If TypeOf pUnk Is ICadLayer Then
    Set pCadLayer = pUnk 
    ' do something with the layer
  End If

Using the Is operator

If your code requires you to compare two interface reference variables, you can use the Is operator. Typically, you can use the Is operator in the following circumstances:

To check if you have a valid interface. For example, see the following code:

  Dim pPt As IPoint
  Set pPt = New Point
  If (Not pPt Is Nothing) Then 'a valid pointer?
    ... ' do something with pPt
  End If

To check if two interface variables refer to the same actual object. Imagine that you have two interface variables of type IPoint, pPt1 and pPt2. Are they pointing to the same object? If they are, then pPt1 Is pPt2.

The Is keyword works with the COM identity of an object. Below is an example that illustrates the use of the Is keyword when finding out if a certain method on an interface returns a copy of or a reference to the same real object.

In the following example, the Extent property on a map (IMap) returns a copy, while the ActiveView property on a MapControl always returns a reference to the real object.

  Dim pEnv1 As IEnvelope
  Dim pEnv2 as IEnvelope
  
  Dim pActiveView1 As IActiveView
  Dim pActiveView2 as IActiveView
  
  Set pEnv1 = MapControl1.ActiveView.Extent
  Set pEnv2 = MapControl1.ActiveView.Extent
  
  Set pActiveView1 = MapControl1.ActiveView
  Set pActiveView2 = MapControl1.ActiveView
  
  ' Extent returns a copy,
  ' so pEnv1 Is pEnv2 returns false
  MsgBox pEnv1 Is pEnv2

  ' ActiveView returns a reference, so
  ' ActiveView1 Is ActiveView2 returns true
  MsgBox pActiveView1 Is pActiveView2

Iterating through a collection

In your work with ArcObjects, you'll discover that, in many cases, you'll be working with collections. You can iterate through these collections with an enumerator. An enumerator is an interface that provides methods for traversing a list of elements. Enumerator interfaces typically begin with IEnum and have two methods, Next and Reset. Next returns the next element in the set and advances the internal pointer, and Reset resets the internal pointer to the beginning.

Enumerators can support other methods, but these two methods are common among all enumerators.

Here is some VB code that loops through the selected features (IEnumFeature) in a map control.

  Dim pEnumFeat As IEnumFeature
  Dim pFeat As IFeature
  Set pEnumFeat = MapControl1.Map.FeatureSelection
  Set pFeat = pEnumFeat.Next
  Do While (Not pFeat Is Nothing)
    Debug.Print pFeat.Value(pFeat.Fields.FindField("state_name"))
    Set pFeat = pEnumFeat.Next
  Loop
Some collection objects, the Visual Basic Collection being one, implement a special interface called _NewEnum. This interface, because of the _ prefix, is hidden, but Visual Basic developers can still use it to simplify iterating though a collection. The Visual Basic For Each construct works with this interface to perform the Reset and Next steps through a collection.
  Dim pColn as Collection
  Set pColn = GetCollection() ' Collection returned from some function
  Dim thing as Variant        ' VB uses methods on _NewEnum to step through
  For Each thing in pColn     ' an enumerator.
    MsgBox Cstr(thing)
  Next

 :