3D PLM Enterprise Architecture |
Middleware Abstraction |
Managing Errors Using HRESULTCreating, analyzing, enriching, and reacting to errors using HRESULT |
Technical Article |
AbstractThis 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] |
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:
iFirstOperand
and iSecondOperand
are the two
integers to addoSum
is the computed sum.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:
CAAAddingMsgKey
is the key of the message associated with
this errorCAAAddingErrorCatalog
is the message catalog containing this
key and the associated message.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:
Even if the failure can be fixed, such as just ignoring an item in a loop, there are problems you may want to log because something wrong happens that needs to be investigated. In these cases, use CATSysLogError.
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]
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]
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]
This includes:
This is fully described in [2].
[Top]
The error must be returned using macros to make the error manager aware of it:
CATReturnSuccess
states that no error has occurred, and
returns S_OK
... return CATReturnSuccess; ... |
CATReturnFailure
states that an error has occurred, and
returns E_UNEXPECTED
, but no error class instance is associated
with the error
... return CATReturnFailure; ... |
CATReturnError
states that an error has occurred, returns E_FAIL
,
and enables an error class instance to be associated with the error, along
with information such as an error message, an the error type
... return CATReturnError(pError); ... |
where pError
is the pointer to the error class instance
created for this error.
[Top]
If the error is a major one, you must trace it. This is fully described in [2].
[Top]
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; ...
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]
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 ... |
Even if the failure can be fixed, such as just ignoring an item in a loop, there are problems you may want to log because something wrong happens that needs to be investigated. In these cases, use CATSysLogError.
Note: there is not always a CATError when a method you call fails.You must check the CATError availabilty
[Top]
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]
This is fully described in [2]. If the error is a major one, you must trace it. This is also described in [2].
[Top]
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]
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]
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]
This is fully described in [2].
[Top]
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]
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]
[1] | CAA V5 Error Processing Rules |
[2] | Using an Error Class with HRESULTs or Exceptions |
[3] | Managing Errors Using Exceptions |
[Top]
Version: 2 [Nov 2001] | Enhanced with new error rules |
Version: 1 [Mar 2000] | Document created |
[Top] |
Copyright © 2000, Dassault Systèmes. All rights reserved.