from
http://progtutorials.tripod.com/COM.htm
- COM¡¯s Error Handling
- HRESULT
- How Does Server Creates Error Object
- ATL¡¯s Support on Creating Error Object
- How Does Client Retrieve Error Object
- How Does VB Client Retrieve Error Object
- Smart Pointer Class and Exception _com_error
- Error Information in ATL¡®s Automation
- Debugging EXE Server
1 COM¡¯s Error Handling
A COM server has two ways to generate error information:
- HRESULT - most COM Server methods and COM library API functions return a HRESULT carrying basic error information. When you get a HRESULT error code you can look up its description from VC¡¯s Tools menu. You can also call Win32 function FormatMessage with the error code, which return the description. Error information carried by HRESULT is very limited.
- IErrorInfo object - Extra error information can be passed using error object which implements IErrorInfo interface.
For non-dispatch function calls, IErrorInfo object is retrived by client through API functions. For a call to IDispatch::Invoke, IErrorInfo is returned through EXCEPINFO structure.
For server-side programming which is mainly generating the error information, the overloaded CComCoClass::Error methods simplifies the process to deal with IErrorInfo error object.
For client-side programming which is to retrieve error information, VC¡¯s smart pointer classes and the _com_error exception which they throw simplifies the error processing with HRESULT and IErrorInfo, while class COleDispatchDriver and the COleDispatchException which it throws simplifies error processing with EXCEPINFO.
Note that the COM server does not throw exceptions. It is the helper classes such as smart pointers and COleDispatchDriver which throw exceptions.
2 HRESULT
HRESULT may take many possible values:
- S_OK - Success. Value is 0.
- S_FALSE - Function works fine, and the returned boolean is false. Value is 1.
- E_NOINTERFACE - The QueryInterface function did not recognize the requested interface. The interface is not supported.
- E_NOTIMPL - The function contains no implementation.
- E_FAIL - An unspecified failure has occurred.
- E_OUTOFMEMORY - The function failed to allocate necessary memory.
- E_POINTER - Invalid pointer.
- E_INVALIDARG - One or more arguments are invalid.
- E_UNEXPECTED - A catastrophic failure has occurred.
- E_HANDLE - Invalid handle.
- E_ABORT - Operation aborted.
- DISP_E_BADPARAMCOUNT - The number of elements provided to DISPPARAMS is different from the number of parameters accepted by the method or property.
- DISP_E_BADVARTYPE - One of the parameters in rgvarg is not a valid variant type.
- DISP_E_EXCEPTION - The application needs to raise an exception. In this case, the structure passed in pExcepInfo should be filled in.
- DISP_E_MEMBERNOTFOUND - The requested member does not exist, or the call to Invoke tried to set the value of a read-only property.
- DISP_E_NONAMEDARGS - This implementation of IDispatch does not support named parameters.
- DISP_E_OVERFLOW - One of the parameters in rgvarg could not be coerced to the specified type.
- DISP_E_PARAMNOTFOUND - One of the parameter DISPIDs does not correspond to a parameter on the method. In this case, puArgErr should be set to the first parameter that contains the error.
- DISP_E_TYPEMISMATCH - One or more of the parameters could not be coerced. The index within rgvarg of the first parameter with the incorrect type is returned in the puArgErr parameter.
- DISP_E_UNKNOWNINTERFACE - The interface identifier passed in riid is not IID_NULL.
- DISP_E_UNKNOWNLCID - The member being invoked interprets string parameters according to the LCID, and the LCID is not recognized. If the LCID is not needed to interpret parameters, this error should not be returned.
12 DISP_E_PARAMNOTOPTIONAL - A required parameter was omitted.
3 How Does Server Creates Error Object
In addition to returning a HRESULT indicating error, a coclass method can use an error object to pass extra error information to the client. An error object implements interface IErrorInfo and ISupportErrorInfo and is created and maintained by the COM run time. The server can not create an error object in its own address space, instead it calls a COM API function to let COM to create one error object for it.
If a coclass wants to pass to client an error object, firstable it has to let the client know (when being asked) that it is able to create IErrorInfo object, by inheriting from interface ISupportErrorInfo. In ISupportErrorInfo¡¯s only method InterfaceSupportsErrorInfo, the server should reply to the client whether the given interface provides error object:
STDMETHODIMP CCar::InterfaceSupportsErrorInfo(REFIID riid)
{
static const IID* arr[] =
{
&IID_IEngine,
&IID_IRegistration,
&IID_IStatus
};
for (int I = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
if (InlineIsEqualGUID(*arr[i], riid))
return S_OK;
}
return S_FALSE;
}
When a coclass method encounters something abnormal that it wants to tell the client, it calls API function CreateErrorInfo to ask COM to create an error object. This API function returns to the server an ICreateErrorInfo pointer.
Interface ICreateErrorInfo has the following methods:
- SetDescription - returns error description as a BSTR
- SetGUID - returns the guid of the interface that defined the error
- SetHelpFile - returns the path to the corresponding help file.
- SetHelpContext - returns the context ID indentifying a particular entry in the help file.
- SetSource - returns the ProgID of the object that caused the error.
Then the server will call ICreateErrorInfo¡¯s methods to place error information into the error object. Then the server will query the ICreateErrorInfo interface to get the error object¡¯s IErrorInfo interface pointer. Then it will call API function SetErrorInfo passing the IErrorInfo pointer, to link the error object with the current thread:
if(...) // Something is wrong!
{
ICreateErrorInfo * pCreate;
HRESULT hr = CreateErrorInfo(&pCreate);
BSTR bstr = SysAllocString(L"Error info created in Client!");
pCreate->SetDescription(bstr);
IErrorInfo * pError;
hr = pCreate->QueryInterface(IID_IErrorInfo, (void **)&pError);
SetErrorInfo(0, pError); // linking the error object with the thread
return E_FAIL;
} COM run time maintains one error object for each thread. A lately linked error object will overwrite the previously one. Actually both the client and the server can create, link or retrieve the error object, although in real life it is always the server which creates and links the error object, and the client which retrieves.
4 ATL¡¯s Support on Creating Error Object
As said before, an ATL object inherits from CComCoClass, which has a set of overloaded Error methods, which wrap function calls such as CreateErrorInfo, SetDescription, QueryInterface and SetErrorInfo and greatly simplifies your job. A coclass can simply make a call to one of these methods and return the return value to the client. One of them looks like:
static HRESULT WINAPI Error
(
LPCOLESTR lpszDesc,
const IID& iid = GUID_NULL, // Interface defining the error
HRESULT hRes = 0
); If hRes is nonzero, Error returns hRes. If it is zero, Error returns DISP_E_EXCEPTION.
A coclass method can simply say:
if(...) // error happens
{
return Error(¡°Not enough memory!¡±);
}
5 How Does Client Retrieve Error Object
When client checks the returned HRESULT and finds out that something is wrong in the server, if he wants more details, he will first query the present interface for interface ISupportErrorInfo. If the query returns the interface, he then calls ISupportErrorInfo::InterfaceSupportsErrorInfo to check whether the interface which returns error supports error object. If yes, the client knows that there must be an error object waiting for him to retrieve. So he calls COM API function GetErrorInfo to get an IErrorInfo interface pointer of the error object. Then he can call the methods to retrieve the error information.
If the server method returns S_OK, even if he creates an error object, it will not be retrieved and checked.
if(FAILED(hr))
{
ISupportErrorInfo* pSupport;
hr = m_pAccount->QueryInterface(IID_ISupportErrorInfo, (void**) &pSupport);
if (SUCCEEDED(hr))
{
hr = pSupport->InterfaceSupportsErrorInfo(IID_IError);
if (SUCCEEDED(hr))
{
IErrorInfo * pError;
hr = GetErrorInfo(0, &pError);
BSTR bstr;
hr = pError->GetDescription(&bstr);
USES_CONVERSION;
::MessageBox(NULL, OLE2CT(bstr), "Client", MB_OK);
SysFreeString(bstr);
}
}
} IErrorInfo has the following methods:
GetDescription - returns error description as a BSTR
GetGUID - returns the guid of the interface that defined the error
GetHelpFile - returns the path to the corresponding help file.
GetHelpContext - returns the context ID indentifying a particular entry in the help file.
GetSource - returns the ProgID of the object that caused the error.
6 How Does VB Client Retrieve Error Object
Private Sub btnDoIt_Click()
On Error GoTo OOPS ¡® Acts like a C++ try block.
¡® Normal code, in which exception may be thrown
Exit Sub
OOPS:
¡® Error handling code
Resume Next ¡® Resume normal execution at next line of throw point
End Sub
7 Smart Pointer Class and Exception _com_error
While CComCoClass simplifies creating an error object at the server side, VC¡¯s smart pointer class wraps all jobs to retrieve the error object at the client side, including
- Check whether the returned HRESULT indicates error;
- Find out whether the coclass supports IErrorInfo;
- Retrive the error information.
As soon as the smart pointer class finds out that the returned HRESULT indicates an error, it will throw an exception of type _com_error containing error information stored in the error object. If server has actually put extra error information into the error object, a call to _com_error¡¯s method Description will return the error string which had been set into the error object by the server with ICreateErrorInfo::SetDescription. Otherwise the method will simply return NULL.
_com_error has some useful methods such as:
- ErrorMessage: calls Win32 FormatMessage function, which returns a simple system message description such as ¡°Exception happened¡±.
- ErrorInfo: returns the IErrorInfo interface pointer.
- Description: calls IErrorInfo::GetDescription and returns a _bstr_t.
Because of the flexibility and power smart pointer classes and _com_error offer, it is always good to use them instead of a great number of lower-level calls.
try
{
m_spEngine->GetTemp();
}
catch(_com_error & ex)
{
char buf[80];
sprintf(buf, ¡°Error happened in IEngine::GetTemp. %S¡±,
ex.Description());
msg(buf);
}
8 Error Information in ATL¡®s Automation
VC¡¯s smart pointer classes can NOT invoke the IDispatch interface.
In ATL, IDispatch::Invoke is implemented by IDispatchImpl<>. When you call a server method through IDispatch::Invoke, the implementation will check for the returned HRESULT. If it indicates an error (e.g. E_FAIL), it will retrieve the error object, put the error information into structure EXCEPINFO, and return it to the client. If the called method did not create the error object, the fields of EXCEPINFO will be NULL.
EXCEPINFO is defined as follow:
typedef struct tagEXCEPINFO
{
WORD wCode;
WORD wReserved;
BSTR bstrSource;
BSTR bstrDescription;
BSTR bstrHelpFile;
DWORD dwHelpContext;
PVOID pvReserved;
HRESULT (__stdcall *pfnDeferredFillIn)(struct tagEXCEPINFO *);
SCODE scode;
} EXCEPINFO, * LPEXCEPINFO; Example of retrieving error information from structure EXCEPINFO:
int main(int argc, char* argv[])
{
CoInitialize(NULL);
IAny * pAny;
IDispatch * pDisp;
HRESULT hr;
hr = CoCreateInstance(CLSID_Any, NULL, CLSCTX_SERVER, IID_IAny, (void **) &pAny);
hr = pAny->QueryInterface(IID_IDispatch, (void**) &pDisp);
LONG lResult;
char buf[80];
EXCEPINFO * pExcepInfo = new EXCEPINFO;
DISPPARAMS params;
params.rgvarg = new VARIANTARG[2];
params.rgvarg[0].vt = VT_I4 | VT_BYREF;
params.rgvarg[0].plVal = &lResult;
params.rgvarg[1].vt = VT_I4;
params.rgvarg[1].lVal = 1234L;
params.cArgs = 2;
params.cNamedArgs = 0;
hr = pDisp->Invoke(
1, // disp ID for method Test
IID_NULL, // reserved for future use
LOCALE_SYSTEM_DEFAULT, // locale context
DISPATCH_METHOD, // context of the Invoke call
¶ms, // DISPPARAMS structure used to convey parameters
NULL, // VARIANTARG *, used to receive result
pExcepInfo, // EXCEPTION *, used to hold exception info
NULL // The index of the first error argument
); // in rgvarg VARIANTARG array
if (FAILED(hr))
{
sprintf(buf, "Error description: %S \n\n",
excepInfo.bstrDescription);
printf(buf);
printf("IDispatch:Invoke failed\n");
}
sprintf(buf, "2 x 1234 = %d\n\n", lResult);
printf(buf);
CoUninitialize();
return 0;
}
9 Debugging EXE Server
For DLL servers, because they are in process, they can be debugged as normal in-process code. For EXE servers:
- Open the server project in VC, set a break point, start debug by pressing "F5". This will start the server.
- Run the client program. When the call to the method of the server containing the break point is made, the control will step into the server program and stop at the break point.
SeriousMoin v1 (koMoinMoin 1.0a4 Modified)