Lesson 2: COM Errors

COM applications and components can use the error handling techniques discussed in Lesson 1, but must also confront COM's preferred method of communicating error information through values known as HRESULT codes.

This lesson describes HRESULT codes in detail and points out various pitfalls to avoid when dealing with COM errors. Keep in mind that, with COM, success and failure are not always black and white concepts. COM allows for the idea that success and failure might be only partial, not complete.

You will also learn how a COM component can notify its client of an error through the Error event, one of the several stock events defined in COM.

After this lesson, you will be able to:

  • Understand and create HRESULT codes used in COM programming.
  • Compensate for applications that do not properly receive error codes from COM servers.
  • Understand when to use the COM Error event.
Estimated lesson time: 15 minutes

HRESULT Codes

In COM, interface methods return HRESULT codes to report success or failure. Despite its "H" prefix, an HRESULT code is not a handle, but merely a 32bit value indicating a success or failure condition.

COM predefines many HRESULT codes such as S_OK for success, S_FALSE for failure, and various error codes such as E_INVALIDARG and E_NOTIMPL. The "E" prefix stands for error. The "S" prefix identifies the first two codes as status code (SCODE) values. Under 32bit Windows, HRESULT and SCODE mean the same thing.

COM allows your own programs to define new HRESULT values, as explored in the next section. However, to prevent confusion, you should use COM's predefined values whenever appropriate. For example, the E_OUTOFMEMORY code, already defined by the COM library, communicates a common error condition without ambiguity. You can find a list of predefined HRESULT values and their meanings in the WinError.h file under the heading "OLE Error Codes."

Anatomy of an HRESULT Code

An HRESULT code consists of four bit fields, as illustrated in Figure 13.1. Table 13.2, which follows the figure, describes the fields in the figure from left to right.

click to view at full size.

Figure 13.1 Bit fields of an HRESULT value

Table 13.2 Bit Field Descriptions

Field ID Bits Description
S 31 Severity code. A value of 0 indicates success, and a value of 1 indicates an error. Because the severity bit is also the value's sign bit, a severity value of 1 makes the HRESULT code a negative number.
R 27-30 Reserved bits of the facility code. Should be zero.
Facility 16-26 Facility code that indicates a general category to which the error code belongs.
Code 0-15 A 16-bit WORD value that identifies the condition.

Facility codes are defined as part of the COM specification. Error codes within most facility categories (for example, FACILITY_NULL, FACILITY_RPC) are specified by COM and have universal meaning. COM defines the FACILITY_ ITF category to contain developer-specified error codes that have their meanings determined by the interface member functions from which they are returned.

When defining your own HRESULT codes, use FACILITY_ITF as shown in the following code:

#define MY_SUCCESS_CODE ((0 << 31) | (FACILITY_ITF << 16) | 0x200) #define E_MY_ERROR_CODE ((1 << 31) | (FACILITY_ITF << 16) | 0x201)

The WinError.h file defines FACILITY_ITF as 4, so the first example creates a positive value of 0x00040200. The second example produces a negative value of 0x80040201. WinError.h also defines the MAKE_HRESULT macro to simplify forming HRESULTs on the fly:

// Set HRESULT value 0x80040202 HRESULT hr = MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, 0x202);

A further wrinkle to keep in mind when dealing with HRESULT codes is that COM assumes that a value of zero indicates success, not failure. The manifest constant S_OK, for example, has a value of zero. As Table 13.1 illustrates, COM also assumes that any positive value indicates success, though perhaps qualified.

To save developers the trouble of memorizing these rules, COM provides two macros, named SUCCEEDED and FAILED, which are often used to test HRESULT codes. The macros are not a required addition to your COM programs, but they help make code more readable, as demonstrated in the following code:

HRESULT hr; hr = pUnk->QueryInterface(IID_SomeObject, (PVOID*) &pObj); if (SUCCEEDED(hr)) {      // Use the pObj value      .      .      .      pObj->Release(); }

The macro definitions in the WinError.h file show that SUCCEEDED returns TRUE for any expression that evaluates to zero or greater; in contrast, FAILED evaluates to TRUE for any negative expression, as demonstrated in the following code:

#define SUCCEEDED(Status) ((HRESULT)(Status) >= 0) #define FAILED(Status) ((HRESULT)(Status) < 0)

When dealing with an HRESULT not of your making, be careful how your code interprets it. Generally, you can rely on any constant with an "E" prefix having a negative value; other than that, you have no assurances. When unsure, consult the appropriate header file to determine a defined constant's true numeric value.

_com_error Support Class

Visual C++ provides the _com_error support class, which encapsulates an HRESULT code and helps hide the details behind COM error codes. The _com_error class provides several member functions that retrieve information about the encapsulated error. The most important of these functions are listed in Table 13.3.

Table 13.3 Important _com_error Member Functions

Member function Return value
Error The HRESULT code from which the _com_error object is constructed.
ErrorInfo A pointer to an associated IErrorInfo (described later), or NULL if no IErrorInfo item exists. The caller must call Release() on the returned IErrorInfo object when finished using it.
Wcode The HRESULT minus 0x80040200 if the HRESULT uses FACILITY_ITF. Otherwise, the function returns zero.
ErrorMessage A TCHAR pointer to a system message describing the error. If no system message is available, the returned string is "Unknown error" followed by the hexadecimal HRESULT value.

If the encapsulated HRESULT has an associated IErrorInfo object, _com_error can access the contextual error information that the object contains. For example, the HelpFile() and HelpContext() member functions access the IErrorInfo object and return information about context-sensitive help pertaining to the error, which the caller can then display to the user through the familiar Windows Help system.

Because _com_error represents an exception condition, it often serves as the object passed to a catch block. Following is a simple program that illustrates how to trap errors with _com_error, using the E_OUTOFMEMORY error code as an example:

 #include <comdef.h> #include <stdio.h> void main() {      try      {           _com_error e(E_OUTOFMEMORY);     // Construct the object           throw(e);                        // Force an exception      }      catch(_com_error& e)      {           printf("Error   = %08lx\n", e.Error());           printf("WCode   = %04x\n",  e.WCode());           printf("Meaning = %s\n\n",  e.ErrorMessage());      } }

When the program runs, it displays the following lines:

 Error     = 8007000e WCode     = 0000 Meaning     = Not enough storage is available to complete this operation.

Client Access to HRESULT Codes

After all this, you might be disappointed to learn that many client applications cannot even receive the HRESULT codes that a server's methods return. Thus, your carefully constructed and expressive codes often go to waste. The blame can be traced to limitations inherent in the IDispatch interface.

For example, the MFC client applications discussed in Chapter 11 do not receive HRESULT codes from their embedded COM components. The reason for this is because the wrapper class that Visual C++ creates for a component communicates through the IDispatch interface rather than calling component methods directly through a vtable. During its circuitous connections with the server, IDispatch loses the method's return value, which never makes its way back to the client. The MFC client using IDispatch can only infer when an error occurs in a COM server because a negative HRESULT value returned from a method generates an exception. By enclosing code that accesses the server in try-catch blocks, the client can respond in a general way to errors, though it cannot tell precisely what error occurred.

A Microsoft Visual Basic client fares no better. Like the MFC client, a program written in Visual Basic cannot receive HRESULT return values. The Visual Basic client can only react to negative error codes, usually through an On Error statement.

Before closing this section, we should point out that some C++ clients do properly receive HRESULT return codes. Whether it uses MFC or not, an application written in C++ does not need to rely on IDispatch and can receive a server's return values by bypassing the wrapper class that Visual C++ creates. However, such a technique is beyond the scope of this course.

Error Event

When a COM component method returns an HRESULT code to the client application, the notification is said to be synchronous. A synchronous notification forms part of the normal flow of communication between server and client: the client calls the server, the server detects an error, the server returns an HRESULT code explaining the problem, and the client reacts to the error. These steps occur in ordered progression; the client learns of the error only after the component ceases its task and returns.

In some cases, the component might encounter an error of which the client should be immediately apprised. In such a case, the component can fire an event and continue working. In another scenario, a component might have worker threads operating simultaneously with the client. If a worker thread detects an error, its only means of notifying the client is by firing an event. COM defines a stock event named Error that allows this type of asynchronous error notification.

The Error event has the predefined dispatch identifier DISPID_ERROREVENT. An ActiveX control using MFC and derived from COleControl can invoke the FireError() member function.

Lesson Summary

This lesson described how to deal with COM error codes, with particular attention paid to creating and using HRESULT codes in servers such as ActiveX controls. HRESULT codes are not handles, but simply 32bit values containing four bit fields. The bit fields describe an error's severity, its facility category, and its code within a category. Negative HRESULT codes indicate failure, and generate exceptions in MFC and Visual Basic clients. The HRESULT and SCODE type definitions are interchangeable under 32bit Windows programming.

By encapsulating an HRESULT value, the _com_error support class can make dealing with COM errors easier. A _com_error object is often passed as the exception parameter to a catch block.

Although developers of COM servers have an obligation to ensure that their products return accurate and expressive error codes, most client applications cannot or do not receive those codes. This will undoubtedly change in the future.

The stock Error event provides a COM component with the means of immediately notifying the client when an error occurs. The Error event should be used in asynchronous programming situations, where it is neither possible nor practical to use returned HRESULT values to indicate errors.



Microsoft Press - Desktop Applications with Microsoft Visual C++ 6. 0. MCSD Training Kit
Desktop Applications with Microsoft Visual C++ 6.0 MCSD Training Kit
ISBN: 0735607958
EAN: 2147483647
Year: 1999
Pages: 95

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net