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
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
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.
The ESRI Interface Implementer Add-In for VB can be used to automate steps 3 and 4.
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.
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
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.
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
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.
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.
By default, Shell runs other programs asynchronously. This means that ArcMap might not finish executing before the statements following the Shell function are executed.
Dim ret As Variant ret = Shell("d:\arcexe83\bin\arcmap.exe _ d:\arcexe83\bin\templates\LetterPortrait.mxt", vbNormalFocus)
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.
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.
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.
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 Description Object variable or With block variable not set
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.
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.