Versioning the Coclass

[Previous] [Next]

Over time, the complexity and sophistication of software increases. Users request more features, new technologies facilitate more features, and developers invent more features. This progression is the nature of the business, and it's generally good for software consumers. COM facilitates software progression by helping developers limit the impact that changes in a component have on the client applications that use it. Once you've published an interface, COM doesn't allow you to make any modifications to that interface that would change its syntax or significantly alter its semantics. So how do you allow your component to progress over time or increase in sophistication? You expose another interface. You can either inherit from the existing interface or develop a completely new one, as long as the old interface still remains available to legacy clients.

If the new interface is simply a superset of an old interface, you'd be smart to have the new interface inherit from the old one, like so:

 [ object, uuid(93ED133D-3DA3-11D2-B380-006008A667FD) ] interface IBuckDog : IUnknown {     HRESULT Bark([out, retval] BSTR* pbstr);     HRESULT Sniff();     HRESULT NeedsWalk([out, retval] BOOL *pVal); }; [ object, uuid(93ED1341-3DA3-11D2-B380-006008A667FD) ] interface IBuckDog2 : IBuckDog {     HRESULT RollOver();     HRESULT HasFleas([out, retval] BOOL *pVal); }; 

In this IDL, the new interface, IBuckDog2, exposes all the methods found in IBuckDog in addition to two new ones. It makes sense to derive IBuckDog2 from IBuckDog and update your implementation to support both interfaces—the new methods as well as the old.

In other cases, we might be wiser to expose an additional interface not derived from one that currently exists. Take our TipOfTheDay component: it currently exposes a single interface that allows its users to iterate through each tip in the pool. It would be nice if the component also supported an interface that allowed administrators or sophisticated users to add new items to the tip pool. Obviously, adding this type of functionality would require significant changes in the internal implementation of the original component, because the current implementation uses a global array. Fortunately, COM allows you to make these changes (perhaps storing the tips in a SQL Server database) completely unbeknownst to the component's clients. So if we do want to allow certain users to add their own tips, we probably want to expose another interface entirely rather than add unrelated methods to the existing interface signature. Thus, we might end up with the following:

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

In both cases, the result is that a single coclass exposes two interfaces. Old clients that expected only the original interface wouldn't be negatively affected by the new one. They wouldn't even know it had been added. New clients would be able to take advantage of the functionality of both interfaces by calling QueryInterface to get back a pointer to either one.

The Dual Interface Dilemma

Unfortunately, this approach doesn't work with dual or dispatch interfaces because a component can expose only a single set of methods and properties via the IDispatch interface. So instead of being able to expose a second interface and take advantage of the power of dynamic discovery (à la QueryInterface), components that expose dual or dispatch interfaces must change the existing interface to create an encompassing superset of both the old and the new methods, as shown here:

 interface ITipOfTheDay : IDispatch {     [id(1), helpstring("method GetNextTip")]     HRESULT GetNextTip([in, out] long* pvCookie,                        [out, retval] BSTR* pbstrText); }; interface ITipManager : IDispatch {     [id(1), helpstring("method GetNextTip")]     HRESULT GetNextTip([in, out] long* pvCookie,                        [out, retval] BSTR* pbstrText);     [id(2), helpstring("method AddTip")]     HRESULT AddTip([in] BSTR bstrText,                    [out, retval] long* plCookie); }; coclass TipOfTheDay {     [default] interface ITipManager;     interface ITipOfTheDay; }; 

In the class declaration, both interfaces would be supported, and vtable-aware clients could call QueryInterface to get at both interfaces. To support scripting clients (which don't use QueryInterface and can thus see only a single dispinterface per component), however, the conceptual integrity of the ITipManager interface would be compromised. It would have to encompass the functionality of the unrelated ITipOfTheDay interface and thus implement the GetNextTip method:

 class ATL_NO_VTABLE CTipOfTheDay :  public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CTipOfTheDay, &CLSID_TipOfTheDay>, public IDispatchImpl<ITipOfTheDay, &IID_ITipOfTheDay,      &LIBID_TIPSERVERLib>, public IDispatchImpl<ITipManager, &IID_ITipManager,      &LIBID_TIPSERVERLib> {          BEGIN_COM_MAP(CTipOfTheDay)     COM_INTERFACE_ENTRY(ITipOfTheDay)     COM_INTERFACE_ENTRY(ITipManager)     COM_INTERFACE_ENTRY2(IDispatch, ITipManager)     END_COM_MAP()      // ITipOfTheDay public:     STDMETHOD(AddTip)(/*[in]*/ BSTR bstrTest,                       /*[out, retval]*/ long* plCookie);     STDMETHOD(GetNextTip)(/*[in, out]*/ VARIANT* pvCookie,                           /*[out, retval]*/ BSTR* pbstrText);      }; 

Inside Atl
Inside ATL (Programming Languages/C)
ISBN: 1572318589
EAN: 2147483647
Year: 1998
Pages: 127 © 2008-2017.
If you may any questions please contact us: