The ATL Object Wizard

[Previous] [Next]

Now that we've described various aspects of the AppWizard-generated makefiles, it's time to proceed with our development of the TipOfTheDay component. Sometimes the words component and server are used interchangeably, but they probably shouldn't be. Technically speaking, a COM component is a COM class (or coclass) that exposes one or more COM interfaces. A COM server is a binary module in which one or more components reside. The ATL COM AppWizard creates an empty COM server, and the ATL Object Wizard is the productivity tool that helps you create coclasses. In our sample, the TipOfTheDay component will reside in a COM server named TIPSERVER.DLL and will expose (among others) an interface named ITipOfTheDay.

As shown in Figure 4-7, the ATL Object Wizard provides handy starter templates for several different types of commonly created components. With the release of Visual C++ 6.0, the APIs required to add your own starter object to the Object Wizard are now documented. Because the TipOfTheDay component is a simple object that exposes no user interface, we chose Simple Object and filled out the wizard options, as shown in Figure 4-8.

Figure 4-7. The ATL Object Wizard.

Figure 4-8. The properties for the new TipOfTheDay component.

For now, we'll make only a few observations regarding the TipOfTheDay properties. In Chapter 9, we'll provide in-depth information about nearly all the options the ATL wizards offer, along with their implications for developers.

ThreadingModel=Apartment

The apartment model (or models) supported by an in-process component is determined by its ThreadingModel Registry setting. Although the wizard makes it easy for you to choose any one of these models, you shouldn't make this decision lightly. If you specify an STA—which, somewhat confusingly, maps to ThreadingModel=Apartment in the Registry—you're guaranteed that instances of your component will be accessed by only a single thread. That means you must protect access to global data but not to instance data. If you specify the MTA (ThreadingModel=Free), you must protect access to both global and instance data. Because we're not sure whether the TipOfTheDay component will maintain per-instance state, we'll initially mark it as ThreadingModel=Apartment to be on the safe side. After we've completed the implementation, we'll reexamine the issue to see whether individual instances of the TipOfTheDay component can handle concurrent access by multiple threads. If so, it might make sense to choose ThreadingModel=Both instead.

Dual Interfaces

As mentioned earlier, we plan to use our TipOfTheDay component from within an ASP application as well as from MFC. Because existing scripting languages don't support vtable interfaces, the TipOfTheDay component must expose a dispinterface. This leaves us with three choices:

  • Expose a dispinterface only
  • Expose a dispinterface and a separate vtable interface
  • Expose a dual interface—a vtable interface derived from IDispatch

Depending on the circumstances, any one of these options might be appropriate. For an in-process server such as the TipOfTheDay component, the overhead associated with routing method calls through IDispatch is significant. For an EXE server, the relative overhead would be less significant but still an issue we would need to consider. Dispatch interfaces are less C++-friendly than vtable interfaces, but they work well in rapid application development (RAD) environments such as Visual Basic and Microsoft Visual InterDev. The default choice in the Object Wizard is the dual interface option. It's an appropriate choice if you must support both scripting clients and vtable-aware clients and if the interface methods use only simple, VARIANT-compatible data types as parameters. Because the ITipOfTheDay interface fits those two criteria, that's the option we chose.

The Object Wizard adds an implementation class to the project and an empty interface—that is, one with no methods—to the IDL file. (See Listing 4-1.) To add a method to the newly created interface, we must do three things:

  • Add a method entry to the ITipOfTheDay interface, as defined in the IDL file.
  • Add the method declaration to the CTipOfTheDay header file. CTipOfTheDay is the class that will provide an implementation of the ITipOfTheDay interface.
  • Add the method definition (implementation) to the CTipOfTheDay source file.

Listing 4-1. Object Wizard_generated IDL for the ITipOfTheDay interface and the TipOfTheDay coclass.

 // TipServer.idl : IDL source for TipServer.dll // // This file will be processed by the MIDL tool to // produce the type library (TipServer.tlb) and marshaling code. import "oaidl.idl"; import "ocidl.idl";     [         object,         uuid(C8FC12DF-47ED-11D2-B387-006008A667FD),         dual,         helpstring("ITipOfTheDay Interface"),         pointer_default(unique)     ]     interface ITipOfTheDay : IDispatch     {     }; [     uuid(C8FC12D1-47ED-11D2-B387-006008A667FD),     version(1.0),     helpstring("TipServer 1.0 Type Library") ] library TIPSERVERLib {     importlib("stdole32.tlb");     importlib("stdole2.tlb");     [         uuid(C8FC12E0-47ED-11D2-B387-006008A667FD),         helpstring("TipOfTheDay Class")     ]     coclass TipOfTheDay     {         [default] interface ITipOfTheDay;     }; }; 

Adding a Method

Fortunately, Visual C++ provides a handy Add Method dialog box that performs these three steps quickly and easily. To use the Add Method dialog box, right-click on the interface in the ClassView pane and choose the Add Method option from the context menu. (See Figure 4-9.) Using this timesaving approach, we added a GetNextTip method to the ITipOfTheDay interface. The autogenerated IDL for the GetNextTip method looks like this:

 interface ITipOfTheDay : IDispatch {         [id(1), helpstring("method GetNextTip")]         HRESULT GetNextTip([in, out] VARIANT* pvCookie,                            [out, retval] BSTR* pbstrText); }; 

Figure 4-9. The Add Method dialog box.

The GetNextTip method will allow the calling client to iterate through the tips provided by the component. To facilitate the iteration, this method uses a cookie.

NOTE
We're not sure who coined the word cookie in the context of storing component state, but it's certainly not a very descriptive choice. Perhaps a word like bookmark would have been more intuitive, if less entertaining. In short, a cookie is a tidbit of state-identifying information (an index, perhaps) passed from a server to its client so that the server can recall the state it was in when the client accesses it again sometime in the future. The client doesn't comprehend the format or meaning of the information and therefore can do nothing with the cookie other than pass it back to the server. By sharing the responsibility of state management with its clients, a server can often achieve greatly increased scalability. Trust us on this one, though: if you hand either of us a chocolate chip cookie, we'll know exactly what to do with it and you'll never get it back.

Because the component must operate in the ASP environment, we used a VARIANT as the data type for the cookie parameter. Although it's generally undesirable to use VARIANTs because of the degree of effort required to work with them in C++, this choice was necessary because ASP supports only the VARIANT data type. When GetNextTip is called, the cookie parameter specifies the tip to be retrieved. At the end of the method invocation, the cookie is set to a new value that identifies the next tip in the sequence.

NOTE
Had the cookie variable been an [in] parameter, we could have declared it as a long instead of a VARIANT. When you pass by value, Visual Basic, Scripting Edition (VBScript) can perform automatic type conversion from VARIANT to any of the standard types that the VARIANT encapsulates. When you pass by reference (as is the case with [out] parameters), no such conversion can be made.

As mentioned previously, the wizard generates the skeleton C++ code for the implementation of the newly added method in addition to the IDL:

 class ATL_NO_VTABLE CTipOfTheDay :  {  // ITipOfTheDay public:     STDMETHOD(GetNextTip)(/*[in, out]*/ VARIANT* pvCookie,                           /*[out, retval]*/ BSTR* pbstrText); };  STDMETHODIMP CTipOfTheDay::GetNextTip(VARIANT *pvCookie,     BSTR *pbstrText) {     return S_OK; } 

Separation of Interface from Implementation

As we've said before, one of the key benefits of COM is its separation of interface from implementation. When a coclass exposes an interface, it makes a contract with its clients regarding the syntax and semantics of its entry points, but it doesn't specify how those methods will be implemented. The implementation details, whether they be coded in C++, Java, Visual Basic, or COBOL, are hidden from the user of the coclass. In the case of the TipOfTheDay component, its clients have no idea how or where the text for each tip is stored—whether in memory, in a sequential file, or in a sophisticated distributed database. The powerful combination of encapsulation and polymorphism means that the implementation of the TipOfTheDay component can change over time without requiring any changes to its clients.

Despite its productivity benefits, the Add Method dialog box does have one drawback. It encourages developers to create interfaces and their associated implementations simultaneously. This two-birds-with-one-stone approach is by no means a fatal blow to solid COM development, but it does blur the line between two phases of software development that are best treated distinctly: interface design and interface implementation. Although we don't discourage the use of the Add Method and Add Property dialog boxes, we don't think they are a good substitute for thoughtful interface design, which is best achieved through careful whiteboard modeling of the interactions between the components in the system.

As shown in Listing 4-2, the initial implementation for our TipOfTheDay component uses a simple, static array as the mechanism for storing the tips. Obviously, a more sophisticated version of the component would have stored the tips in a file or a database rather than hard-wiring them into the code, thereby allowing administrators to add, configure, or edit the tips after deployment. Fortunately, our use of COM would allow us to make that transition behind the scenes, without requiring changes to existing clients.

Listing 4-2. A simple implementation of the ITipOfTheDay interface.

 // TipOfTheDay.cpp : Implementation of CTipOfTheDay #include "stdafx.h" #include "TipServer.h" #include "TipOfTheDay.h" /////////////////////////////////////////////////////////////////// // CTipOfTheDay #define TIP_COUNT    7  static LPWSTR gTipText[TIP_COUNT] = {     L"You should avoid strangers",     L"Good things come to those who wait",     L"Lay off the code; your love life is hurting",     L"Do a good turn today",     L"You win some, you lose some",     L"COM is love?",     L"All's fair in love and war" }; STDMETHODIMP CTipOfTheDay::GetNextTip(VARIANT *pvCookie,     BSTR *pbstrText) {     if(!pvCookie || !pbstrText)          return E_POINTER;     ::VariantChangeType(pvCookie, pvCookie, 0, VT_I4);     if(pvCookie->lVal < 0 || pvCookie->lVal >= TIP_COUNT)         return E_INVALIDARG;     *pbstrText = SysAllocString(gTipText[pvCookie->lVal]);     if(++(pvCookie->lVal) == TIP_COUNT)         pvCookie->lVal = 0;     return S_OK; } 



Inside Atl
Inside ATL (Programming Languages/C)
ISBN: 1572318589
EAN: 2147483647
Year: 1998
Pages: 127

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