3D PLM Enterprise Architecture

Middleware Abstraction

Managing Errors Using HRESULT

Creating, analyzing, enriching, and reacting to errors using HRESULT
Technical Article

Abstract

This article explains how to report a failure to calling methods by returning a HRESULT to which an error class and an error message is associated, how to process the errors returned by the methods you call, and how to convert exceptions into HRESULT errors. The error management described here complies with the CAA V5 Error Processing Rules [1]


Understanding Error Management Using HRESULT with an Example

Assume you have to create a method that adds two integers the end user has entered in a dialog box and returns their sum. Assume also that the intended scope of the method is to work correctly when both integers are greater than or equal to zero. Requesting to add negative integers is out of the scope of the method, and should then be processed as an error. The code for such a method could be:

HRESULT CAAClass::Add(int iFirstOperand, int iSecondOperand, int * oSum)
{
  if (iFirstOperand<0 || iSecondOperand<0)     // Out of the method scope
  { // Instantiate an error class instance with a message
    CATResourceError * pError = new CATResourceError("CAAAddingMsgKey",
                                                     "CAAAddingErrorCatalog"); 
    // Prepare parameters for the message
    CATUnicodeString Param1, Param2;
    Param1.BuildFromNum(iFirstOperand);
    Param2.BuildFromNum(iSecondOperand);
    // Assign parameters to the error
    pError->SetNLSParameters(2, &Param1, &Param2);
    // Inform caller that method fails, and pass the error object
    return CATReturnError(pError);
  }
  if (NULL != oSum)
  {
    *oSum = iFirstOperand + iSecondOperand;
    return CATReturnSuccess;      // Inform caller that method succeeds
  }
  else
    return CATReturnFailure;
}

where:

Note that the method returns a HRESULT, and not the computed sum which is passed as an output parameter, and that this HRESULT value is built using the macros CATReturnError when the method fails, and CATReturnSuccess when the method executes successfully.

Let's analyze what happens when one or both input parameters are less than zero. First, an instance of the CATResourceError class, supplied by the System framework, is created. The CATResourceError class constructor has two parameters:

The message takes the two operands as parameters. According to the message processing rules, these parameters must first be converted into CATUnicodeString instances, and then attached to the error using the SetNLSParameters method, before the error be returned by the CATReturnError macro. The message extracted from the CAAAddingErrorCatalog.CATNls file could be:

...
CAAAddingMsgKey = "First operand /p1 and//or second operand /p2 is negative.\n Select only positive values";
...

Now let's look at the calling method. It should test the returned value to know whether the called method executes well or not. If it fails, the error class instance associated with the error can be retrieved from the error manager thanks to the GetLastError method. Depending on the error class, the action to perform can be one of the following:

When a method you call fails you must try in the following order:

Note: there is not always a CATError when a method you call fails.You must check the CATError availabilty

...
  int a, b, aplusb;
...
  HRESULT rc = ptr->Add(a, b, &aplusb);
  ptr->Release();
  ptr = NULL;
  if (FAILED(rc))
  {       // Retrieve the last error from the error manager
    CATError * pOccurringError = CATError::CATGetLastError(rc);
    if (NULL != pOccurringError)
    {
      if (0 != pOccurringError->IsAKindOf(CATInputError::ClassName()))       // Repair
      {
        ... // Repair the error
        pOccurringError->Release();
        pOccurringError = NULL;
        ... // and go on
        return CATReturnSuccess;
      }
      else if (0 != pOccurringError->IsAKindOf(CATSystemError::ClassName())) // Return as is
      {
        return CATReturnError(pOccurringError);
      }
      else if (0 != pOccurringError->IsAKindOf(CATInternalError::ClassName()))  // Enrich
      {
        ... // Analyze the error
        pOccurringError->Release();
        pOccurringError = NULL;
        CATInternalError * pMyError = new CATInternalError("MsgKey", "MsgCatalog");
          ... // Add appropriate parameters, if any
        return CATReturnError(pMyError);
      }
      else                                                                   // Log and return
      {
        if (NULL == pPointer)
        {
          ... // Clean
          CATSysLogAbend("This pointer should never be NULL");
          return CATReturnedError(pOccurringError);
        }
      }
    }
  }
  ... // The Add method succeeds, and the method goes on normally
  return CATReturnSuccess;
}

Note that the different processing cases rely on whether the error class derives from a given class. Other criteria are available, such as the error type, the error message identifier, or the error identifier [2].

[Top]

What Is a CAA V5 Error Made Of?

The main characteristics of the error management is that an error detected by a method is returned to its caller, and can then be propagated from callee to caller upwards in the call stack. An error is built using the following:

Once returned, the error is held by a specific object, called the error manager. The caller can retrieve the error from the error manager to analyze it and take the appropriate decision. Refer to Receiving and Error from a Called Method.

[Top]

Returning an HRESULT with an Error

When developing your objects and client applications, you will need to create your own errors. This includes mainly:

The first three items are extensively described in [2] and are just recalled below.

[Top]

Initializing the Error Data

This includes:

This is fully described in [2].

[Top]

Returning the Error

The error must be returned using macros to make the error manager aware of it:

[Top]

Tracing the Error

If the error is a major one, you must trace it. This is fully described in [2].

[Top]

Documenting the Error

Documentation of a method's returned values.

  /**
   ...
   * @return S_OK
   *   The component is successfully created and the interface pointer is successfully returned
   * @return E_FAIL
   *   The component cannot be created, or the interface query failed. The
   *   following error classes and error identifiers and severity can be
   *   associated with this error.
   * @error CATInternalError ComponentERR_1000
   *   The component cannot be created because no default constructor exists
   *   for the requested class
   * @error CATInternalError ComponentERR_1001
   *   The component cannot be created because the class to instantiate
   *   doesn't exist
   * @error CATInternalError ComponentERR_1002
   *   The component was successfully created, but the interface query fails,
   *   whether the component doesn't implement the interface, or the IID passed is invalid.
   *   An IUnknown pointer to the component is returned
   * @error CATInternalError ComponentERR_1003
   *   The memory allocation failed for the component
   ...
   */
  virtual HRESULT CreateInstance(IUnknown* iUnkOuter, REFIID iIid, void ** oObject) = 0;

which gives after being processed:

public virtual HRESULT CreateInstance( IUnknown* iUnkOuter, REFIID iIid, void ** oObject ) = 0;
...
Returns:
S_OK
The component is successfully created and the interface pointer is successfully returned
E_FAIL
The component cannot be created, or the interface query failed. The following error classes and error identifiers and severity can be associated with this error.
Returned Errors:
Error Class Error Id Description
CATInternalError ComponentERR_1000 The component cannot be created because no default constructor exists for the requested class
CATInternalError ComponentERR_1001 The component cannot be created because the class to instantiate doesn't exist
CATInternalError ComponentERR_1002 The component was successfully created, but the interface query fails, whether the component doesn't implement the interface, or the IID passed is invalid. An IUnknown pointer to the component is returned
CATInternalError ComponentERR_1003 The memory allocation failed for the component

[Top]

Receiving and Error from a Called Method

When you call a method, you should always check the HRESULT value returned. To determine whether an error occurred, use always the SUCCEEDED or FAILED macros, and never check the returned value directly.

...
HRESULT rc = ptr->Method(arg1, arg2, arg3);
if (SUCCEEDED(rc))
  ...
else
  ...
When a method you call fails you must try in the following order:

Note: there is not always a CATError when a method you call fails.You must check the CATError availabilty

[Top]

The Error Comes from a Returned HRESULT

This is the most common way you will deal with. An example of such an error analysis and process is shown in Understanding Error Management Through an Example. In this case, you should retrieve the error using the CATGetLastError method with one parameter, that is, the return code. You can use IsAKindOf and all the other methods to analyze the error.

...
HRESULT rc = ptr->Method(arg1, arg2, &arg3);
if (FAILED(rc))
{       // Retrieve the last error from the error manager
  CATError * pOccurringError = CATError::CATGetLastError(rc);
  ptr->Release();
  ptr = NULL;
  ...  // Analyze the error

[Top]

Analyzing the Error

This is fully described in [2]. If the error is a major one, you must trace it. This is also described in [2].

[Top]

Enriching the Error

You often can add information about the error that happens because you have a better knowledge of what you request from the method you call than this method itself. For example, when you ask a method to compute the intersection of two surfaces, this method can simply return, in case of error, that the surfaces cannot be intersected, or that the intersection operator fails. You can replace this error in the calling context, that is for example to create a fillet or a chamfer, release the error class instance, and create a new one that better explains what really happens and that provide useful information to your own caller, or to the end user.

...
HRESULT rc = ptr->Intersect(Surf1, Surf2, &Curve);
ptr->Release();
ptr = NULL;
if (FAILED(rc))
{       // Retrieve the last error from the error manager
  CATError * pOccurringError = CATError::CATGetLastError(rc);
  if (NULL != pOccurringError)
  {
    if pOccurringError->IsAKindOf(CATInputError::ClassName())
    {
      if (0 == strcmp("ERR_1256", pOccurringError->GetMsgId()))
      {
        ... // Clean
        pOccurringError->Release();
        pOccurringError = NULL;
        CATInputError * pMyError = new CATInputError("MyErrorERR_4563", "CATMyMsgCatalog");
        ... // Add appropriate parameters, if any
        return CATReturnError(pMyError);
      }
...

[Top]

Resending the Error

You can simply resend the error if you cannot do anything else, or if your own caller is at the approriate level to process it. You cannot reuse the same error class instance while changing the error message. If you want to change the error message, you need to release the error, create a new one with the appropriate error message as described in Enriching the Error.

...
HRESULT rc = ptr->Method(arg1, arg2, &arg3);
ptr->Release();
ptr = NULL;
if (FAILED(rc))
{       // Retrieve the last error from the error manager
  CATError * pOccurringError = CATError::CATGetLastError(rc);
  if (NULL != pOccurringError)
  {
    if (0 == strcmp("IdIDontCareAboutERR_1000", pOccurringError->GetMsgId()))
      return CATReturnError(pOccurringError);
    ...

[Top]

Releasing the Error

You can release the error if it has no meaning for your caller, but nevertheless return a failure, or return successfully if its a warning or information you take into account, but that is useless or meaningless for your caller.

...
HRESULT rc = ptr->Method(arg1, arg2, &arg3);
if (FAILED(rc))
{       // Retrieve the last error from the error manager
  CATError * pOccurringError = CATError::CATGetLastError(rc);
  ptr->Release();
  ptr == NULL;
  if (NULL != pOccurringError)
  {
    if (0 == strcmp("IdNobodyCaresAboutERR_1000", pOccurringError->GetMsgId()))
    {
      pOccurringError->Release();
      pOccurringError = NULL;
      return CATReturnFailure;
    }
    else if (CATErrorTypeInformation == pOccurringError->GetType())
    {
      pOccurringError->Release();
      pOccurringError = NULL;
      ... // maybe you have something to do in-between
      return CATReturnSuccess;
    }
    else
      ...

[Top]

Informing the End User with a Prompt Box

This is fully described in [2].

[Top]

Converting a Caught Exception into a HRESULT

When your method is located at the framework border, you should use the CATTry and CATCatch macros to enclose the code portions respectively dedicated to execute your code and to process the errors. You cannot retrieve a HRESULT value as a return code of the method in the CATTry section. Then you simply must catch this exception and retrieve the error class instance. If you can't repair the error, you must stop the exception propagation, convert this exception into a HRESULT, and attach the exception error class instance to the failing return.

HRESULT MyClass::MyMethod()
{
  ...
  CATTry
  {
    ...
    Ptr->Add(a, b, &aplusb); // Internal method without HRESULT that throws exceptions
    ...
  }
  CATCatch (CATError, pError)
  {
    ... // Clean
    if ( NULL != pError )
    {
      const char * pMsgId = pError->GetMsgId();
      if     (0 == strcmp("ErrorICanRepairERR_1000", pMsgId))      // Repair
        ...   // Repair the error
      else if (0 == strcmp("ErrorIReturnAsIsERR_1000", pMsgId))    // Return as is
        return CATReturnError(pError);
      else if (0 == strcmp("ErrorIEnrichERR_1000", pMsgId))        // Enrich
      {
        pError->Release();
        pError = NULL;
        CATResourceError * pMyError = CATResourceError("MsgId", "MsgCatalog");
        ... // Possibly fill in the message with parameters
        return CATReturnError(pMyError);
      }
      else if (0 == strcmp("MajorErrorILogandReturnAsIsERR_1000", pMsgId))  // Log and return
      {
        ::CATSysLogAbend("Pertinent Log Message");
        return CATReturnError(pError);
      }
    }
    else
      return CATReturnFailure;                                                 // Return
  }
  CATEndTry
}

For example here, and exception can be thrown inside the CATTry block of a method located at the border of a framework. The CATCatch block will catch this exception. After a possible clean-up, if an non null error instance is caught, process as for any returned error, that is try to repair, or return as is, enrich, log, or return the failure without error.

[Top]


In Short

Failures in methods or global functions are reported using the HRESULT value returned. Using the macro CATReturnError, an error class instance can be associated with the returned failure, along with an error identifier and a error message that can include parameters values from the method current state. Errors can be traced using the CATSysLogAbend and CATSysLogError macros.

Returned errors must be processed by the caller, that is, first analyzed, an then repaired, enriched, or resent as is. If the method is located at the appropriate level, a prompt can be issued to the end user.

If a method located at the framework border catches exceptions thrown by its callees, it must convert them into HRESULT associated with the same or another error class instance.

[Top]


References

[1] CAA V5 Error Processing Rules
[2] Using an Error Class with HRESULTs or Exceptions
[3] Managing Errors Using Exceptions

[Top]


History

Version: 2 [Nov 2001] Enhanced with new error rules
Version: 1 [Mar 2000] Document created
[Top]

Copyright © 2000, Dassault Systèmes. All rights reserved.