Developers Workshop to COM and ATL 3.0
| < Free Open Study > |
|
ATL provides such fantastic support for IDispatch that the work on your part becomes a non-issue. If you are creating a brand new coclass using the ATL Object Wizard, simply ensure you specify a dual interface from the Attributes tab:
Note | The ATL Object Wizard does not provide a way to support a pure dispinterface (i.e., IDispatch only). |
When you specify a dual interface, you will see that your initial [default] interface has been configured with the correct IDL attributes to configure it as a dual. Assume we have created a new ATL DLL, and have inserted the CoATLSquiggle simple object. Our ICoATL- Squiggle interface definition would appear as such:
// The initial IDL definition for an ATL dual interface. [ object, uuid(938EA4F3-5802-11D3-B926-0020781238D4), dual, helpstring("ICoATLSquiggle Interface"), pointer_default(unique) ] interface ICoATLSquiggle : IDispatch { [id(1)] HRESULT DrawASquiggle(); [id(2)] HRESULT FlipASquiggle(); [id(3)] HRESULT EraseASquiggle(); };
As dual interface objects must be able to return an IDispatch pointer upon request, you will notice that your COM_MAP has been updated with a COM_INTERFACE_ENTRY listing to support late binding. Most importantly, your class will now derive from a new ATL template, IDispatchImpl<>:
// A dual interface ATL object will always derive from IDispatchImpl<>. class ATL_NO_VTABLE CCoATLSquiggle : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CCoATLSquiggle, &CLSID_CoATLSquiggle>, public IDispatchImpl<ICoATLSquiggle, &IID_ICoATLSquiggle, &LIBID_ATLDUALOBJECTLib> { ... BEGIN_COM_MAP(CCoATLSquiggle) COM_INTERFACE_ENTRY(ICoATLSquiggle) // Smart clients. COM_INTERFACE_ENTRY(IDispatch) // Dumb clients. END_COM_MAP() ... };
Beyond these slight changes, everything else is as you would expect. You can use all the same CASE tools to populate your interface and write the necessary implementations (e.g., Add Method Wizard, Add Property Wizard, Implement Interface Wizard). The logic of IDispatch is of no concern to you, as IDispatchImpl<> is doing the work of finding DISPIDs and calling Invoke() for any late bound client.
The IDispatchImpl<> Template
ATL provides a complete implementation of IDispatch using your server's type information. In fact, the code you examined in the previous section (CoSquiggle with type information) is just about the exact same code used by ATL! IDispatchImpl<> implements the four methods of IDispatch for your dual interface coclasses. The details of each method are fleshed out by use of an ATL helper class named CComTypeInfoHolder. This class is a wrapper around your type library and, as you might expect, is loading your server and extracting the type information for your dual interface, just as we did in the previous example. <atlcom.h> defines IDispatchImpl<> as so:
// IDispatchImpl<> provides a full implementation of IDispatch based off your // server's type information. template <class T, const IID* piid, const GUID* plibid = &CComModule::m_libid, WORD wMajor = 1, WORD wMinor = 0, class tihclass = CComTypeInfoHolder> class ATL_NO_VTABLE IDispatchImpl : public T { public: // A typedef to CComTypeInfoHolder. typedef tihclass _tihclass; STDMETHOD(GetTypeInfoCount)(UINT* pctinfo) { *pctinfo = 1; return S_OK; } STDMETHOD(GetTypeInfo)(UINT itinfo, LCID lcid, ITypeInfo** pptinfo) { return _tih.GetTypeInfo(itinfo, lcid, pptinfo); } STDMETHOD(GetIDsOfNames)(REFIID riid, LPOLESTR* rgszNames, UINT cNames, LCID lcid, DISPID* rgdispid) { return _tih.GetIDsOfNames(riid, rgszNames, cNames, lcid, rgdispid); } STDMETHOD(Invoke)(DISPID dispidMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pdispparams, VARIANT* pvarResult, EXCEPINFO* pexcepinfo, UINT* puArgErr) { return _tih.Invoke((IDispatch*)this, dispidMember, riid, lcid, wFlags, pdispparams, pvarResult, pexcepinfo, puArgErr); } protected: // An instance of CComTypeInfoHolder. static _tihclass _tih; static HRESULT GetTI(LCID lcid, ITypeInfo** ppInfo) { return _tih.GetTI(lcid, ppInfo); } };
IDispatchImpl<> takes six parameters, four of which have default values. You must specify the GUID and name of the dual interface this template is wrapping using the first and second parameters. Beyond this, you may specify the major and minor version of your type library (which is defaulted to version 1.0), as well as your LIBID and the class that is holding onto your type information (which is by default the ATL CComTypeInfoHolder class).
As you can see, the CComTypeInfoHolder class is held in the _tih member variable, which is used to provide the implementation of GetTypeInfoCount(), GetTypeInfo(), GetIDsOfNames(), and Invoke(). Finally, also notice that your coclass will inherit the GetTI() member function, which allows you to access the type information in your own coclass-specific coding.
The ATL Type Information Wrapper Class: CComTypeInfoHolder
This ATL helper class is responsible for holding onto your type information during the execution of your coclass. It is also defined in <atlcom.h>. The first point of interest is the public data members defined by the class. Note that your ITypeInfo interface, major and minor version number, LIBID, and so forth are all held by CComTypeInfoHolder:
// CComTypeInfoHolder maintains all relevant type information. class CComTypeInfoHolder { public: const GUID* m_pguid; const GUID* m_plibid; WORD m_wMajor; WORD m_wMinor; ITypeInfo* m_pInfo; ... };
Next we have the GetTI() member function, which does the work of loading the type library and holding onto the ITypeInfo interface. Here is a streamlined version, illustrating the relevant points under discussion:
// Load the type library and gain an ITypeInfo pointer to your dual interface. inline HRESULT CComTypeInfoHolder::GetTI(LCID lcid) { HRESULT hRes = E_FAIL; if (m_pInfo == NULL) { ITypeLib* pTypeLib; hRes = LoadRegTypeLib(*m_plibid, m_wMajor, m_wMinor, lcid, &pTypeLib); if (SUCCEEDED(hRes)) { CComPtr<ITypeInfo> spTypeInfo; hRes = pTypeLib->GetTypeInfoOfGuid(*m_pguid, &spTypeInfo); if (SUCCEEDED(hRes)) { CComPtr<ITypeInfo> spInfo(spTypeInfo); CComPtr<ITypeInfo2> spTypeInfo2; if (SUCCEEDED( spTypeInfo->QueryInterface(&spTypeInfo2))) spInfo = spTypeInfo2; LoadNameCache(spInfo); m_pInfo = spInfo.Detach(); } pTypeLib->Release(); } } return hRes; }
Here, your type library is loaded with a call to LoadRegTypeLib(). GetTypeInfoOfGuid() is then called to obtain an ITypeInfo pointer to your dual interface (via some ATL smart pointer logic). This interface pointer is then held in the m_pInfo member variable.
With that aside (and given your current understanding of this approach to implementing IDispatch), the code of IDispatchImpl<> is quite straightforward. For each member of IDispatch, ATL simply leverages the CComTypeInfoHolder member variable. Again, here is the ATL implementation of GetIDsOfNames(), which in turn delegates all real work to your server's type library and the COM library:
// It can't get much easier than this. STDMETHOD(GetIDsOfNames)(REFIID riid, LPOLESTR* rgszNames, UINT cNames, LCID lcid, DISPID* rgdispid) { return _tih.GetIDsOfNames(riid, rgszNames, cNames, lcid, rgdispid); }
So then, ATL is using the same approach we used in our third version of CoSquiggle. Recall that we also loaded our type library, extracted type information for the dual interface, and delegated our IDispatch implementation to our ITypeInfo pointer. ATL makes late binding a non-issue. Just check the correct option in the ATL Object Wizard, and code some COM objects.
Regarding Multiple Dual Interfaces on a Single Coclass
As you build ATL COM objects that support a dual interface, you are bound to ask what to do when you wish to extend your object's functionality (i.e., add another interface). The Object Wizard provides options for custom and dual interfaces, and given what you know about the virtues of the dual interface, you may decide to extend CoSquiggle with another dual as so:
// IDL for another dual. [ object, uuid(21D961E0-5F09-11d3-B928-0020781238D4), dual ] interface IColor : IDispatch { [id(1)] HRESULT SetTheColor([in] long colorID); }; // A dual interface ATL object will always derive from IDispatchImpl<>. class ATL_NO_VTABLE CCoATLSquiggle : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CCoATLSquiggle, &CLSID_CoATLSquiggle>, public IDispatchImpl<ICoATLSquiggle, &IID_ICoATLSquiggle, &LIBID_ATLDUALOBJECTLib>, public IDispatchImpl<IColor, &IID_IColor, &LIBID_ATLDUALOBJECTLib> { ... BEGIN_COM_MAP(CCoATLSquiggle) COM_INTERFACE_ENTRY(ICoATLSquiggle) COM_INTERFACE_ENTRY(IColor) COM_INTERFACE_ENTRY2(IDispatch, IColor) END_COM_MAP() ... };
Note that because we are using IDispatchImpl<> to provide coding details for the methods of IDispatch, we need to derive from this template twice (once for each dual interface). However, doing so will confuse the compiler, as we offer two possible ways to obtain the IDispatch interface. As you have already seen, ATL provides the COM_INTER- FACE_ENTRY2() macro for this very ambiguity. However, this is not the problem.
The whole reason for supporting IDispatch is to allow dumb clients to access your server, given they provide no way for developers to directly call QueryInterface(). Now, if you have two (or more) dual interfaces on a single coclass, how are the dumb clients ever going to gain access to them? They can't! Dumb clients will only be able to access the dispinterface marked as the [default] interface. Period. A smart client will always choose to gain access to the custom vTable interfaces of your COM objects. Therefore, the whole idea of supporting multiple dual interfaces on a single coclass makes no sense.
Extending a coclass so that both smart and dumb clients are happy takes a bit of hoop jumping. One approach should be to define a single dual interface on the coclass, which is assumed to be used exclusively by dumb clients. Once you have added this dual interface, you safely version the object by adding additional custom vTable interfaces to the object. If you wish to allow the dumb clients to have access to the methods defined in a new vTable interface, simply extend the dispinterface of your existing dual.
For example, if CoSquiggle were to support the IColor interface, we could rewrite our IDL to define two custom vTable interfaces: IColor and ICoATLSquiggle (ICoATLSquiggle was originally a dual, which we have retrofitted to now be a pure vTable interface) and a new single dual for scripting clients:
// IDL for new vTable interface. [ object, uuid(21D961E0-5F09-11d3-B928-0020781238D4), oleautomation ] interface IColor : IUnknown { HRESULT SetTheColor([in] long colorID); }; // Retrofit our previous dual to be a vTable interface. [ object, uuid(938EA4F3-5802-11D3-B926-0020781238D4) , oleautomation ] interface ICoATLSquiggle : IUnknown { HRESULT DrawASquiggle(); HRESULT FlipASquiggle(); HRESULT EraseASquiggle(); }; // This interface allows dumb clients to get at the functionality offered by the // vTable interfaces. [ object, uuid(938EA4F3-5802-11D3-B926-0020781238D4), dual, helpstring("ICoATLSquiggle Interface"), pointer_default(unique) ] interface ITheInterfaceForDumbClients : IDispatch { [id(1)] HRESULT DrawASquiggle(); [id(2)] HRESULT FlipASquiggle(); [id(3)] HRESULT EraseASquiggle(); [id(4)] HRESULT SetTheColor([in] long colorID); };
In this way, we choose which methods of the sole [dual] will be accessible to late binding clients. This may or may not be the entire functionality supported by the set of vTable interfaces-that's a design choice. Notice as well that each vTable interface has been assigned the [oleautomation] attribute. This not only more clearly illustrates that these interfaces are VARIANT compatible, but has the added bonus of forcing ATL to register each interface under HKCR\Interface and mark each as using the universal marshaler.
And with this, we wrap up our formal discussion of scriptable COM objects. Here is a question: What if you wish to add late binding capabilities to a legacy ATL object that was not written to support IDispatch? Funny you should ask, as that is the topic of our next lab. Put CoCar on the web, and investigate the idea of a single dual interface on a coclass.
Lab 10-4: IDispatch Support in ATL
This lab will pull together all your knowledge of the IDispatch interface, and allow you to script your ATL CoCar from MS Internet Explorer. You will be modifying a previous coclass that was not configured to support IDispatch using the IDispatchImpl<> template. Furthermore, you will contend with the reality that scripting clients can only access the [default] dual interface on an object-and see how to keep everyone happy.
On the CD The solution for this lab can be found on your CD-ROM under:Labs\Chapter 10\ScriptableCoCarLabs\Chapter 10\ScriptableCoCar\Script Client
Step One: Adjust Your Existing IDL Code
Note | To keep things simple, I suggest you choose to retrofit your existing ATLCoCar project from Chapter 6. |
Back in Chapter 6, you created an ATL-based server (ATLCarServer) containing a single coclass (ATLCoCar) with no support for scripting. At that time, CoCar supported a set of custom vTable interfaces:
// The original coclass has no support for IDispatch. [ uuid(271CC4E1-0805-11D3-B8E5-0020781238D4)] coclass ATLCoCar { [default] interface ICreateCar; interface IStats; interface IEngine; interface IOwnerInfo; };
To support IDispatch, you will need to edit your IDL code to support a single dual interface, which is intended to be used only by scripting clients. Define a new dual interface in IDL as the following:
// This interface is intended for scripting clients only. [object, uuid(938EA507-5802-11D3-B926-0020781238D4), dual, helpstring("If you are a smart client don't use me!"), pointer_default(unique) ] interface IDualCoCar : IDispatch { };
Next, you need to decide which methods of your existing interfaces will be available to scripting clients. As you have seen, scripting clients are only able to access the [default] IDispatch interface, and therefore multiple dual interfaces are of little help. Update your dual interface to support the following dispinterface (just copy and paste the method prototypes from the existing vTable definitions):
// Your [default] dual will leverage the existing vTable methods. interface IDualCoCar : IDispatch { [id(1)] HRESULT SetPetName([in] BSTR petName); [id(2)] HRESULT SetMaxSpeed([in] int maxSp); [id(3)] HRESULT DisplayStats(); [id(4)] HRESULT GetPetName([out, retval] BSTR* petName); [id(5)] HRESULT SpeedUp(); [id(6)] HRESULT GetMaxSpeed([out, retval] int* maxSpeed); [id(7)] HRESULT GetCurSpeed([out, retval] int* curSpeed); [propget, id(8)] HRESULT Name([out, retval] BSTR *pVal); [propput, id(8)] HRESULT Name([in] BSTR newVal); [propget, id(9)] HRESULT Address([out, retval] BSTR *pVal); [propput, id(9)] HRESULT Address([in] BSTR newVal); };
Finally, be sure that your dual interface is assigned as the [default], and mark each existing vTable interface with the [oleautomation] attribute:
// Mark the [default] as your new dual. coclass DualCoCar { [default] interface IDualCoCar; // Script clients only get the [default] dual. interface IStats; interface IOwnerInfo; interface IEngine; interface ICreateCar; };
Step Two: Update Your Coclass
To leverage ATL's support for dual interfaces, you must derive your coclass from IDispatchImpl<> and update your COM_MAP. As we support only a single dual (and rightfully so) we only need to tweak CATLCoCar as such:
// Update your coclass to implement IDispatch. class ATL_NO_VTABLE CDualCoCar : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CDualCoCar, &CLSID_DualCoCar>, public IDispatchImpl<IDualCoCar, &IID_IDualCoCar, &LIBID_SCRIPTABLECOCARLib>, public ICreateCar, public IStats, public IEngine, public IOwnerInfo { ... BEGIN_COM_MAP(CDualCoCar) COM_INTERFACE_ENTRY(IDualCoCar) COM_INTERFACE_ENTRY(IDispatch) COM_INTERFACE_ENTRY(ICreateCar) COM_INTERFACE_ENTRY(IStats) COM_INTERFACE_ENTRY(IEngine) COM_INTERFACE_ENTRY(IOwnerInfo) END_COM_MAP() ... };
We do not need to add any new code for the methods behind any of the existing interfaces, including our new dual. Recall that IDispatchImpl<> will use your type information to determine which method to call (in the coclass) for a given DISPID. As you have already written code for the custom vTable methods, these same methods will be called by late bound clients using IDispatch! The server is complete.
Step Three: Build a Web Client
Some of you might think I am being unfair, making you write VB, Java, and now HTML code in a book about ATL. Well, call me cruel (really though, COM is all about language independence). If you are not a webmaster, feel free to use the HTML test client included on your CD-ROM (be sure to update the ProgID). This page was built using MS Visual InterDev 6.0, and is straight HTML. The relevant parts of this page are listed below:
' For the most part, VBScript looks and feels like VB proper. ' <SCRIPT language = VBScript> dim o Sub btnCreateCar_onClick set o = CreateObject("ScriptableCoCar.DualCoCar") o.SetPetName (petName.value) o.Address = ownerAddress.value o.SetMaxSpeed (maxSpeed.value) o.Name = ownerName.value txtSpeed.value = o.GetCurSpeed End Sub Sub btnSpeedUp_onClick if o.GetCurSpeed < o.GetMaxSpeed then o.SpeedUp txtSpeed.value = o.GetCurSpeed else msgbox o.GetPetName() & " is Destroyed!",,"Lead Foot!" end if End Sub Sub btnGetStats_onClick o.DisplayStats msgbox o.Name,,"Your name is:" msgbox o.Address ,,"You live here:" End Sub </SCRIPT>
When you run the script, IE will catch that your coclass is not marked as "safe for scripting" and will issue the following warning dialog:
In Chapter 9, we spent some time discussing COM categories. As you recall, ATL allows you to add support for custom or standard CATIDs. If you wish to mark your new CoCar as web safe, add the following CATEGORY_MAP to your coclass and recompile:
// You must include the following header file to grab the correct GUIDs! // #include <ObjSafe.h> BEGIN_CATEGORY_MAP(CDualCoCar) IMPLEMENTED_CATEGORY(CATID_SafeForScripting) IMPLEMENTED_CATEGORY(CATID_SafeForInitializing) END_CATEGORY_MAP()
When you do so, the ATL registrar will make additional entries under HKCR\CLSID\{guid} that informs IE your little automobile means no harm:
With that, here is your CoCar, in all its glory, from the mean streets of structured programming to the world of a web-enabled COM object.
| < Free Open Study > |
|