Getting Started

Visual Basic Development Environment

The Visual Basic Environment (General)

In the previous topic on Visual Basic for Applications (VBA) Specifics, we focused primarily on how to write code in the Visual Basic for Applications (VBA) development environment embedded within ArcMap and ArcCatalog. Now this topic focuses on particular issues related to creating ActiveX DLLs that can be added to the applications and writing external standalone applications using the Visual Basic development environment. More details of using Visual Basic are given with the documentation that accompanies ArcObjects Developer Controls.

Creating COM components
Implementing interfaces
Setting references to the ESRI object libraries
    To add a reference to an object library
Referring to a document
Getting to an object
Running ArcMap with a command line argument
Debugging Visual Basic Code
    Running The Code Within an Application
    Visual Basic Debugger Issues
    Alternatives to the Visual Basic Debugger
        Visual C++ Debugger
        ATL Wrapper Classes

Creating COM components

Most developers use Visual Basic to create a COM component that works with ArcMap or ArcCatalog. Earlier in the Getting Started section of the help you learned that since the ESRI applications are COM clients—their architecture supports the use of software components that adhere to the COM specification—you can build components with different languages, including Visual Basic. These components can then be added to the applications easily. For information about packaging and deploying COM components that you've built with Visual Basic, see the Packaging and Deploying Customizations topic.

This section is not intended as a Visual Basic tutorial; rather, it highlights aspects of Visual Basic that you should know in order to be effective when working with ArcObjects.

In Visual Basic you can build a COM component that will work with ArcMap or ArcCatalog by creating an ActiveX DLL. This section will review the rudimentary steps involved. Note that these steps are not all-inclusive. Your project may involve other requirements.

  1. Start Visual Basic. In the New Project dialog box, create an ActiveX DLL Project.
  2. In the Properties window, make sure that the Instancing property for the initial class module and any other class modules you add to the Project is set to 5—MultiUse.
  3. Reference the ESRI Object Library.
  4. Implement the required interfaces. When you implement an interface in a class module, the class provides its own versions of all the public procedures specified in the type library of the interface. In addition to providing a mapping between the interface prototypes and your procedures, the Implements statement causes the class to accept COM QueryInterface calls for the specified interface ID. You must include all the public procedures involved. A missing member in an implementation of an interface or class causes an error. If you don't put code in one of the procedures in a class you are implementing, you can raise the appropriate error (Const E_NOTIMPL = &H80004001). That way, if someone else uses the class, they'll understand that a member is not implemented.

    The ESRI Interface Implementer Add-In for VB can be used to automate steps 3 and 4.

  5. Add any additional code that's needed.
  6. Establish the Project Name and other properties to identify the component. In the Project Properties dialog box, the Project Name you specify will be used as the name of the component's type library. It can be combined with the name of each class the component provides to produce unique class names (these names are also called ProgIDs). These names appear in the Component Category Manager. Save the project.
  7. Compile the DLL.
  8. Set the component's Version Compatibility to binary. As your code evolves, it's good practice to set the components to Binary Compatibility so if you make changes to a component, you'll be warned that you're breaking compatibility. For additional information, see the 'Binary compatibility mode' help topic in the Visual Basic online help.

    Visual Basic automatically generates the necessary GUIDs for the classes, interfaces, and libraries. Setting binary compatibility forces VB to re-use the GUIDs from a previous compilation of the DLL. This is essential since ArcMap stores the GUIDs of commands in the document for subsequent loading.

  9. Save the project.
  10. Make the component available to the application. You can add a component to a document or template by clicking the Add from file button in the Customize dialog box's Commands tab. In addition, you can register a component in the Component Category Manager.

Implementing interfaces

You implement interfaces differently in Visual Basic depending if they are inbound or outbound interfaces. An outbound interface is seen by Visual Basic as an event source, and is supported through the WithEvents keyword. To handle the outbound interface, IActiveViewEvents, in Visual Basic (the default outbound interface of the Map class), use the WithEvents keyword and provide appropriate functions to handle the events.

  Private WithEvents ViewEvents As Map

  Private Sub ViewEvents_SelectionChanged()
    ' User changed feature selection update my feature list form
    UpdateM FeatureForm
  End Sub

Inbound interfaces are supported with the Implements keyword. However, unlike the outbound interface, all the methods defined on the interface must be stubbed out. This ensures that the vTable is correctly formed when the object is instantiated. Not all of the methods have to be fully coded, but the stub functions must be there. If the implementation is blank an appropriate return code should be given to any client to inform them that the method is not implemented (see the section Working With HRESULTs in the "The Visual Basic Environment (General)" topic). To implement the IExtension interface, code similar to that below is required. Note all the methods are implemented.

Private m_pApp As IApplication
Implements IExtension

Private Property Get IExtension_Name() As String
  IExtension_Name = "Sample Extension"
End PropertY

Private Sub IExtension_Startup(B Ref initializationData As Variant)
  Set m_pApp = initializationData
End Sub

Private Sub IExtension_Shutdown()
  Set m_pApp = Nothing
End Sub

Setting references to the ESRI object libraries

The principle difference between working with the VBA development environment embedded in the applications and working with Visual Basic is that the latter environment requires that you load the appropriate object libraries so that any object variables that you declare can be found. If you don't add the reference, you'll get the error message to below. In addition, the global variables ThisDocument and Application are not available to you.

To add a reference to an object library

In all cases, you'll need to load the ESRI Object Library esriCore.olb. Depending on what you want your code to do, you may add other ESRI object libraries, perhaps for one of the extensions.

To display the References dialog box in which you can set the references you need, select References in the Visual Basic Project menu.

After you set a reference to an object library by selecting the check box next to its name, you can find a specific object and its methods and properties in the Object Browser.

The References dialog.

After the ESRI Object Library is referenced, all the types contained within it are available to Visual Basic. IntelliSense will also work with the contents of the object library.

If you are not using any objects in a referenced library, you should clear the check box for that reference to minimize the number of object references Visual Basic must resolve, thus reducing the time it takes your project to compile. You should not remove a reference for an item that is used in your project.

You can't remove the "Visual Basic for Applications" and "Visual Basic objects and procedures" references because they are necessary for running Visual Basic.

Referring to a document

Each VBA project (Normal, Project, TemplateProject) has a class called ThisDocument, which represents the document object. Anywhere you write code in VBA you can reference the document as ThisDocument. Further, if you are writing your code in the ThisDocument Code window, you have direct access to all the methods and properties on IDocument. This is not available in Visual Basic. You must first get a reference to the Application and then the document. When adding both extensions and commands to ArcGIS applications, a pointer to the IApplication interface is provided. For code samples that show you how to get a handle on the application, see chapter 3, 'Customizing the user interface' in the Exploring ArcObjects book.

Implements IExtension
Private m_pApp As IApplication

Private Sub IExtension_Startup(ByRef initializationData As Variant)
  Set m_pApp = initializationData    ' Assign IApplication
End Sub

Implements ICommand
Private m_pApp As IApplication

Private Sub ICommand_OnCreate(ByVal hook As Object)
  Set m_pApp = hook                  ' QI for IApplication
End Sub

Now that a reference to the application is in an IApplication pointer member variable, the document, and hence all other objects, can be accessed from any method within the class.

  Dim pDoc as IDocument
  Set pDoc = m_pApp.Document
  MsgBox pDoc.Name

Getting to an object

In the previous example, navigating around the objects within ArcMap is a straightforward process since a pointer to the Application object, the root object of most of the ArcGIS application's objects, is passed to the object via one of its interfaces. This, however, is not the case with all interfaces that are implemented within the ArcObjects application framework. There are cases when you may implement an object that exists within the framework and there is no possibility to traverse the object hierarchy from that object. This is because very few objects support a reference to their parent object (the IDocument interface has a property named Parent that references the IApplication interface). In order to give developers access to the application object, there is a singleton object that provides a pointer to the running application object. The code below illustrates its use.

  Dim pAppRef As New AppRef
  Dim pApp as IApplication
  Set pApp = pAppRef

Singletons are objects that only support one instance of the object. These objects have a class factory that ensures that anytime an object is requested, a pointer to an already existing object is returned.

You must be careful to ensure that this object is only used where the implementation will only ever run within ArcMap and ArcCatalog. For instance, it would not be a good idea to make use of this function from within a custom feature, since that would restrict what applications could be used to view the feature class.

Running ArcMap with a command line argument

You can start ArcMap from the command line and pass it an argument that is either the pathname of a document (.mxd) or the pathname of a template (.mxt). In the former case, ArcMap will open the document; in the latter case ArcMap will create a new document based on the template specified.

You can also pass an argument and create an instance of ArcMap by supplying arguments to the Win32 API's ShellExecute function or Visual Basic's Shell function, as follows.

  Dim ret As Variant
  ret = Shell("d:\arcexe83\bin\arcmap.exe _
    d:\arcexe83\bin\templates\LetterPortrait.mxt", vbNormalFocus)

By default, Shell runs other programs asynchronously. This means that ArcMap might not finish executing before the statements following the Shell function are executed.

In Visual Basic, it is not possible to determine the command line used to start the application. There is a sample on disk that provides this functionality. It can be found at \arcgis\arcexe83\ArcObjects Developer Kit\samples\COMTechniques\Command Line.

To execute a program and wait until it is terminated, you must call three Win32 API functions. First, call the CreateProcessA function to load and execute ArcMap. Next, call the WaitForSingleObject function, which forces the operating system to wait until ArcMap has been terminated. Finally, when the user has terminated the application, call the CloseHandle function to release the application's 32-bit identifier to the system pool.

Debugging Visual Basic Code

Visual Basic has a debugger integrated into its development environment. This is in many cases a valuable tool when debugging Visual Basic code, however, in some cases it is not possible to use the VB debugger. The use of the debugger and these special cases are discussed below.

Running The Code Within an Application

It is possible to use the Visual Basic debugger to debug your ArcObjects-based source code, even when ActiveX DLLs are the target server. The application that will host your DLL must be set as the Debug application. To do this, select the appropriate application, ArcMap.exe, for instance, and set it as the Start Program in the Debugging Options of the Project Properties.

The Projects menu.

The Debugging pane of the Project Properties dialog box.

Using commands on the Debug toolbar, ArcMap can be started and the DLL loaded and debugged. Breakpoints can be set, lines stepped over, functions stepped into and variables checked. Moving the line pointer in the left-hand margin can also set the current execution line.

The Debug toolbar.

Visual Basic Debugger Issues

In many cases, the Visual Basic debugger will work without any problems, however there are two problems when using the debugger that is supplied with Visual Basic 6. Both of these problems exist because of the way that Visual Basic implements its debugger.

Normally when running a tool within ArcMap, the DLL is loaded into ArcMap's address space and calls are made directly into the DLL. When debugging this is not the case. Visual Basic makes changes to the registry so that the CLSID for your DLL does not point to your DLL, but instead it points to the Visual Basic Debug DLL (VB6debug.dll). The Debug DLL must then support all the interfaces implemented by your class on the fly. With the VB Debug DLL loaded into ArcMap, any method calls that come into the DLL are forwarded on to Visual Basic where the code to be debugged is executed. The two problems with this are caused by the changes made to the Registry and the cross-process space method calling. When these restrictions are first encountered it can be confusing since the object works outside the debugger, or at least until it hits the area of problem code.

Since the method calls made from ArcMap to the custom tool are across apartments, there is a requirement for the interfaces to be marshaled. This marshalling causes problems in certain circumstances. Most data types can be automatically marshaled by the system, but there are a few that require custom code, because the standard marshaler does not support the data types. If one of these data types is used by an interface within the custom tool and there is no custom marshalling code, the debugger will fail with an "Interface not supported error".

The registry manipulation also breaks the support for component categories. Any time there is a request on a component category, the category manager within COM will be unable to find your component, because rather than asking whether your DLL belongs to the component category, COM is asking whether the VB debugger DLL belongs to the component category, which obviously it doesn't. What this means is that anytime a component category is used to automate the loading of a DLL, the DLL cannot be debugged using the Visual Basic debugger.

This obviously causes problems for many of the ways to extend the framework. The most common way to extend the framework is to add a command or tool. Previously it was discussed how component categories were used in this instance. Remember the component category was only used to build the list of commands in the dialog box. This means that if the command to be debugged is already present on a toolbar the Visual Basic debugger can be used. Hence, the procedure for debugging Visual Basic objects that implement the ICommand interface is to ensure that the command is added to a toolbar when ArcMap is executed standalone, and then, after saving the document, loading ArcMap through the debugger.

In some cases, like extensions and property pages, it is not possible to use the Visual Basic debugger. If you have access to the Visual C++ Debugger you can use one of the options outlined below. Fortunately, there are a number of ESRI Visual Basic Add-ins that make it possible to track down the problem quickly and effectively. The add-ins described in the topic ESRI Add-Ins for Visual Basic, provide error log information, including line and module details. A sample output from an error log is given below; note the call stack information along with line numbers.

Error Log saved on : 8/28/2000 -  0:39:04 AM
Record Call Stack Sequence - Bottom line is error line.
    chkVisible_MouseUp C:\Source\MapControl\Commands\frmLa er.frm Line :  96
    RefreshMap C:\Source\MapControl\Commands\frmLa er.frm Line : 20
    Object variable or With block variable not set

Alternatives to the Visual Basic Debugger

If the Visual Basic debugger and add-ins do not provide enough information, the Visual C++ debugger can be used, either on its own or with C++ ATL wrapper classes. The Visual C++ debugger does not run the object to be debugged out of process from ArcMap, which means that none of the above issues apply. Common debug commands are given in the section Debugging Tips In Developer Studio in the "The Visual C++ Environment" topic. Both of the techniques below require the Visual Basic project to be compiled with Debug Symbol information.

The Visual C++ Debugger can work with this symbolic debug information and the source files.

Visual C++ Debugger

It is possible to use the Visual C++ debugger directly by attaching to a running process that has the Visual Basic object to be debugged loaded, and then setting a breakpoint in the Visual Basic file. When the line of code is reached, the debugger will halt execution and step you into the source file at the correct line. The required steps are shown below.

  1. Start an appropriate application, such as ArcMap.exe.
  2. Start Microsoft Visual C++.
  3. Attach to the ArcMap process using Menu option Build -> Start Debug -> Attach to process.
  4. Load the appropriate Visual Basic Source file into the Visual C++ debugger and set the break point.
  5. Call the method within ArcMap.

No changes can be made to the source code within the debugger, and variables cannot be inspected, but code execution can be viewed and altered. This is often sufficient to determine what is wrong, especially with logic-related problems.

ATL Wrapper Classes

Using ATL, you can create a class that implements the same interfaces as the Visual Basic Class. When you create the ATL Object you create the Visual Basic object. All method calls are then passed to the Visual Basic Object for execution. You debug the contained object by setting a break point in the appropriate C++ wrapper method, and when the code reaches the breakpoint the debugger is stepped into the Visual Basic Code. For more information on this technique, look at the ATL Debug Wrapper Class sample located in the "\ArcObjects Developer Kit\Samples\Exploring ArcObjects\Chapter Two\ATL Debug Wrapper Class" folder in your ArcGIS install location.