Developers Workshop to COM and ATL 3.0

 < Free Open Study > 


That brings us to the end of our examination of CoRectangle. Hopefully you now have a better idea how the ATL framework is composing your objects. To close this chapter, we will switch gears completely, and revisit CComCoClass<>. As you recall, this class defines a number of overloaded Error() methods that can be used to send out COM exceptions.

You are aware by now that each method contained in a COM interface should return the de facto HRESULT. When a COM client has obtained some interface pointer off your coclass, the HRESULT is often tested to determine the success or failure of the method invocation. The HRESULT is useful in that a COM client can quickly determine if a given interface method responded as hoped:

// The SUCCEEDED and FAILED macros test for the // most significant bit of the HRESULT. HRESULT hr; hr = pUnk->QueryInterface(IID_IDraw, (void**)&pDraw); if(SUCCEEDED(hr)) { // Things worked so start drawing. pDraw->Draw(); pDraw->Release(); } pUnk->Release();

When a method does fail, it would be helpful to explain to the client exactly what went wrong. For example, assume our CoRectangle was able to return a text string describing why the request to render itself failed. Furthermore, what if the offending interface method was able to return to the client a path to a Windows help file, the ProgID of the failing COM object, and more? This very sort of rich error handling is an intrinsic service of the COM architecture.

This facility is often termed COM exceptions and is supported by three standard interfaces and three COM library functions. Some are used by the coclass to raise the error; others are used by the client to catch the error. As a COM developer, you may be tempted to devise your own set of custom error interfaces. For example, you might have a custom interface that always returns a single text string describing what caused a given method to fail. You may then configure your COM clients to ask for this custom interface whenever they read a failed HRESULT. While this is a valid approach, the problem is that your custom error interface is unique to your given solution(s), and perhaps even specific to a given language.

COM offers a standard and generic mechanism to return useful error information to COM clients. As this approach is well known and established through a set of standard interfaces and COM library calls, each COM-aware language is able to respond to these exceptions in their respective language mapping in a location-transparent manner. In other words, COM exceptions are the error reporting mechanism that every COM language mapping understands without any infrastructure work on your part.

Raising a COM exception using ATL is as simple as calling one of the Error() methods you have inherited from CComCoClass<>. However, before we see just how easy this can be, we will begin by examining what a raw C++ coclass would need to do in order to support standard COM error handling. We will then see how Visual Basic and C++ clients catch the exception, and then see how ATL makes throwing a COM exception a piece of cake.

Standard Error Interfaces and COM Library Functions

To support COM exceptions, three standard interfaces have been defined in <oaidl.idl>:

Standard COM Interface

Meaning in Life

ISupportErrorInfo

This interface is implemented by the coclass. A client may obtain a pointer to this interface and determine which interfaces supported by the coclass are capable of returning rich error information.

ICreateErrorInfo

The COM object that wishes to send out rich error information uses this interface to describe the error that occurred.

IErrorInfo

This interface is used by the recipient of the error. From an IErrorInfo pointer, a client is able to interpret the COM exception.

To work with these standard error interfaces, we have three related COM library functions at our disposal:

COM Library Function

Meaning in Life

CreateErrorInfo()

This function is used to create a COM exception. The function returns a pointer to a "live" ICreateErrorInfo interface, from which a coclass may begin describing the error itself.

SetErrorInfo()

Once the error has been defined, this function is used to send the error to the current thread of execution (i.e., back to the client).

GetErrorInfo()

Used by the client to "catch" the COM exception, and discover what went wrong.

Configuring a Coclass to Support COM Exceptions in Raw C++

For the sake of argument, assume we have written CoRectangle using straight C++, and avoided (for some bizarre reason) the ATL framework. We might have the following initial definition:

// A CoRectangle à la C++. class CoRectangle : public IDraw { public: CoRectangle() { m_ref = 0;} // IUnknown implementation... // IDraw implementation... private: ULONG m_ref; };

If we wish CoRectangle to support COM exceptions, the first step is to add ISupportErrorInfo to our inheritance chain:

// A CoRectangle à la C++ supporting COM exceptions. class CoRectangle : public IDraw, public ISupportErrorInfo { public: CoRectangle() { m_ref = 0;} // IUnknown implementation... // IDraw implementation... // ISupportErrorInfo implementation... private: ULONG m_ref; };

ISupportErrorInfo is used by a COM client for exactly one reason: determining which interfaces supported on the coclass are able to return COM exceptions. As a coclass developer, you are able to specify which interfaces will participate in rich error information. This may be each and every interface, or a subset of all possible interfaces. This standard COM interface contains one method. Here is the IDL:

// Clients make use of this interface to discover which interfaces supported // by the coclass provide rich error information. [ object, uuid(DF0B3D60-548F-101B-8E65-08002B2BD119)] interface ISupportErrorInfo: IUnknown { HRESULT InterfaceSupportsErrorInfo([in] REFIID riid); };

The implementation of InterfaceSupportsErrorInfo() is trivial. Once you decide which interfaces will be providing rich error information to COM clients, test the incoming IID and return S_OK or E_FAIL accordingly. Thus, to let the world know that IDraw supports rich error information when implemented by CoRectangle:

// Return S_OK if the client asks for an interface providing COM exceptions. STDMETHODIMP CoRectangle::InterfaceSupportsErrorInfo(REFIID riid) { if(riid == IID_IDraw) return S_OK; return E_FAIL; }

Realize, of course, that when you inform a COM client that a given interface can return COM exceptions, you will want to throw your error from within a method of the given interface. For CoRectangle, this is simple, as we only have the Draw() method to contend with. Also realize that a coclass supporting numerous interfaces can specify any number of interfaces from within InterfaceSupportsErrorInfo(). Recall the original C++ CoCar developed in Chapter 3. If we wish to return COM exceptions from IEngine and IStats but not ICreateCar, we may implement InterfaceSupportsErrorInfo() as so:

// Specifying multiple interfaces that are capable of throwing COM // exceptions. STDMETHODIMP CoCar::InterfaceSupportsErrorInfo(REFIID riid) { if((riid == IID_IEngine) || (riid == IID_IStats)) return S_OK; return E_FAIL; }

Throwing a COM Exception

When a coclass has determined that some error of interest has occurred, and wishes to inform the client of the situation, the method issuing the error will make use of the CreateErrorInfo() COM library function. Assume that the Draw() method of CoRectangle is attempting to allocate a ton of memory during the scope of the function. Perhaps this memory is needed to render the image in an offscreen buffer. If this memory allocation fails, CoRectangle wishes to raise a COM exception that informs the client about the specifics of the error. Here is a possible implementation:

// The Draw method attempts to allocate lots of memory. // If it fails, we will return E_FAIL to the client and issue a COM exception, // otherwise return S_OK and all is fine... STDMETHODIMP CoRectangle::Draw() { HRESULT hr; bool b = false; // Go attempt to grab a ton of memory. b = GetTonsOfMemory(); if(b == false) // A SNAFU has occurred. { ICreateErrorInfo *pCER; // Go create an "error object" and set a description of the error. if(SUCCEEDED(CreateErrorInfo(&pCER))) pCER->SetDescription(L"I can't get that much memory!!"); // Ask the error object for the IErrorInfo interface. IErrorInfo *pEI; pCER->QueryInterface(IID_IErrorInfo, (void**)&pEI); // Now send the error object back to the current thread. SetErrorInfo(NULL, pEI); // Clean up our end. pCER->Release(); pEI->Release(); return E_FAIL; } else // No error, so draw a rectangle. GoDrawARectangle(); return S_OK; }

The sequence of events is very boilerplate. A method determines if it needs to raise a COM exception, and if so, it creates a COM error object using the CreateErrorInfo() COM library function. If this call is successful, the fetched ICreateErrorInfo interface may be used to establish the information that identifies the error. ICreateErrorInfo has a number of mutator functions which may be called to fully define the error in question (note that you are not required to set each item of the error object). Here is the IDL:

// ICreateErrorInfo allows a coclass to define the scope of a COM exception. [ object, uuid(22F03340-547D-101B-8E65-08002B2BD119)] interface ICreateErrorInfo: IUnknown { // IID of the offender. HRESULT SetGUID( [in] REFGUID rguid ); // ProgID of the offender. HRESULT SetSource( [in] LPOLESTR szSource ); // Custom string describing what happened. HRESULT SetDescription( [in] LPOLESTR szDescription ); // Path to any help file. HRESULT SetHelpFile( [in] LPOLESTR szHelpFile ); // Help context identifier. HRESULT SetHelpContext( [in] DWORD dwHelpContext); };

Once the error has been described, we need to raise the error using SetErrorInfo(). Note that we must ask our error object for its IErrorInfo interface, as this interface will be used by the client to catch the exception.

Catching a COM Exception: A C++ Client

Assume we have a C++ client that has created a CoRectangle object, obtained a pointer to IDraw, and is attempting to draw accordingly. Being a good COM citizen, the client tests the HRESULT for success or failure. If the returned HRESULT is set to a failure, the C++ client may go through the following steps to catch the COM exception:

The IErrorInfo interface has methods that complement ICreateErrorInfo. Rather than a set of mutator methods, IErrorInfo holds the accessors for the COM exception. Here is the IDL:

// IErrorInfo contains all the information thrown by the coclass. [ object, uuid(1CF2B120-547D-101B-8E65-08002B2BD119)] 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 ); };

Now, if our C++ client has detected that the Draw() method has failed, it may catch the exception as so (see Figure 7-12 for output):

Figure 7-12: C++ client catching our COM exception.

// Get the exception thrown by the coclass. HRESULT hr; hr = pDraw->Draw(); if(FAILED(hr)) { ISupportErrorInfo *pISER; // Does CoRectangle have COM exceptions for me? hr = pDraw->QueryInterface(IID_ISupportErrorInfo, (void**)&pISER); if(SUCCEEDED(hr)) { // Does IDraw have COM exception support? if(SUCCEEDED(pISER->InterfaceSupportsErrorInfo(IID_IDraw))); // Great! Get the exception. IErrorInfo *pEI; if(SUCCEEDED(GetErrorInfo(NULL, &pEI))) { BSTR desc; pEI->GetDescription(&desc); char buff [80]; WideCharToMultiByte(CP_ACP, NULL, desc, -1, buff, 80, NULL, NULL); cout << desc << endl; SysFreeString(desc); pEI->Release(); } } pISER->Release(); }

Again, the code used by a COM client is very boilerplate in nature. The client tests the HRESULT for a failed invocation. If something went wrong, the client first asks the object if it supports COM exceptions by querying for ISupportErrorInfo. If that call is successful, the client then determines if the offending interface supports COM exceptions by calling InterfaceSupportsErrorInfo(). If that is also successful, the client catches the exception using the GetErrorInfo() COM library call, and calls methods off the fetched IErrorInfo interface.

To be sure, writing C++ error handling code can be a drag, given the amount of repeat code on both ends. To help streamline your code, you would do well to create global helper functions that you can call whenever you detect a non-successful HRESULT (client side) or need to throw the error (server side).

For example, we could create a function called ParseError() to help the C++ client deal with the COM exception. Whenever a COM method fails, send in the offending interface pointer and its associated IID. This function could then do all the grunt work of determining if the interface supports error information, and dump the exception to a file, list box, message box, or whatever mechanism you require. You will have a chance to do this very thing in your next lab.

Catching a COM Exception: A Visual Basic Client

As you may suspect, catching a COM exception in Visual Basic is substantially easier than in C++, because that VB includes an intrinsic Err object, which may be used to extract the details of the COM exception. Before we examine the Err object, however, we need to learn how VB handles errors as a whole. Generally speaking, VB error trapping happens on the method level. Thus, if we wish to equip a function to respond to any possible error, we begin by defining an error label within the function using the On Error GoTo syntax:

' Generic VB error handling. ' Private Sub btnDoIt_Click() On Error GoTo OOPS ' Write your VB code here. If an error occurs, the flow of logic ' will jump to the label below. Exit Sub ' Error trap OOPS: ' Process the error here using the Err object. Resume Next End Sub

Once you have defined the label to catch the error, you need to be sure to explicitly tell the flow of logic to exit the subroutine once the function is about to exit successfully, or you will fall into the error trap again! Also notice the optional Resume Next syntax that informs VB to continue executing the subroutine, as the error has been caught.

Within the scope of the error label you will want to make use of the intrinsic Err object to extract the information about the error, including any COM exceptions thrown by a COM object. The Err object contains a small set of properties which, as you may have guessed, map to the same methods of IErrorInfo. Armed with this information, here is some simple VB client-side code that can catch the COM exception thrown by CoRectangle and displays it in a message box (see Figure 7-13):

Figure 7-13: VB client catching our COM exception.

' Trap and use the COM exception à la VB. ' Private Sub btnDoIt_Click() On Error GoTo OOPS ' Create your CoRectangle Dim r as New CoRectangle ' Throws an exception if the object cannot grab enough memory. r.Draw Exit Sub ' Error trap OOPS: ' Process the error here using the Err object. MessageBox Err.Description, , "Message from the CoRectangle" Resume Next End Sub

ATL's Support for ISupportErrorInfo

Now that you have seen what it takes to configure a raw C++ server to send out COM error objects, let's see what ATL can do to help ease the necessary COM goo. When you are inserting a new coclass with the ATL Object Wizard, one of the options you have is to allow ATL to provide an implementation of ISupportErrorInfo for you. This option is available from the Attributes tab:

Figure 7-14: Specifying ATL support for COM exceptions.

If you have legacy ATL objects that were not initially equipped with this option, it is very easy to retrofit your coclass. To do so, let's see what ATL will do for you when you select the Support ISupportErrorInfo checkbox. First off, your new coclass will derive from ISupportErrorInfo as well as the necessary framework templates and your initial [default] interface:

// The Object Wizard automatically adds support for ISupportErrorInfo if // asked to do so... class ATL_NO_VTABLE CCoRectangle: public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass< CCoRectangle, &CLSID_CCoRectangle >, public ISupportErrorInfo, public IDraw { ... };

Next, you will find that your COM map has been updated, which allows a COM client to retrieve a pointer to ISupportErrorInfo:

// The wizard also automatically updates your COM map. BEGIN_COM_MAP(CCoRectangle) COM_INTERFACE_ENTRY(IDraw) COM_INTERFACE_ENTRY(ISupportErrorInfo) END_COM_MAP()

Finally, ATL will implement the sole method of ISupportErrorInfo on your behalf. Initially, InterfaceSupportsErrorInfo() will only return S_OK for your initial [default] interface. As you add more interfaces to your coclass, you will have to come back to this implementation and add an entry to the IID array by hand. Here is the initial code:

// ATL's implementation of InterfaceSupportsErrorInfo() performs a lookup // using an extendable array of IIDs. STDMETHODIMP CCoRectangle::InterfaceSupportsErrorInfo(REFIID riid) { static const IID* arr[] = { &IID_IDraw }; for (int i=0; i < sizeof(arr)/sizeof(arr[0]); i++) { if (InlineIsEqualGUID(*arr[i],riid)) return S_OK; } return S_FALSE; }

ATL's implementation takes an incoming GUID and performs an inline comparison between it and each member of the static IID array. To illustrate extending this array, assume that your CoRectangle also supports the IShapeEdit interface, and you wish to allow clients to receive COM exceptions from a valid IShapeEdit pointer:

// Extending the initial implementations. STDMETHODIMP CCoRectangle::InterfaceSupportsErrorInfo(REFIID riid) { static const IID* arr[] = { &IID_IDraw, &IID_IShapeEdit }; for (int i=0; i < sizeof(arr)/sizeof(arr[0]); i++) { if (InlineIsEqualGUID(*arr[i],riid)) return S_OK; } return S_FALSE; }

ATL's Support for Raising COM Exceptions

As we learned earlier in this chapter, CComCoClass<> defines a number of overloaded Error() methods, which may be used by your ATL COM objects whenever they wish to throw a COM exception. The implementation of each Error() method delegates to an ATL helper function named AtlReportError(); however, the set of parameters will depend on how fully you wish to define the error. Here are the prototypes:

// ATL allows you to throw a COM exception with a single function call. template <class T, const CLSID* pclsid = &CLSID_NULL> class CComCoClass { public: ... static HRESULT WINAPI Error(LPCOLESTR lpszDesc, const IID& iid = GUID_NULL, HRESULT hRes = 0); static HRESULT WINAPI Error(LPCOLESTR lpszDesc, DWORD dwHelpID, LPCOLESTR lpszHelpFile, const IID& iid = GUID_NULL, HRESULT hRes = 0); static HRESULT WINAPI Error(UINT nID, const IID& iid = GUID_NULL, HRESULT hRes = 0, HINSTANCE hInst = _Module.GetResourceInstance()); static HRESULT WINAPI Error(UINT nID, DWORD dwHelpID, LPCOLESTR lpszHelpFile, const IID& iid = GUID_NULL, HRESULT hRes = 0, HINSTANCE hInst = _Module.GetResourceInstance()); static HRESULT WINAPI Error(LPCSTR lpszDesc, const IID& iid = GUID_NULL, HRESULT hRes = 0); static HRESULT WINAPI Error(LPCSTR lpszDesc, DWORD dwHelpID, LPCSTR lpszHelpFile, const IID& iid = GUID_NULL, HRESULT hRes = 0); ... };

To throw a COM exception from within the ATL framework is a no-brainer. Simply call the Error() method when some condition fails. If we were to rewrite CoRectangle::Draw() using ATL's Error methods, we could save ourselves the trouble of having to create the error object, establish the error context, and send the error to the calling thread:

// The Error method creates and sends the exception in a single call. STDMETHODIMP CCoRectangle::Draw() { HRESULT hr; bool b = false; // Go attempt to grab a ton of memory. b = GetTonsOfMemory(); if(b == false) { // Throw ATL error. Error("I can't get that much memory!!"); return E_FAIL; } else GoDrawARectangle(); return S_OK; }

The VB and C++ clients would continue to work as before.

Note 

If you would like to see the real work behind the Error() methods, look at the code behind AtlSetErrorInfo(), which can be found in <atlbase.h>. This helper function is doing the exact same thing we did when throwing a raw C++ exception.

Lab 7-1: Sending and Receiving COM Exceptions

ATL makes throwing COM exceptions painfully easy, which is a good thing. Rather than having you select the correct Object Wizard option and call an Error() method, in this lab you will take your existing raw C++ CoCar developed in Chapter 4 and add support for COM error handling. This will drill the architecture of COM exceptions into your mind, and let you appreciate the work ATL is doing on your behalf. As well, you will be creating a C++ and Visual Basic client to catch the error you raise from within your coclass.

 On the CD   The solution for this lab can be found on your CD-ROM under:Labs\Chapter 07\RawErrorsLabs\Chapter 07\RawErrors\VB ClientLabs\Chapter 07\RawErrors\CPP Client

Step One: Support ISupportErrorInfo

Open up your CarServerTypeInfo project from Chapter 4 (or make a copy of this project and begin there). We will adding in the necessary infrastructure to support COM error handling from your CoCar object. Start by opening the header file for CoCar, derive from ISupportErrorInfo, and add a QueryInterface() provision for IID_ISupportErrorInfo. Recall that this COM interface allows a client to figure out which interfaces support error information. In your class definition, prototype the InterfaceSupportsErrorInfo() method:

// Any coclass which can throw COM errors must implement ISupportErrorInfo class CoCar : public IEngine, public ICreateCar, public IStats, public ISupportErrorInfo { public: ... // ISupportErrorInfo STDMETHODIMP InterfaceSupportsErrorInfo(REFIID riid); };

Next, let's implement InterfaceSupportsErrorInfo() to specify IEngine is capable of returning COM errors:

// List the interfaces that can return error information here... STDMETHODIMP CoCar::InterfaceSupportsErrorInfo(REFIID riid) { if(riid == IID_IEngine) return S_OK; return E_FAIL; }

Step Two: Throw a COM Exception

Your previous COM clients using CoCar had to monitor when the current speed was greater than the maximum speed and react accordingly. As an example, a VB client using our raw CoCar might have code like the following:

' An ugly form of client-side error handling. ' Private Sub btnSpeed_Click() Set itfEngine = mCar Set itfStats = mCar ' Rev that engine and check for error. Do While itfEngine.GetCurSpeed <= itfEngine.GetMaxSpeed itfEngine.SpeedUp lstSpeed.AddItem itfEngine.GetCurSpeed Loop MsgBox itfStats.GetPetName & " is has blown up! Lead foot!" End Sub

A far better design would be to allow CoCar to let the object inform the client when the engine has exploded through a COM exception. Go to the current implementation of SpeedUp() and throw a COM exception whenever the current speed is greater than the maximum speed. The general flow is as follows:

  1. Call CreateErrorInfo() to receive a pointer to a COM error object via the ICreateErrorInfo interface.

  2. From the acquired interface call any mutator methods (i.e., SetXXX()) to set the information about the error.

  3. Now, ask the error object for the IErrorInfo interface and send the error using SetErrorInfo().

  4. Release all acquired interfaces and return E_FAIL.

Here is some sample code that sets the description of the error:

// Sending an error in straight C++. STDMETHODIMP CoCar::SpeedUp() { m_currSpeed += 10; if(m_currSpeed > m_maxSpeed) { ICreateErrorInfo *pCER; // Go create an 'error object' and set a description of the error. if(SUCCEEDED(CreateErrorInfo(&pCER))) pCER->SetDescription(L"Engine block has exploded!!"); // Ask the error object for the IErrorInfo interface. IErrorInfo *pEI; pCER->QueryInterface(IID_IErrorInfo, (void**)&pEI); // Now send the error object back to the current thread. SetErrorInfo(NULL, pEI); pCER->Release(); pEI->Release(); return E_FAIL; } else return S_OK; }

If we were building an ATL object supporting COM exceptions, the code above could be replaced with a single line:

// Throwing a COM error using ATL. STDMETHODIMP CoCar::SpeedUp() { m_currSpeed += 10; if(m_currSpeed > m_maxSpeed) { Error("Engine block has exploded!!"); return E_FAIL; } else return S_OK; }

Go ahead and recompile your project, and move on to create some test clients.

Step Three: A C++ Client

Catching a COM exception from a C++ client entails lots of repeat code. To help out, we will be creating a global level helper function that the C++ client can call whenever it detects a failed HRESULT. First, create a new Win32 Console Application project (a simple project). In your initial CPP file, prototype a global function named ParseError() which takes the following parameters:

// A custom client-side error handler. void ParseError(IUnknown* pInterface, REFIID riid);

A full, production level implementation of this helper method would need to acquire the IErrorInfo pointer using GetErrorInfo() and extract fully the state of the error object. For this lab, we will only extract the description of the error. Implement the method to perform the following tasks:

  1. AddRef() the incoming interface pointer.

  2. Query the incoming interface for IID_ISupportErrorInfo. If this is successful, call InterfaceSupportsErrorInfo(), passing in the GUID parameter.

  3. If this succeeded, call GetErrorInfo() and use the acquired IErrorInfo pointer to call GetDescription().

  4. Release any interfaces.

Here is the necessary code:

// A generic COM error handler function. void ParseError(IUnknown* pInterface, REFIID riid) { pInterface->AddRef(); ISupportErrorInfo *pISER; HRESULT hr; // Does this object have COM exceptions for me? hr = pInterface->QueryInterface(IID_ISupportErrorInfo, (void**)&pISER); if(SUCCEEDED(hr)) { // Does this interface have COM exception support? if(SUCCEEDED(pISER->InterfaceSupportsErrorInfo(riid))) { // Great! Get the exception & dump description. IErrorInfo *pEI; if(SUCCEEDED(GetErrorInfo(NULL, &pEI))) { BSTR desc; pEI->GetDescription(&desc); char buff [80]; WideCharToMultiByte(CP_ACP, NULL, desc, -1, buff, 80, NULL, NULL); cout << buff << endl; SysFreeString(desc); pEI->Release(); } } pISER->Release(); } pInterface->Release(); }

Now we need to code main() to make use of our new error facility. Using standard COM calls, create your CoCar and set the maximum speed from an ICreateCar pointer. Next, acquire an IEngine interface:

// Create the CoCar and grab some interfaces. int main(int argc, char* argv[]) { CoInitialize(NULL); IEngine *pEngine; ICreateCar *pCCar; // Make the car. CoCreateInstance(CLSID_CoCarErrors, NULL, CLSCTX_SERVER, IID_ICreateCar, (void**)&pCCar); pCCar->SetMaxSpeed(20); pCCar->QueryInterface(IID_IEngine, (void**)&pEngine); pCCar->Release(); ... }

From the IEngine interface, call SpeedUp() a number of times to trigger the COM error, each time making use of the ParseError() method:

// Speed up to cause error if(FAILED(pEngine->SpeedUp())) ParseError(pEngine, IID_IEngine);

Once you call SpeedUp() one too many times (i.e., 10 miles over the maximum) you should see output from your server showing the text description of the problem.

Step Four: A VB Client

To test the language-independent nature of the COM error protocol, open up Visual Basic and create a new Standard EXE project. Create a really simple GUI which allows you to create a CoCar and catch the error in a single button click. Recall that VB supplies the Err object to receive the COM exception. Set up an error handler within the button's Clicked event and place the description into a message box:

' Don't forget to set a reference to your type library! ' Private Sub btnDoIt_Click() On Error GoTo OOPS ' Create a new car Dim o As New CoCarErrors o.SetMaxSpeed 30 ' Get IEngine interface. Dim i As IEngine Set i = o ' Destroy the automobile. i.SpeedUp i.SpeedUp i.SpeedUp i.SpeedUp Exit Sub ' Error trap. Use Err object to suck out information. OOPS: MsgBox Err.Description, , "Message from the Car" End Sub

Very good! In the next chapter we will continue to look into the ATL framework by examining further uses of the COM map and the notion of object identity.


 < Free Open Study > 

Категории