3D PLM Enterprise Architecture

Middleware Abstraction

Managing Errors Using Exceptions

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

Abstract

This article explains how to create your own error objects and messages, and how to process the errors returned by the methods you call using exceptions. Using exceptions is not recommended.


Understanding Error Management Using Exceptions with an Example

Suppose you code a method in a matrix class to compute a 3x3 matrix inversion. If the matrix to invert has a null determinant, you cannot invert the matrix, and you can report the error by throwing an exception. The code that will call your method will catch the exception and will process it. Since the null determinant depends on the input matrix, you will use the CATInputError class, and create a message such as the one with the key MatrixERR_3241 you will associate with the class, this message being located, for example, in the file CAAMatrixInputError.CATNls, as follows:

MatrixERR_3241.Request = "Cannot invert the matrix /p1" 
MatrixERR_3241.Diagnostic = "The matrix determinant is null.\n
Its determinant value is: /p2\n
This value is less than the lower limit for\n
significant real numbers\n
and is considered as null.";
MatrixERR_3241.Advice = "Check the /p1 matrix";

The matrix inversion method can be as follows:

#include "CATInputError.h"
#include "CAAMatrix3x3.h"

CAAMatrix3x3 * CAAMatrix3x3::Invert()
{
  double det = Determinant();
  if (fabs(det) <= _epsilon)
  {
    CATInputError* pError = new CATInputError("MatrixERR_3241",        // Message key
                                              "CAAMatrixInputError");  // Message catalog
    char * pId = GetMatrixId();
    // Prepare parameters for the message
    CATUnicodeString Param1(pId);
    CATUnicodeString Param2;
    Param2.BuildFromNum(det);
    // Assign parameters to the error
    pError->SetNLSParameters(2, &Param1, &Param2);
    pError->SetType(CATErrorTypeCritical);

    CATThrow(pError);
  }
...
}

The determinant value computed by the method Determinant is compared to the smallest acceptable value _epsilon and if it is smaller than this value, a CATInputError class instance is created. Note that the error class constructor uses the error identifier and the error message file as arguments. The matrix identifier is retrieved, and is converted into a CATUnicodeString instance, along with the actual determinant value, and assigned as the parameters of the error message. They will value /p1 and /p2 in the error message respectively. The error is also assigned the type CATErrorTypeCritical. Then the exception is thrown.

The code to write when you use this method could be the following:
...
#include "CATInputError.h"
#include "CAAMatrix3x3.h"

HRESULT CAAMyClass::Compute()
{
  CAAMatrix3x3 * volatile pMat    = NULL;
  CAAMatrix3x3 * volatile pInvMat = NULL;
  CATTry
  {
    pMat = new CAAMatrix3x3();
    ...
    pInvMat = pMat->Invert();
    ...
  }
  ...

Note that the macro CATThrow does not appear explicitly in the method Compute, but in the Invert method called from Compute. You could have used the Invert method several times in your CATTry block without to worry about the error it can throw. You simply need to catch these errors once in CATCatch macros.

When catching an exception you must try in the following order:
  ...
  CATCatch(CATInputError, pOccurringError)
  {
    if (NULL != pOccurringError)
    {
      ... // Clean
      const char * pMsgId = pOccurringError->GetMsgId();
      if (0 == strcmp("MatrixERR_3241", pMsgId))                       // Repair
      {
        pOccurringError->Release();
        pOccurringError = NULL;
        ... // Repair the error, for example, by modifying pMat, and go on
        return CATReturnSuccess;
      }
      else if (0 == strcmp("MatrixERR_3242", pMsgId))                  // Rethrow as is
      {
        ... // Clean
        CATRethrow;
      }
      else if (0 == strcmp("MatrixERR_3243", pMsgId))                  // Enrich
      {
        pOccurringError->Release();
        pOccurringError = NULL;
        ... // Clean
        CATInputError * pMyError = new CATInputError("AnalysisERR_1367", "CATAnalysisInputError");
        ... // Set message parameters
        CATThrow(pMyError);
      }
      else                                                             // Log and rethrow
      {
        if (NULL == pInvMat)
        {
          ... // Clean
          ::CATSysLogAbend("This pointer should never be NULL");
          CATRethrow;
        }
      }
    }
    else
    {
       ... // Process exceptions with NULL pointers ??
    }
  }
  CATCatch(CATSystemError, pOccurringError)
  {
    ...
  }
  ...
  CATCatchOthers
  {
    ...
  }
  CATEndTry
}

[Top]

Exceptions Basics

When a failure happens in a method, such as a division by zero, or if input parameters are out of the scope of the method, exceptions can be used. To do this, instantiate an existing error class by passing an appropriate error message key and the error message catalog file name as parameters of the error class constructor. If the message has parameters, assign them values of matching variables from your method, and throw the exception. The exception is thrown to an exception manager with the error class instance attached. Exception managers are associated with CATTry blocks and catch the exceptions thrown along with error class instances and parameters, and processes them.

To make your applications platform-independent, macros are supplied to implement exceptions rather than using the C++-native try, catch, and throw expressions. You will have to split your code into blocks and encapsulate each block using the macros CATTry and CATEndTry. Then you or the methods you call inside the CATTry block use the macro CATThrow whenever an exception is to be thrown. Finally you use the macros CATCatch and CATCatchOthers to write the error processing code.

Below is an example of such blocks along with the error macros:

...
CATTry
{
... // a part of your code which contains CATThrow
}
CATCatch (CATInputError, pError) {
... // code processing the input errors
}
CATCatch (CATResourceError, pError)
{
... // code processing the resource errors
}
CATCatchOthers
{
... // code processing the other errors
}
CATEndTry
...

Errors are described using five classes, CATSystemError, CATResourceError, CATInputError, CATInternalError, and CATCOMErrors which all derive from the abstract class CATError [2]. Each CATCatch block applies to exceptions to which a given error class is attached. The CATCatchOthers block applies to exceptions that are not processed by the preceding CATCatch blocks in the same CATTry/CATEndTry block.

The code encapsulated by the CATTry macro is your main code to execute. If during its execution, or during the execution of one of the methods or functions called from it, an exception occurs and is thrown using the macro CATThrow, and if it is not caught by the code you call, the appropriate CATCatch block embedded in the same CATTry/CATEndTry block is executed, depending on the error class. Then the process resumes after the macro CATEndTry.

Each CATCatch macro has the error class it processes as first argument. If the error is an instance of the specified class, or if it is an instance of one of its derived classes, the CATCatch block for this class is executed. The second argument contains a pointer to the error class instance conveyed with the exception.

[Top]

What Is a CAA V5 Exception Made Of?

The main characteristics of the error management using exceptions is that an error detected by a method is not returned to its caller, but thrown and may traverse several caller layers before being caught by a caller upwards in the call stack. An exception is built using the following:

Once thrown, the exception traveses the call stack until it finds an appropriate CATCatch block. Any caller can catch the exception to analyze it and take the appropriate decision. Refer to Catching and Processing Exceptions.

[Top]

Throwing an Exception

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

[Top]

Initializing the Error Data

This includes:

This is fully described in [2].

[Top]

Throwing the Exception

Exceptions are thrown using the CATThrow macro and an error class instance. Use CATThrow as follows:

...
    CATThrow(pError);
...

where pError is the pointer to the error class instance created for this exception.

[Top]

Avoiding Memory Leaks while Throwing an Exception

Memory leaks with exceptions happen when automatic objects are used. They are allocated on the stack during the execution of a method or a function. The compiler generates calls to their constructors at function entry and to their destructors at function exit. Unlike C++, if an exception is thrown (either in the function itself or by some other functions it called), the call to the destructors will be skipped. The memory taken by these objects will automatically be released as the stack is restored but their destructors will not be called. This may have a serious impact if these destructors were used, for example, to release previously AddRef'd objects.

Consider the following example.

...
CATBody * pBody = new Body(); // heap allocation
CATDimBoolean * pBool = new CATDimBoolean();
pBool->Run(pBody);
...

pBody is allocated on the heap, and passed to the Run method.

CATDimBoolean::Run(CATBody * iBody)
{
  CATTry
  {
    // the constructor of CATTopCheck calls iBody->AddRef();
    CATTopCheck Tcheck(iBody); // stack allocation
    Tcheck.Check();            // may throw an exception
  }
  CATCatch(CATError, pError)
  {
    ...
  }
}

The Run method uses iBody to instantiate a CATTopCheck instance as an automatic object on the stack that AddRef's the CATBody. Then the Check method is called, and may throw an exception. In this case, the Tcheck variable is correctly deallocated from the stack, but the CATTopCheck destructor is not called, and the iBody variable is not released. This causes a memory leak.

To avoid memory leaks when such an exception is thrown, declare local variables as volatile beyond the CATTry scope, allocate them on the heap, and explicitly delete or release them when no exception is called, catch the exception and explicitly delete or release them if necessary.

CATDimBoolean::Run(CATBody * iBody)
{
  CATTopCheck * volatile  pTcheck = NULL;
  CATTry
  {
    // the constructor of CATTopCheck calls iBody->AddRef();
    pTcheck = new CATTopCheck(iBody); // heap allocation
    pTcheck->Check();                 // may throw an exception
    delete pTcheck;                   // or Release pTcheck
    pTcheck = NULL;
  }
  CATCatch (CATError, pError)
  {
    if (NULL != pTcheck)
    {
      delete pTcheck;                 // or Release pTcheck
      pTcheck = NULL;
    }
    ...      //Repair, rethrow, or enrich the exception
  }
  ...
}

In other words, avoiding automatic objects prevents from memory leaks with exceptions.

[Top]

Tracing Exceptions

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

[Top]

Documenting Exceptions

...
/**
 ...
 * @exception CATInternalError ComponentERR_1000
 *   The component cannot be created because no default constructor exists
 *   for the requested class
 * @exception CATInternalError ComponentERR_1001
 *   The component cannot be created because the class to instantiate
 *   doesn't exist
 * @exception 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
 * @exception CATInternalError ComponentERR_1003
 *   The memory allocation failed for the component
 ...
 */
ExportedByJS0CORBA virtual void CATInstantiateComponent(const char *iname,
                                                        const IID &iid,
                                                        void **oppv);

which gives after being processed:

public virtual void CATInstantiateComponent(const char *iname,
                                            const IID &iid,
                                            void **oppv);
Throws:
 
Error Class Error Id Error Meaning
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]

Catching and Processing Exceptions

The exception processing is made using the CATTry, CATCatch, CATCatchOthers and CATEndTry macros. If you catch an exception, your strategy of dealing with this error can be constructed knowing that you can:

When catching an exception you must try in the following order:

[Top]

Catching Exceptions

Pay attention that this macro sequence CATTry, CATCatch, CATCatchOthers and CATEndTry is fully respected. If this is not the case, the C++ source generated will be invalid and will not compile. In addition, the error classes used must be one, or derive from one, of the five error base classes [2].

The CATTry macro is used to start a code block to which the exception processing applies. If no error is detected during this code execution, the other macros are skipped and the process resumes after the macro CATEndTry.

Never use a goto or case from a switch statement to enter in a block. The macro CATTry must always be executed to ensure proper error processing.

If errors may be encountered, use the macro CATCatch to process these errors. Use one macro per error class which can potentially occurs. If such an error occurs, the macro CATCatch corresponding to this error class is executed. The process resumes then after the macro CATEndTry, if no other macro CATThrow or CATRethrow is found inside the macro CATCatch.

#include "CATErrorMacros.h"
#include "MyInternalError.h"
...
CATTry
{
  Function1 (x);
  Function2 (y);
  ...
}
CATCatch (MyInternalError, pErrorFound)
{
  if (pErrorFound->GetId() == TheErrorIAmWaitingFor)
  ...
}
CATCatchOthers
{
  ...
}
CATEndTry

The first argument of CATCatch is the error class, and the second is a pointer, valued by the code executed inside the macro CATTry, to the parameters associated with the error.

The macro CATCatchOthers is used to insert code for any error class that is not handled by the macros CATCatch located in the same code block. It must be the last of the CATCatch macro sequence and must be followed by CATEndTry. CATCatchOthers must be used to protect from exception raise. For example, the preceding macro CATCatchOthers could be coded as follows:
...
CATCatchOthers {
... // clean up
cerr << "This error belongs to an unknown class\n;
}
CATEndTry

You can replace CATCatchOthers by CATCatch (CATError, Error_Found) since all errors derive from the class CATError.

Never use a goto or a case from a switch statement to enter in a CATCatch block[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 and Throwing a New Exception

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.

...
  CATCurve * volatile ptr = NULL;
  ...
  CATTry
  {
    ...
    ptr->Intersect(Surf1, Surf2, &Curve);
    ...
  }
  CATCatch(CATError, pError)
  {
    ptr->Release();
    ptr = NULL;
    if (NULL != pError)
    {
      if pError->IsAKindOf(CATInputError::ClassName())
      {
        if (0 == strcmp("CurveERR_1256", pOccurringError->GetMsgId()))
        {
          pError->Release();
          pError = NULL;
          CATInputError * pMyError = new CATInputError("FilletERR_2341", "MsgCatalog");
          ... // Add appropriate parameters, if any
          CATThrow(pMyError);
        }
        ...

[Top]

Rethrowing Exceptions

You can simply resend the error if you cannot do anything, or if your own caller can process it.

To raise again the current exception, use CATRethrow. This is the same as the C++ throw clause with no operand. This macro can only be used inside a CATCatch block.

...
  CATTry
  {
    ...
    ptr->Intersect(Surf1, Surf2, &Curve);
    ...
  }
  ...
  CATCatchOthers
  {
    ... // Clean
    CATRethrow;
  }
CATEndTry

As with CATThrow, the CATTerminate function is called if there is no CATCatch* block to handle this exception.

[Top]

Informing the End User with a Prompt Box

This is fully described in [2].

[Top]

V5 Exceptions Compared with C++ Exceptions

The following table summarizes the differences between the V5 error management and the C++ exceptions

V5 Exceptions C++ Native Exeptions
Error objects must derive from CATError Error objects can be of any type
CATTry try
CATCatch (CATError, pErr)
pErr must be a pointer
catch (CATError, err)
err can be passed as a pointer, by reference or value
CATCatchOthers catch(...)
CATEndTry NA
CATThrow(pErr)
pErr must be a pointer
throw(err)
err can be passed as a pointer, by reference or value
CATRethrow throw
CATDestructOnExit NA
CATTerminate terminate
CATSetTerminate set_terminate
NA unexpected
NA set_unexpected

[Top]


In Short

Failures in methods or global functions can be reported using exceptions. Using the macro CATThrow, an error class instance can be associated with the exception, along with an error identifier and a error message that can include parameters values from the method current state. Exceptions can be traced using the CATSysLogAbend and CATSysLogError macros.

Contratry to returned errors, exceptions can traverse the call stack and can be processed by a caller far away from the callee that throws the exceptions. This caller processes the exception, that is, first analyzes it, an then repairs, enriches, or resends it 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 HRESULT

[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.