Wednesday, April 2, 2008

Turning COM Errors and HRESULTs into .Net Exceptions

.NET will automatically convert an HRESULT into a specific type of Exception based on the HRESULT.  This is fine for many of the built-in HRESULTs closely related to Win32 API like function calls.  In my case though, I wanted to turn an application specific error that occurs in an ATL COM server (ATL3.0 no less!) into a pretty Exception so that managed code would get a useful error message.

To do this:
  1. Create an empty error object via CreateErrorInfo( &pcerrInfo )
  2. Set the description via pcerrInfo->SetDescription().  CreateErrorInfo allocates a pointer to an object that implements ICreateErrorInfo.  SetDescription() is a method defined on ICreateErrorInfo.  Whatever gets set here will be assigned to the .NET Exception.Message property.
  3. call SetErrorInfo(0, perrInfo) where perrInfo is a pointer to IErrorInfo. 
    1. To get perrInfo pointer, call pcerrInfo->QueryInterface(IID_IErrorInfo, (LPVOID*) &perrInfo);
    2. SetErroInfo() sets the error object for the current thread (actually it clears the current error object then sets it to the new one).
  4. after Release()-ing both pointers, return an error HRESULT (e.g., return E_FAIL).
    1. Don't make any other COM calls before returning the HRESULT as these might overwrite the error object just set.
  5. In .NET this will show up as a COMException where COMException.Message = IErrorInfo->GetDescription()

Since this will be used a lot, I put it into a single function that takes a string.  In practice, I immediately return an error HRESULT after calling the function.


void
CSimpleTestObj::AppSetErrorInfo(LPWSTR pszErr)

{

ICreateErrorInfo *pcerrinfo;

IErrorInfo *perrinfo;

HRESULT hr;

hr = CreateErrorInfo(&pcerrinfo); // create generic error object

if
(SUCCEEDED(hr))

{

// set the
text - in .NET this will map to Exception.Message


pcerrinfo->SetDescription(pszErr);

// need to
get IErrorInfo because this is what the .NET code will see


hr =
pcerrinfo->QueryInterface(IID_IErrorInfo, (LPVOID FAR*) &perrinfo);

if
(SUCCEEDED(hr))

{

// SetErrorInfo sets the
error object for the current thread

// .Net will take it,
make an exception,

// then set
ApplicationException.Message = IErrorInfo.GetDescription()


SetErrorInfo(0, perrinfo);


perrinfo->Release();

}

pcerrinfo->Release();

}

}







No comments :

Post a Comment