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:
Most development environments and programming languages have their own error handling model. COM was designed to be language-neutral, and therefore, needs a language-neutral error handling model. The COM specification defines two parts to the COM error handling model.
A method call in a COM server that has generated an exception creates an instance of the COM error object using the ICreateErrorInfo interface. The server then populates the error object with information about the source and cause of an error. When program control returns to the COM client, the client can retrieve the error object, find the information, and use this information determine how to handle the error.
Unlike exceptions thrown within a program, COM exceptions do not stop the flow of execution; instead, a COM error object is created within the current thread. The error object supports the IErrorInfo interface, which has methods allowing the client to get the name of the class and interface that created the error and a description of the error.
COM defines another interface, ISupportErrorInfo, which is used to indicate that a class may create a COM error object. Many ArcObjects classes implement the ISupportErrorInfo interface.
As the COM error object does not halt program flow, there needs to be some mechanism by which the client can tell when an error object has been createdCOM's answer to this is the HRESULT.
The COM specification states that all function calls should return an HRESULTa 32-bit unsigned integer, which indicates success or failure of the call. An HRESULT contains various pieces of information.
HRESULT values are often written using hexidecimal (base 16) notationsome of the most common standard HRESULTS are shown in the table below.
| Symbolic Constant | Hexidecimal Value | Description |
|---|---|---|
| S_OK | 00000000 | Standard return value indicating successful completion |
| S_FALSE | 00000001 | Alternate success value, indicating successful but nonstandard completion (precise meaning depends on context) |
| E_UNEXPECTED | 8000FFFF | Catastrophic failure |
| E_NOTIMPL | 80004001 | Not implemented |
| E_OUTOFMEMORY | 8007000E | Out of memory |
| E_INVALIDARG | 80070057 | One or more arguments are not valid |
| E_NOINTERFACE | 80004002 | Interface not supported |
| E_POINTER | 80004003 | Pointer not valid |
| E_HANDLE | 80070006 | Handle not valid |
| E_ABORT | 80004004 | Operation aborted |
| E_FAIL | 80004005 | Unspecified error |
| E_ACCESSDENIED | 80070005 | General access denied |
If you are working in VC++, you can find a listing of standard Windows and COM HRESULTS and their associated constants in the WinError.h header file, which is installed with Visual Studio. If you are working in VB, you can look up these HRESULTs in MSDN, although you should be aware that the VBVM may translate certain HRESULTS into VB-specific error codes; see the following sections for more information on VB error codes.
Facility Codes
The facility code 4 indicates an error is caused by a call to a COM interface. Errors created by ArcObjects interface calls generally use a facility code of 4, although there are exceptions to this rulefor example, the Engine Controls use the facility code 10 (FACILITY_CONTROL). A list of standard facility codes can also be found in the WinError.h file or in MSDN.
ArcObjects error codes
A number of enumerations, defined in ArcObjects libraries, give the error codes and description string of errors which may be created by ArcObjects method calls.
ArcObjects libraries contain enumerations of constants listing error codes.
The way in which a component handles errors may differ from the way in which an application handles errors.
User-interface components, such as property pages and dialog boxes, can make use of error-handling routines to check user input to the interface and to allow a user to correct an error. However, many of the examples presented throughout this book demonstrate classes without a user interfacefor example, a workspace extension, or a custom symbol without a property page. For such classes you should always avoid using UI features such as message boxes or forms in your error handling routines. Also, when designing your error handling routines, you should always consider the context in which your component may be runningcould your component be instantiated in a server environment?
If you cannot handle an error created in your component (or raised from a server component), then you should pass the error back to your client in turn. In some cases, you may want to translate the error to a more appropriate error for your component. For example, a custom layer component may experience a problem reading from the disk; however, the layer client would find it more useful to receive an error indicating that the layer cannot be displayed, rather than a low-level disk or file error.
You can define your own range of error codes, allowing clients to see which error codes may be raised by your component. You can expose your error codes in a similar way as used by ArcObjectsby declaring enumerations of constants, giving an error code and symbolic constant.
As errors created by your custom components will be created when the client calls an interface member, your HRESULTs should use the facility code 4. For the information part of an HRESULT, the range 0 to 512 (H0 to H200) is reserved for system errors. The range 16382 to 16639 (H4000 to H40FF) is also reserved for OLE errors. When specifying your own error codes, it is best practice to avoid these ranges entirely. You may also want to avoid the codes used by ArcObjects.
More details on the ways in which you can declare, handle, and raise errors in your components are discussed separately for the VB and VC++ environments.
Error handling is included at its minimum throughout the examples in the book to keep the sample code as readable as possible. If you are having problems with one of the VB samples, it is a simple matter to add error handling throughout the component by using the ESRI Error Handler Generator. In the VC++ samples a minimum of HResult checking is performed, which again is not a suitable pattern for released code, but helps keep sample code simpler to read and understand.
Some COM-compatible runtime environments check HRESULTS and automatically respond by translating the information from the COM error object into the language-specific error handling model.
VB has its own form of exception handling that uses the intrinsic global Err object. This mechanism is unrelated to the COM error object, but VB developers are still able to successfully work with errors raised from COM methods, thanks to the Visual Basic Virtual Machine (VBVM).
The VBVM checks the value of every HRESULT returned from a COM method call. If the severity code of an HRESULT is 1 (indicating an error has occurred), the VBVM populates the Err object's Number property with the value of the HRESULT. The data type of the VB global error object's Number property is a signed integer, and therefore, the unsigned HRESULT integer is coerced to a signed integer, which results in all COM error codes in VB having a negative value. The VBVM also attempts to populate the Source and Description fields with information retrieved from the COM error object via IErrorInfo. Any HRESULT with a severity code of 0 is ignored by the VBVM; therefore, no error is raised in VB, and VB does not receive any information from such codes.
Due to the 'under-the-covers' activity of the VBVM, VB users do not see HRESULTs returned from interface members in VBinstead a member returns the parameter specified using the IDL attribute [out]. Another effect of this action is that VB can only handle one error at a timeonly the most recent error can be discovered, although the underlying COM error object may support a collection of errors.
You may also notice that the VBVM translates some COM errors into more VB-friendly termsfor example, the E_NOTIMPL error (H80004001) becomes the VB error 445, 'Object doesn't support this action'. You can find the full list of VB's trappable errors in the VB online reference or in MSDN.
Using VB's On Error statement you can deal with any errors within your componentas a VB programmer you should already be familiar with writing error handling routines.
To attempt to deal with the cause of an error, you must first work out which error you are dealing with. If you need to identify the facility and information codes of an error number separately, you can use the functions below.
FunctionFacilityCode(dwordAs Long)As LongFacilityCode = ((dwordAnd&HFFFF0000) \ &H10000)And&HFFFEnd Function FunctionInformationCode(ByValeAs Long)As LongInformationCode = eAnd&HFFFF&End Function
For example, the code excerpt below shows an error handler, which traps the error that a Geometry (m_pTopological) is not Simple, and simplifies the geometry.
ErrorHandler:If(Err.Number < 0)And(Facility(Err.Number) = 4)Then IfBasicError(Err.Number) = esriGeometryError.E_GEOMETRY_NOTSIMPLEThen If Not(m_pTopological.IsSimple)Thenm_pTopological.Simplify ...Else...
At this point, your action should depend on your code. You may want to use the Resume keyword to attempt the operation which caused the error.
If your component cannot recover from the error and continue running, you should raise an error back to your client. This can also be performed by using the VB Err object. Often you may raise an error directly. For example, in the code below, the standard COM error is raised to indicate that a particular method has no implementation code. A constant is declared to hold the HRESULT value, and the error is raised within the interface member.
ConstE_NOTIMPL = &H80004001 ...Private Property GetICommand_HelpContextID()As LongErr.Raise E_NOTIMPLEnd Property
The VBVM takes care of mapping the VB error to a COM error object and HRESULT, so clients can access the error information as they would any other COM error.
Of course, it may be more appropriate to translate an error into your own custom error, giving your client an error that relates directly to your component.
It is good practice to advertise the errors that you may raise by declaring an enumeration of constants. VB provides the vbObjectError constant to help you define error codesthis sets the severity code to 1, and the facility code to 4. When you decide on your own range of error codes, don't forget to add 512 to your constant to ensure you avoid the reserved range of information codes.
Public EnumenumMyErrors myErrorNoLicense = vbObjectError + 512 + 500 myErrorGeneralError = vbObjectError + 512 + 501End Enum
When the underlying error occurs, you can report the error back to your client. If you use a standard VB error number, you do not have to pass a description string; however, if you are raising a custom error, you should pass a useful description of your error back to your client.
ErrorHandler:IfErr.Number = esriGeodatabase.fdoError.FDO_E_SE_OUT_OF_LICENSESThenErr.Raise myErrorNoLicense, "Component_Class", _ "You do not have a license to use that editing functionality."End If End If End Sub
The ESRI ErrorHandler add-in for VB may be useful when you are writing and debugging your components. However, the ErrorHandler relies on a UI component to handle errors, and therefore, should never be used if your component may run in a server environment, and is not suitable for any deployed code.
In the VC++ environment, unlike VB, it is the responsibility of the COM client to check the HRESULT of each method call to see if an error has been created. You can then interrogate the COM error object for information about the error. Similarly, if an exception is generated in your code, it is your responsibility to create the COM error object and return the appropriate HRESULT.
You should explicitly check the return values of all method calls to uncover any errorsthis includes calls to property accessors (get_ and put methods). Use the FAILED and SUCCEEDED macros defined in WinError.h.
hr = ipFeatureLayer->put_Visible(VARIANT_FALSE);
The simplest course of action when you find a failure is to exit the procedure, returning the same HRESULT.
if (FAILED(hr)) return hr;
However, if you want to investigate the error further and attempt to resolve it, you can use the GetErrorInfo function defined in the Ole automation library.
if(FAILED(hr)) { IErrorInfoPtr ipError; ::GetErrorInfo(0,&ipError); ipError->GetDescription(&bError);' Do something based on the error here.}
In addition to returning an HRESULT, you can also create a COM error object to give additional information about an exception. This step is not mandatory, although it is considered best practice.
Any class that creates the COM error object should also implement the additional COM interface ISupportErrorInfothis indicates to clients that an error object may be created by this class and also ensures that error information can be returned to the client.
A class that creates COM error objects using the IErrorInfo interface needs to implement the ISupportErrorInfo interface.
When it comes to returning errors to clients of your component, the process is somewhat simplified if you create your class using the ATL Object Wizard. In the ATL Object Wizard Properties dialog box, the Attributes tab contains an option called 'Support ISupportErrorInfo'. Selecting this option will create a class that supports ISupportErrorInfo, and a method will be added to your class that looks something like the code below.
STDMETHODIMP CMyClass::InterfaceSupportsErrorInfo(REFIID riid)
{
static const IID* arr[] =
{
&IID_IMyClass
};
for (int i=0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
if (InlineIsEqualGUID(*arr[i],riid))
return S_OK;
}
return S_FALSE;
}Using the ATL Object Wizard simplifies the process of returning errors from your VC++ class.
You can define an enumeration of error codes in your IDL file, so that clients can find out which errors you may create. You can use the MAKE_HRESULT macro, defined in the Winerror.h header file, to build your HRESULT.
typedef enum enumMyClassErrorCodes
{
[helpstring("My Class in invalid")]
E_MYCLASS_INVALID = MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, 0x3000)
} enumMyClassErrorCodes;
Remember that clients, such as VB, will not be able to retrieve any information if you return an HRESULT with a severity code of 0 (indicating nonstandard success of a method call).
You can use the ATL global function AtlReportError to create and populate the COM error object.
AtlReportError(CLSID_MyClass, _T("No connection to Database."), IID_IMyClass, E_FAIL);The function also has a form that works with resource files to ensure easy internationalization of the message strings.
AtlReportError(CLSID_MyClass, IDS_DBERROR, IID_IMyClass, E_FAIL, _Module.m_hInstResource);
If you return a failed HRESULT without specifying the details of the error in the COM error object, the string "Unknown Error" will be used by clients such as VB.