Page #57 (Reusing Components)


Error Reporting

Recall our implementation of the ISVideo interface method GetSVideoSignalValue. If a member variable exceeded a limit, which could happen only if we had some implementation problem, we returned an SDK-defined error code E_UNEXPECTED. The following code fragment shows the relevant lines:

 // File Vcr.cpp  HRESULT CVcr::GetSVideoSignalValue(long* pRetVal)  {   if (m_nCurCount >= 5 || m_nCurCount < 0) {     return E_UNEXPECTED;    }    ...  } 

If the client receives this error code and runs it through the DumpError method defined in Chapter 2, the error displayed would be catastrophic failure. This error doesn t convey much to either the client or the developer, as it could have been generated by any of the layers between the client and the server (such as the COM library, the COM marshaling mechanism, the ORPC channel, etc.).

Instead of returning such a generic error message, it makes more sense to return an error code that is meaningful in the context of the ISVideo interface alone. This is where the custom HRESULTs (from Chapter 2) come into picture.

A custom HRESULT can be defined in the IDL file using the familiar MAKE_HRESULT macro, as shown here:

 // File Video.idl  cpp_quote("#define VCR_E_INTERNALERROR \    MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, 0x200 + 1)") 

Keyword cpp_quote causes MIDL to emit the specified string into the generated header file.

The GetSVideoSignalValue code can be modified to return this new HRESULT value.

 // File Vcr.cpp  HRESULT CVcr::GetSVideoSignalValue(long* pRetVal)  {   if (m_nCurCount >= 5 || m_nCurCount < 0) {     return VCR_E_INTERNALERROR;    }    ...  } 

The client can now check if the error code is specifically from the object, as follows:

 // File tv.cpp  int main(int /*argc*/, char* /*argv*/[])  {   ::CoInitialize(NULL);    DoIt();    ::CoUninitialize();    return 0;  }  void DoIt()  {   ISVideo* pSVideo = NULL;  HRESULT hr = ::CoCreateInstance(CLSID_VCR, NULL, CLSCTX_ALL,    IID_ISVideo, reinterpret_cast<void**>(&pSVideo));  if (FAILED(hr)) {   DumpError(hr);    return;  }    long val;    hr = pSVideo->GetSVideoSignalValue(&val);    if (SUCCEEDED(hr)) {     cout << "Value: " << val << endl;      return;    }    // The call failed    if (HRESULT_FACILITY(hr) == FACILITY_ITF) {      cout << "ISVideo specific error: 0x" << hex << hr << endl;    }else {     DumpError(hr);    }  } 

Recall from Chapter 2 that a custom HRESULT is meaningful only in the context of the interface. If two interfaces define the same custom HRESULT, it will not cause any problems.

A last bit of information on custom HRESULTs a custom HRESULT need not always return failure codes. It can also return success codes using SEVERITY_SUCCESS.

At this point, I would like to temporarily deviate from the main topic and bring your attention to a bug in our TV client code.

If you observe the code, you will see that we forgot to call Release on the pSVideo interface pointer, not just at one place, but at two places.

Such a programming mistake, however, cannot be completely eliminated, at least for us humans. What would be nice is if the pointer automatically released itself when it goes out of scope.

Writing such a smart pointer class is not that difficult. However, it turns out that ATL has already done this for us. It defines a template called CComPtr that a client can use with minimal code changes. Our revised TV client code, using CComPtr, is as follows:

 // File tv.cpp  ...  #include <atlbase.h>  ...  void DoIt()  {   CComPtr<ISVideo> spSVideo;    HRESULT hr = ::CoCreateInstance(CLSID_VCR, NULL, CLSCTX_ALL,      IID_ISVideo, reinterpret_cast<void**>(&spSVideo));    if (FAILED(hr)) {     DumpError(hr);      return;    }    long val;    hr = spSVideo->GetSVideoSignalValue(&val);    if (SUCCEEDED(hr)) {     cout << "Value: " << val << endl;      return;    }    // The call failed    if (HRESULT_FACILITY(hr) == FACILITY_ITF) {     cout << "ISVideo specific error: 0x" << hex << hr << endl;    }else {     DumpError(hr);    }  } 

The variable spSVideo will release itself, if it is not NULL, when it goes out of scope.

Note that I changed the variable name from pSVideo to spSVideo. This follows the convention I have adopted throughout the book (and in real life) whenever I use a smart pointer.

Visual C++ natively supports a smart pointer class called _com_ptr_t that is similar in functionality to CComPtr. To create a specialized version of an interface, Visual C++ defines a macro called _COM_SMARTPTR_TYPEDEF. The following code, for example, declares the _com_ptr_t specialization ISVideoPtr.

 _COM_SMARTPTR_TYPEDEF(ISVideo, __uuidof(ISVideo)); 

Visual C++ defines _com_ptr_t specialization for many standard interfaces. In addition, it can generate specialization for an interface defined in a type-library using a pre-processor directive called import. The usage of this directive is shown later in the section.


Always use smart pointers to make your code more robust.

We can now return to our main topic of error reporting.

A custom HRESULT by itself is still not very descriptive to the client. What would be nice is if the object returns a descriptive text to the client.

Using C++ style exceptions as a possibility can be ruled out. Recall from Chapter 1 that there is no binary standard for them.

To address this problem, COM defines an interface, IErrorInfo, that the server can use to set extended error information and the client can use to retrieve it. The interface divides the extended error information into five fields:

  • The IID of the interface that sets the error information.

  • The source responsible for setting the error information. This field is typically filled with the class name of the object.

  • The textual description of the error.

  • The Windows help filename, if any, that documents this error.

  • The Windows help context ID for this error.

The server code may implement an object that supports IErrorInfo, populate its fields, and call a COM API SetErrorInfo to set the extended error information. Following is the prototype for SetErrorInfo:

 HRESULT SetErrorInfo([in] DWORD dwReserved,    [in] IErrorInfo* pEI); 

COM provides a default implementation of IErrorInfo that can be instantiated using the COM API function CreateErrorInfo. Following is its prototype:

 HRESULT CreateErrorInfo([out] ICreateErrorInfo** ppCEI); 

The API returns an object that supports an additional interface, ICreateErrorInfo. This interface defines methods to populate the above-mentioned fields. Note that not all fields need to be populated. The server may choose to leave some fields empty.

The following server side code fragment shows how the default implementation of IErrorInfo can be used to set the error information:

 void CVcr::Error(REFIID iid, LPOLESTR pszDesc)  {   // Create error information    CComPtr<ICreateErrorInfo> spCEI;    HRESULT hr = ::CreateErrorInfo(&spCEI);    _ASSERT (SUCCEEDED(hr));    hr = spCEI->SetGUID(iid);    _ASSERT (SUCCEEDED(hr));    hr = spCEI->SetSource(OLESTR("My VCR"));    _ASSERT (SUCCEEDED(hr));    hr = spCEI->SetDescription(pszDesc);    _ASSERT (SUCCEEDED(hr));    // Make error information available to the client    CComPtr<IErrorInfo> spEI;    hr = spCEI->QueryInterface(IID_IErrorInfo, (void**) &spEI);    _ASSERT (SUCCEEDED(hr));    hr = ::SetErrorInfo(0, spEI);    _ASSERT (SUCCEEDED(hr));  } 

The following code fragment shows how our server client can use the above method to set the error information:

 HRESULT CVcr::GetSignalValue(long* pRetVal)  {   if (m_nCurCount >= 5 || m_nCurCount < 0) {     Error(IID_IVideo, OLESTR("Count is out of range"));      return VCR_E_INTERNALERROR;    }    ...  } 

With this logic, the client can now obtain the extended error information using the COM API GetErrorInfo. Following is its prototype:

 HRESULT GetErrorInfo([in] DWORD dwReserved,    [out] IErrorInfo** ppEI); 

While calling GetErrorInfo will return some error information, the client unfortunately does not know if it is some stale information left over as a result of calling some other COM API or some other interface method on some other object.

To ensure that the error information originated as a result of calling the specific interface on the object, COM mandates that the client first ask the object if it supports error information by querying it for a standard interface IsupportErrorInfo. Following is its definition:

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

Method InterfaceSupportsErrorInfo can be used to further confirm if the object supports error information on a specific interface.

The following TV client code fragment illustrates the use of obtaining extended error information:

 void DoIt()  {   ...    // The call on ISVideo failed    ...    // Check if the object supports extended error information    CComPtr<ISupportErrorInfo> spSEI;    hr = spSVideo->QueryInterface(IID_ISupportErrorInfo,      (void**) &spSEI);    if (FAILED(hr)) {     return; // error info not supported    }    // Check if error info is available for ISVideo interface    hr = spSEI->InterfaceSupportsErrorInfo(IID_ISVideo);    if (S_OK != hr) {     return; // error info not supported on ISVideo    }    // Get the error information and display    CComPtr<IErrorInfo> spEI;    hr = ::GetErrorInfo(0, &spEI);    if (FAILED(hr)) {     return; // failed for some obscure reason    }    CComBSTR bsDesc;    hr = spEI->GetDescription(&bsDesc);    if (FAILED(hr)) {     return; // failed for some obscure reason    }    USES_CONVERSION;    cout << "Extended error info: " << W2T(bsDesc) << endl;  } 

Obtaining descriptive text requires a lot of coding. Fortunately, the code can easily be encapsulated in a utility function. However, Visual C++ has added native COM support to deal with this situation in the form of a class called _com_error. The following code fragment shows our revised DumpError logic using this class:

 void DumpError(_com_error& e)  {   if (HRESULT_FACILITY(e.Error()) == FACILITY_ITF) {     cout << "ISVideo specific error: 0x"        << hex << e.Error() << endl;    }else {     cout << e.ErrorMessage() << endl;    }    // Extended error, if any    _bstr_t bsDesc = e.Description();    if (NULL != (LPCTSTR) bsDesc) {     cout << "Extended error info: "        << (LPCTSTR) bsDesc << endl;    }  } 

To use the native COM support, a function that fails typically throws an exception of type _com_error. Using the exception mechanism, our main entry function can be rewritten as follows:

 int main(int /*argc*/, char* /*argv*/[])  {   ::CoInitialize(NULL);    try {     DoIt();    }catch(_com_error& e) {     DumpError(e);    }    ::CoUninitialize();    return 0;  } 

What about the actual DoIt code?

In order to fully utilize the potential of native COM support, the compiler has added a new directive called import that takes a filename as an argument. The filename could be a type-library file or an executable file containing a type library.

 #import "Video.tlb" no_namespace 

Upon this directive, the compiler reads the type library and generates wrapper classes for all the interfaces encountered. These wrapper classes implement methods that throw a _com_error exception on failures.

Attribute no_namespace turns off the namespace scoping on the generated classes. Consult the online documentation for other attributes.

With the native COM support enabled, our DoIt method can be rewritten as follows:

 void DoIt()  {   ISVideoPtr spSVideo(__uuidof(VCR));    long val = spSVideo->GetSVideoSignalValue();    cout << "Value: " << val << endl;  } 

It is worth comparing this code with the earlier written code that was based on raw method calls. The code based on native COM support is very small and easy to maintain.

We are done (finally). Now we know how to develop COM-based servers and clients, and we have picked up a few good tips on using ATL as well as native COM support provided by Visual C++.


COM+ Programming. A Practical Guide Using Visual C++ and ATL
COM+ Programming. A Practical Guide Using Visual C++ and ATL
ISBN: 130886742
Year: 2000
Pages: 129 © 2008-2017.
If you may any questions please contact us: