[Previous] [Contents] [Next]

Rich Error Information

HRESULTs do not work well for applications written in Java or Visual Basic because these high-level languages hide HRESULT values from the programmer. In Java and Visual Basic, the parameter marked with the [out, retval] IDL attributes replaces the HRESULT as the method's return value. In these languages, errors are dealt with as exceptions, not return codes. Therefore, Visual Basic raises an exception if the HRESULT returned by a method indicates an error. You can trap this error in Visual Basic using an On Error statement. Unfortunately, Visual Basic programmers do not have much information to work with when they try to respond to an error message represented by an HRESULT. To overcome these limitations and to provide richer error information for components of all languages, COM+ offers the IErrorInfo, ICreateErrorInfo, and ISupportErrorInfo interfaces.

When a run-time error occurs inside a component, a COM+ exception can be propagated back to the client application. In order for the COM+ exception-handling mechanism to work, both the client and the component must support COM+ exceptions. The component implements the ISupportErrorInfo interface and, when an error occurs, makes use of the system implementation of the ICreateErrorInfo interface. The component then returns a standard error code to the client, whereupon the client can retrieve the additional information configured in the system error object.

The ISupportErrorInfo Interface

A component that wants to provide rich error information to its client must first implement the ISupportErrorInfo interface to indicate this capability. The ISupportErrorInfo defines only one method, as shown in the following IDL definition:

interface ISupportErrorInfo : IUnknown
{
    HRESULT InterfaceSupportsErrorInfo([in] REFIID riid);
}

Both the Visual Basic and Java VMs query for ISupportErrorInfo and then call the ISupportErrorInfo::InterfaceSupportsErrorInfo method for every custom interface exposed by the component and accessed by the client program. Some C++ client programs, such as those written using MFC, also do this. It is easy to implement this interface to return S_OK or S_FALSE when a client attempts to determine whether your component offers rich error information for the methods of a specific interface. The following code fragment shows a sample implementation:

HRESULT CInsideCOM::InterfaceSupportsErrorInfo(REFIID riid)
{
    if(riid == IID_ISum)
        return S_OK;
    else
        return S_FALSE;
}

The ICreateErrorInfo Interface

During method execution, a component that supports the COM+ exception mechanism calls the CreateErrorInfo function to instantiate a generic error object. The CreateErrorInfo function returns an ICreateErrorInfo interface pointer. The methods of the ICreateErrorInfo interface set the rich error information describing the HRESULT value that will be returned by the component. The following code shows the IDL definition of the ICreateErrorInfo interface:

interface ICreateErrorInfo : IUnknown
{
    HRESULT SetGUID([in] REFGUID rguid);
    HRESULT SetSource([in] LPOLESTR szSource);
    HRESULT SetDescription([in] LPOLESTR szDescription);
    HRESULT SetHelpFile([in] LPOLESTR szHelpFile);
    HRESULT SetHelpContext([in] DWORD dwHelpContext);
}

After setting the desired method error information using the ICreateErrorInfo interface, the component calls the SetErrorInfo function. SetErrorInfo assigns the error object to the current thread of execution, enabling the client program to retrieve the error information using the GetErrorInfo function. The only problem is that SetErrorInfo requires an IErrorInfo interface pointer and the component currently has an ICreateErrorInfo interface pointer. The component can obtain the correct interface pointer for SetErrorInfo simply by calling the QueryInterface method on the ICreateErrorInfo interface pointer to request IErrorInfo; the standard error object implements both interfaces. The following code shows a special implementation of the ISum::Sum method that returns rich error information if either parameter is negative:

HRESULT CInsideCOM::Sum(int x, int y, int* retval)
{
    // If either x or y is negative, return error info.
    if(x < 0 || y < 0)
    {
        // Create generic error object.
        ICreateErrorInfo* pCreateErrorInfo;
        CreateErrorInfo(&pCreateErrorInfo);

        // Set rich error information.
        pCreateErrorInfo->SetDescription(
            L"Negative numbers not allowed.");
        pCreateErrorInfo->SetGUID(IID_ISum);
        pCreateErrorInfo->SetSource(L"Component.InsideCOM");

        // Exchange ICreateErrorInfo for IErrorInfo.
        IErrorInfo* pErrorInfo;
        pCreateErrorInfo->QueryInterface(IID_IErrorInfo, 
            (void**)&pErrorInfo);

        // Make the error information available to the client.
        SetErrorInfo(0, pErrorInfo);

        // Release the interface pointers.
        pErrorInfo->Release();
        pCreateErrorInfo->Release();

        // Return the actual error code.
        return E_INVALIDARG;
    }

    // Business as usual...
    *retval = x + y;
    return S_OK;
}

Visual Basic and Java automatically map the error information interfaces offered by COM+ to their language-based exception-handling mechanisms. This mapping enables a client program running in Visual Basic to trap a COM+ error as if it were a regular Visual Basic exception, as shown here:

Private Sub Command1_Click()
    On Error GoTo MyHandler
    Dim Test As New Component.InsideCOM
    Print Test.Sum(4, 3) ' Everything is OK.
    Print Test.Sum(-1, 5) 'Raises exception.
    Exit Sub

MyHandler: 'Exception transfers execution control here.
    MsgBox Err.Description 'Displays: Negative numbers 
                           '          not allowed.
End Sub

Obtaining Error Information

A component written in Java can return rich error information by throwing an instance of ComFailException, a class provided by the com.ms.com package. The ComFailException class is derived from the com.ms.com.ComException abstract class, which in turn inherits from the java.lang.RuntimeException class. This class hierarchy means it is legal to use an instance of the ComFailException class anywhere an instance of the RuntimeException class is required, such as with the throw statement. The com.ms.com.ComSuccessException class (also derived from ComException) is used to return an HRESULT of S_FALSE. The Java component shown in Listing 6-1, with the code that throws the exception in boldface, behaves identically to the C++ component above.

insidecom.java

/**
 * This class is designed to be packaged with a COM DLL output 
 * format. The class has no standard entry points other than the 
 * constructor. Public methods are exposed as methods on the 
 * default COM interface.
 * @com.register ( clsid=DB379AA0-D639-11D2-BB51-006097B5EAFC, 
       typelib=DB379AA1-D639-11D2-BB51-006097B5EAFC )
 */
import com.ms.com.*;

/**
 * @com.register ( clsid=2652CA66-D6CF-11D2-BB51-006097B5EAFC, 
       typelib=2652CA65-D6CF-11D2-BB51-006097B5EAFC )
 */
public class InsideCOM implements component.ISum
{
    // TODO: Add additional methods and code here
    public int Sum(int x, int y)
    {
        if(x < 0 || y < 0)
            throw new ComFailException(0x80070057, 
                 "Negative numbers not allowed");

        return x + y;
    }

    /**
     * NOTE: To add auto-registration code, refer to the 
     * documentation on the following method:
     *   public static void onCOMRegister(boolean unRegister) {}
     */
}

Listing 6-1. The insidecom.java source file showing how a Java component can raise COM+ exceptions.

The IErrorInfo Interface

Client applications that want to obtain rich error information use the methods of the IErrorInfo interface. The IDL definition of the IErrorInfo interface is shown here:

interface IErrorInfo : IUnknown
{
    HRESULT GetGUID([out] GUID * pGUID);
    HRESULT GetSource([out] BSTR * pBstrSource);
    HRESULT GetDescription([out] BSTR * pBstrDescription);
    HRESULT GetHelpFile([out] BSTR * pBstrHelpFile);
    HRESULT GetHelpContext([out] DWORD * pdwHelpContext);
}

Before it calls the methods of the IErrorInfo interface, the client must determine whether the component reporting the error provides rich error information. To do this, the client calls the QueryInterface method to request the ISupportErrorInfo interface. If this call is successful, the client calls ISupportErrorInfo::InterfaceSupportsErrorInfo to verify that rich error information is available for the methods of the interface in question. If this call is successful, the client can call the GetErrorInfo function to obtain an IErrorInfo pointer to the object containing the rich error information. The methods of the IErrorInfo interface shown in the preceding code can now be called to obtain the error information. Later, the IErrorInfo and ISupportErrorInfo interface pointers must be released. The following code fragment shows how a C++ client program can obtain the error information thrown by our sample Java component:

hr = pSum->Sum(-2, 3, &sum);
if(SUCCEEDED(hr))
    cout <<"Sum = " << sum << endl;
else
{
    ISupportErrorInfo* pSupportErrorInfo;
    if(SUCCEEDED(pSum->QueryInterface(IID_ISupportErrorInfo,
        (void**)&pSupportErrorInfo)))
    {
        if(pSupportErrorInfo->InterfaceSupportsErrorInfo(
            IID_IInsideCOM) == S_OK)
        {
            IErrorInfo* pErrorInfo;
            GetErrorInfo(0, &pErrorInfo);

            BSTR description;
            pErrorInfo->GetDescription(&description);
            wprintf(L"HRESULT = %x, Description: %s\n", hr, 
                description);
            SysFreeString(description);

            pErrorInfo->Release();
        }
        pSupportErrorInfo->Release();
    }
}