Rich Error Information

Team-Fly    

 
.NET and COM Interoperability Handbook, The
By Alan Gordon
Table of Contents
Chapter Eight.  Advanced COM to .NET Interop

Rich Error Information

Most COM programmers know that COM objects return a data structure called an HRESULT to indicate the status of a method call. If the method is successful, the COM object should return S_OK. If the method fails, the COM object can return any number of pre-defined HRESULTS, such as E_FAIL or E_INVALIDARG, or you can use the MAKE_HRESULT macro to create your own error HRESULTs. Although just returning an HRESULT is sufficient in some cases, in many cases, a client will want a textual description of the error, the name of the method that was the source of the error, and perhaps even a link to a help file that contains additional information about the error. A COM object can return this sort of rich error information to a client in two ways: (1) using the EXCEPINFO structure or (2) using COM error objects. The EXCEPINFO structure is used by late-bound clients and is an output parameter on the Invoke method of the IDispatch interface. The fields in this structure are shown in Table 8-1.

Table 8-1. The mapping between fields in an EXCEPINFO structure and .NET exception

EXCEPINFO Field

Value When Set from a .NET Exception

wCode

wReserved

bstrSource

Source property

bstrDescription

Message property

bstrHelpFile

HelpLink property

dwHelpContext

pvReserved

Null

PfnDeferredFillIn

Null

scode

HResult property (protected)

v-table-bound COM objects use COM error objects. A COM error object is an object that implements the IErrorInfo interface. 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); } 

The COM runtime provides methods that you can use to create a COM error object and send it to a client. The COM API also includes other methods that you can use to fetch the COM error object that the COM has set.

Managed objects use managed exception objects (a class that derives from System.Exception ) to return error information to managed clients. The CCW will catch the managed exception and convert it to an EXCEPINFO structure for a late-bound client and to an error object for a v-table-bound client. The mapping between the fields in the .NET exception object and a COM error object is shown in Table 8-2.

Table 8-2. The mapping between fields in a COM error object and a .NET exception

Field in the COM Error Object

Value When Set from a .NET Exception

IErrorInfo->GetDescription()

Message

IErrorInfo->GetSource()

Source

IErrorInfo->GetGUID()

GUID_NULL

IErrorInfo->GetHelpFile()

HelpLink

IErrorInfo->GetHelpContext()

To illustrate the concepts that you just learned, I defined the following exception class:

 public class FinancialException : ApplicationException {     public FinancialException(string message,       double rate,short nMonths) : base(message)     {       mNumMonths=nMonths;       mInterestRate=rate;     }     public double InterestRate     {       get { return mInterestRate; }     }     public short NumMonths     {       get { return mNumMonths; }     }     private double mInterestRate;     private short mNumMonths; } 

This exception class is used to throw errors that result from an improperly specified interest rate or term for a loan. This exception class derives from System.ApplicationException (which, in turn , derives from System.Exception ).

Note

Non-fatal, application-specific exceptions that are thrown from managed applications should derive from System.ApplicationException. This class is provided to differentiate between exceptions defined by user programs versus exceptions defined by the system. When you derive from ApplicationException, you should pass a human-readable message describing the error to the base class constructor. The ApplicationException class uses the HRESULT COR_E_APPLICATION, which has the value 0x80131600.


Next, I added logic to the MonthlyPayment and LoanAmount methods to throw instances of this exception class when certain conditions occur. For instance, the MonthlyPayment method will throw a FinancialException if you specify a number of months for the loan that is less than zero as shown here:

 public decimal MonthlyPayment(short numMonths,     double interestRate, decimal loanAmt) {     double monthlyRate, tempVal;     decimal unRoundedAmt;     if (numMonths <= 0)       throw new FinancialException(           "Invalid number of months",           interestRate,numMonths);     if (interestRate <= 0.0  interestRate >= 100.0)       throw new FinancialException(           "Invalid interest rate",           interestRate,numMonths);     monthlyRate=interestRate/1200;     tempVal=Math.Pow((1+monthlyRate),(double)numMonths);     unRoundedAmt=(decimal)((double)       loanAmt*(monthlyRate*tempVal/(tempVal-1)));     return decimal.Round(unRoundedAmt,2); } 

Visual Basic Client

Let's first look at using .NET exceptions from an unmanaged Visual Basic 6 client. Regardless of whether you are using late binding or early binding, the situation is the same.

 1.  Private Sub Command1_Click() 2.      On Error GoTo errHandler 3.      Dim obj As Object 4.      Dim rslt As Single 5.      Set obj = CreateObject("DotNetFinancial.TimeValue") 6.      rslt = obj.MonthlyPayment(-60, 7, 25000) 7.      Exit Sub 8.  errHandler: 9.      Dim strMsg As String 10.     strMsg = "Source: " & Err.Source & vbCrLf & _ 11.            " Description: " & Err.Description 12.     MsgBox strMsg 13. End Sub 

To catch the managed exception, you use Visual Basic's familiar "on error goto" syntax. The source and description fields of the error object (err) will contain the source and description information from the managed exception object as shown in Table 8-2.

Unmanaged Visual C++ Client

The following code shows how you would catch the exception from a late-bound client that was built with Visual C++. Here you receive the error information from the EXCEPINFO structure that is returned by the Invoke method.

 1.  void CDotNetFinancialClientDlg::OnMonthlyPmtButton() 2.  { 3.      DECIMAL decLoanAmt; 4.      IDispatch *pDispatch; 5.      HRESULT hRes; 6.      DISPID dispid; 7.      OLECHAR *methodName; 8.      EXCEPINFO errorInfo; 9.      UINT intArg; 10.     VARIANT vntArgs[3], vntResult; 11.     DISPPARAMS param; 12. 13.     UpdateData(TRUE); 14. 15.     VarDecFromR8(m_loanAmt,&decLoanAmt); 16.     methodName=L"MonthlyPayment"; 17.     param.cArgs=3; 18.     param.rgvarg=vntArgs; 19.     param.cNamedArgs=0; 20.     param.rgdispidNamedArgs=NULL; 21.     vntResult.vt=VT_R4; 22.     vntArgs[0].vt=VT_R4; 23.     vntArgs[0].fltVal=360000; 24.     vntArgs[1].vt=VT_R4; 25.     vntArgs[1].fltVal=6.00; 26.     vntArgs[2].vt=VT_I2; 27.     vntArgs[2].iVal=-360; 28. 29.     CoInitializeEx(NULL,COINIT_MULTITHREADED); 30.     hRes=CoCreateInstance(CLSID_TimeValue,NULL, 31.       CLSCTX_ALL,IID_IDispatch, 32.       (void **)&pDispatch); 33. 34.     hRes=pDispatch->GetIDsOfNames(IID_NULL, 34.       &methodName,1,GetUserDefaultLCID(),&dispid); 35.     hRes=pDispatch->Invoke(dispid,IID_NULL, 36.       GetUserDefaultLCID(),DISPATCH_METHOD, 37.       &param,&vntResult,&errorInfo,&intArg); 38.     if (hRes==DISP_E_EXCEPTION) 39.     { 40.       CString strMsg; 41.       strMsg.Format("Source: %s \n Description: %s", 42.         CString(errorInfo.bstrSource), 43.         CString(errorInfo.bstrDescription)); 44.       AfxMessageBox(strMsg); 45.     } 46.     m_monthlyPmt=vntResult.decVal; 47.     pDispatch->Release(); 48.     UpdateData(FALSE); 49. } 

I've gone through code like this so many times in this chapter that I won't go line by line through this code. The key lines of code are lines 35 through 37 where I call the Invoke method on IDispatch and lines 38 through 45 where I print the source and description of the error if the Invoke method returns an error. The contents of the EXCEPINFO structure are populated from the managed exception object that was thrown by the managed TimeValue class.

Here is equivalent code using v-table binding and COM error objects:

 1.  void CDotNetClientDlg::OnBnClickedMonthlypayment() 2.  { 3.      HRESULT hRes; 4.      DECIMAL decLoanAmt,decMonthlyPmt; 5.      ITimeValue *pTimeValue; 6.      IErrorInfo *pErrorInfo; 7.      BSTR bstrSource, bstrDescription; 8.      CoInitializeEx(NULL,COINIT_MULTITHREADED); 9.      hRes=CoCreateInstance(CLSID_TimeValue, 10.       NULL,CLSCTX_ALL,IID_ITimeValue, 11.       (void **)&pTimeValue); 12.     VarDecFromR8(360000,&decLoanAmt); 13.     hRes=pTimeValue->MonthlyPayment(360, 14.       -6.75,decLoanAmt,&decMonthlyPmt); 15.     if (FAILED(hRes)) 16.     { 17.       hRes=::GetErrorInfo(0,&pErrorInfo); 18.       pErrorInfo->GetSource(&bstrSource); 19.       pErrorInfo->GetDescription(&bstrDescription); 20.       CString strMsg; 21.       strMsg.Format("Source: %s \n Description: %s", 22.          CString(bstrSource), 23.        CString(bstrDescription)); 24.       AfxMessageBox(strMsg); 25.       ::SysFreeString(bstrSource); 26.       ::SysFreeString(bstrDescription); 27.     } 28.     pTimeValue->Release(); 29.     m_monthlyPmt=decMonthlyPmt; 30.     UpdateData(FALSE); 31.     } 

The key lines of code here are lines 13 and 14 where I call the MonthlyPayment method on the TimeValue object and lines 15 through 27 where I handle errors returned from the method. Notice that lines 18 and 19 call the GetSource and GetDescription methods to retrieve the source and description from the managed exception object.

Using the _Exception Interface

If you use v-table binding to call a managed object from an unmanaged client, and the method raises a managed exception, the CLR will use the managed exception object as the COM error object. This means that the _Exception interface, which contains all of the methods in the System.Exception class, is available to unmanaged code. All you have to do is to QueryInterface on the COM error object for _Exception, and then you can access all of the members of the System.Exception class from your unmanaged code including the stack trace.

To use the _Exception interface from Visual C++, you must first import the type library for mscorlib.dll as follows in stdafx.h :

 #import "C:\WINNT\Microsoft.NET\Framework\v1.0.3705\mscorlib.tlb"   no_namespace named_guids raw_interfaces_only 

The following code shows you how to obtain the _Exception interface from a COM error object and how to use the interface:

 1.  void CDotNetClientDlg::OnBnClickedMonthlypayment() 2.  { 3.      HRESULT hRes; 4.      DECIMAL decLoanAmt,decMonthlyPmt; 5.      ITimeValue *pTimeValue; 6.      _Exception *pException; 7.      IErrorInfo *pErrorInfo; 8.      BSTR bstrSource, bstrDescription, bstrStackTrace; 9.      CoInitializeEx(NULL,COINIT_MULTITHREADED); 10.     hRes=CoCreateInstance(CLSID_TimeValue,NULL, 11.       CLSCTX_ALL,IID_ITimeValue, 12.       (void **)&pTimeValue); 13.     VarDecFromR8(360000,&decLoanAmt); 14.     hRes=pTimeValue->MonthlyPayment(360, 15.       -6.75,decLoanAmt,&decMonthlyPmt); 16.     if (FAILED(hRes)) 17.     { 18.         hRes=::GetErrorInfo(0,&pErrorInfo); 19.         pErrorInfo->GetSource(&bstrSource); 20.         pErrorInfo->GetDescription(&bstrDescription); 21.         pErrorInfo->QueryInterface( 22.          IID__Exception,(void **)&pException); 23.         pException->get_StackTrace(&bstrStackTrace); 24.         pException->Release(); 25.         pErrorInfo->Release(); 26.         CString strMsg; 27.         strMsg.Format( 28.          "Source: %s \n Description: %s \n Stack: %s", 29.         CString(bstrSource), 30.         CString(bstrDescription), 31.         CString(bstrStackTrace)); 32.         AfxMessageBox(strMsg); 33.         ::SysFreeString(bstrSource); 34.               ::SysFreeString(bstrDescription); 35.               ::SysFreeString(bstrStackTrace); 36.     } 37.     pTimeValue->Release(); 38.     m_monthlyPmt=decMonthlyPmt; 39.     UpdateData(FALSE); 40. } 

The key lines of code here are lines 21 and 22 where I use QueryInterface for the _Exception interface and line 23 where I call the get_StackTrace method on the _Exception interface pointer to retrieve the stack trace. Of course, the stack trace only shows the managed code portion of the stack.

Note

Because the Interface name for the System.Exception class is _Exception, the named IID for this interface is IID__Exception with two underscores.



Team-Fly    
Top
 


. Net and COM Interoperability Handbook
The .NET and COM Interoperability Handbook (Integrated .Net)
ISBN: 013046130X
EAN: 2147483647
Year: 2002
Pages: 119
Authors: Alan Gordon

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