Developers Workshop to COM and ATL 3.0
| < Free Open Study > |
|
So then, thus far we have examined how to create a raw dispinterface in C++, as well as defining and implementing a dual interface with the help of IDL. Before we examine what the ATL framework will do for us when working with IDispatch, we will examine one last way to build up an implementation for your dispinterface using the server's type information.
When you think about it, the MIDL-generated *.tlb file is a perfect snapshot that describes the entire exposed functionality within the COM housing. Using a type library, a client understands the coclasses, interfaces, and DISPIDs for each member in the dispinterface (using the [id] attribute). So, if a COM client can load our type information to make requests of the COM server, it only stands to reason that the COM server can also use its own type information if the need should arise.
When you are implementing a dual interface in raw C++, you have seen that updating the DISPIDs and Invoke() implementation can become tedious as you add more methods and properties to the coclass. When you add a new member to the dispinterface, this will typically entail updating your IDispatch logic to hand out the correct DISPID and trigger the correct function based on the DISPID. Rather than going through these steps by hand, what if we were provided a way to delegate the task of looking up a DISPID and triggering the correct method to the COM subsystem?
If we were to load up our type information from within the coclass, we could simply hold onto this type information during the lifetime of the coclass and, using some COM library methods, let COM worry about the dispinterface logic. By doing so, we only need to keep our IDL file up to date, and we can avoid having to write GetIDsOfNames() and Invoke() logic when we extend our dispinterface. The COM library does offer this very functionality, and as we will see, ATL uses this same approach when providing an implementation for your dual interfaces.
Assume we wish to modify CoSquiggle one last time. Here, we are going to make use of the coclass's own type information to implement the methods of IDispatch. We would begin by adding a new private member variable to CoSquiggle to hold onto the type information of the dual interface:
// This version of CoSquiggle will make use of its own type information to // provide an IDispatch implementation. class CoDualSquiggle : public ISquiggle { public: CoDualSquiggle(); virtual ~CoDualSquiggle(); ... private: ULONG m_ref; ITypeInfo* m_ptypeInfo; // To hold onto our type information. };
The ITypeInfo interface is another standard interface provided by COM, which allows you to programmatically pull information from your type library at run time. For our current needs, we will only make use of a small subset of its overall functionality. To begin with, we need to load the object's type library using LoadRegTypeLib(). This function will retrieve an ITypeLib interface that represents the type library itself. From a valid ITypeLib pointer, we can access specific aspects of the type library. Here we are interested in obtaining the type information for CoSquiggle's dual interface (IID_ISquiggle). Here is the code:
// When this object comes to life, load up the type library and grab the // type information for your dual interface. CoDualSquiggle::CoDualSquiggle() : m_ref(0), m_ptypeInfo(NULL) { ++g_objCount; // When our object is constructed, we are going to // load up the *.tlb file and store it in our ITypeInfo pointer. ITypeLib* pTypeLibrary = NULL; HRESULT hr; hr = LoadRegTypeLib(LIBID_DualObjectWithTypeInfo, 1, 0, LANG_NEUTRAL, &pTypeLibrary); if(SUCCEEDED(hr)) { pTypeLibrary->GetTypeInfoOfGuid(IID_ISquiggle, &m_ptypeInfo); pTypeLibrary->Release(); } }
Note | As constructors do not allow a return value, you have no way to test if the type information has been loaded correctly. An alternative approach would be to create a secondary creation method, and have your IClassFactory::Cre- ateInstance() implementation call the method in question before exiting. This is more "ATL-like," but for now, we will stick to using the C++ constructor to load our type info. |
If the above code is successful (which it will be, as long as we have registered our type information into the system registry) we now have programmatic control over the server's type library. As the ITypeInfo interface has been AddRef-ed by GetTypeInfoOfGuid(), we will need to release our reference to it when the object shuts down:
// Release the type information when our object dies. CoDualSquiggle::~CoDualSquiggle() { --g_objCount; m_ptypeInfo->Release(); }
Now for the real fun. Using this valid ITypeInfo pointer, we can update our existing IDispatch implementation as follows. First, as we do provide real type information to any interested COM client, we should update GetTypeInfoCount() to return "1" (we have it) rather than "0" (we have nothing):
// This version of CoDualSquiggle has valid type information. STDMETHODIMP CoDualSquiggle::GetTypeInfoCount( UINT *pctinfo) { // We DO support type information in this object. *pctinfo = 1; return S_OK; }
As we have informed the world we have type info, we better be sure we return our ITypeInfo if we are asked. We would update GetTypeInfo() as such:
// Return our type information and be sure to AddRef() accordingly. STDMETHODIMP CoDualSquiggle::GetTypeInfo( UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo) { // Return pointer as we DO support type info. *ppTInfo = m_ptypeInfo; m_ptypeInfo->AddRef(); return S_OK; }
Next in line, we can simplify GetIDsOfNames() a great deal. The COM library provides the DispGetIDsOfNames() function to perform an automatic lookup of a requested DISPID based off a string name. The magic happens with help from your type information. As long as your dual interface has made use of the [id] attribute to mark each member of your dispinterface, this COM helper function finds the correct DISPID on your behalf. The really great news is that your dispinterface can now grow over time without requiring you to update the implementation of GetIDsOfNames():
// Use the ITypeInfo pointer and DispGetIDsOfNames() to do the // grunt work. STDMETHODIMP CoDualSquiggle::GetIDsOfNames( REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId) { // Now we just delegate the work of the lookup to our type library. return DispGetIDsOfNames(m_ptypeInfo, rgszNames, cNames, rgDispId); }
Last but not least, we will use our type information to provide the implementation of Invoke(). Making use of the COM library function DispInvoke(), we shuttle in the client-supplied parameters and our ITypeInfo pointer. Again, the best news is that this allows us to forget about updating Invoke() each time we add new members to the dispinterface:
// Delegate all work to the COM library. STDMETHODIMP CoDualSquiggle::Invoke( DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr) { // Again, delegate to the type info. return DispInvoke(this, m_ptypeInfo, dispIdMember, wFlags, pDispParams, pVarResult, pExcepInfo, puArgErr); }
Great! With this, we are now able to implement CoDualSquiggle's late binding behavior using the COM library and our server's type information. When our coclass is created, it loads up its type library, and gains access to ISquiggle's type information (and therefore the correct DISPIDs, function names, and parameters). If a client is making an early bound connection, this type information is never accessed, as the client is working directly with the ISquiggle vTable interface.
However, if the client has bound late using IDispatch, when (for example) DrawASquiggle() is called, GetIDsOfNames() looks up the DISPID from the loaded type information. Invoke() also makes use of this type information to ultimately call the same method (DrawASquiggle()) as an early bound client.
Lab 10-3: A Dual Object Implemented with Type Information
In this final raw C++ lab, we will make use of our own type information to help build a better dispinterface (read: less code to write). Here you will modify the implementation of CoSquiggle to make use of the COM library and your type information to fill out the details of Invoke() and GetIDsOfNames(). This approach is far easier than updating your dispinterface by hand as every new property and method comes along. Once you have come this far, understanding ATL's IDispatch implementation will be trivial, given that ATL follows the same approach as outlined in this lab.
On the CD The solution for this lab can be found on your CD-ROM under:Labs\Chapter 10\DualWithTypeInfoLabs\Chapter 10\DualWithTypeInfo\Dual VB Client
Step One: Load the Type Information
Note | Again, although I am assuming that you will be making modifications to Lab 10-2, this lab (and the solution) works with an entirely new in-proc server. |
In this final iteration of the raw C++ CoSquiggle, we will modify our current implementation of IDispatch to make use of our type information. If you have not done so already, open up your previous version of CoSquiggle (Lab 10-2). This time around, CoSquiggle is going to make use of its own type information to implement its dispinterface. Begin by adding a private data member of type ITypeInfo* (beyond this, nothing else will need to change in your header file):
// This time with type info! class CoDualSquiggle : public ISquiggleTI { ... private: ULONG m_ref; ITypeInfo* m_ptypeInfo; };
Now, in your constructor, load your type information using the LoadRegTypeInfo() COM library function. Be sure to include your *_i.c MIDL-generated file to make use of the LIBID constant. If that call is successful, gain type information for your ISquiggle interface, and store it in your ITypeInfo pointer:
// Upon construction we load our type information. #include "dualobjwithti_i.c" // MIDL-generated file... CoDualSquiggle::CoDualSquiggle() : m_ref(0), m_ptypeInfo(NULL) { ++g_objCount; // When our object is constructed, we are going to // load up the *.tlb file and store it in our ITypeInfo pointer. ITypeLib* pTypeLibrary = NULL; HRESULT hr; hr = LoadRegTypeLib(LIBID_DualObjectWithTypeInfo, 1, 0, LANG_NEUTRAL, &pTypeLibrary); if(SUCCEEDED(hr)) { pTypeLibrary->GetTypeInfoOfGuid(IID_ISquiggleTI, &m_ptypeInfo); pTypeLibrary->Release(); } }
In the destructor, be sure you release your type information:
// Unload our type info. CoDualSquiggle::~CoDualSquiggle() { --g_objCount; m_ptypeInfo->Release(); }
Step Two: Use that Type Information!
Next, we will need to reimplement all four methods of IDispatch to make use of your type information. Recall that the dispinterface is the summation of all properties and methods supported by a given coclass's IDispatch implementation. If you were to code GetIDsOfNames() and Invoke() in straight C++, this would entail adding a new string comparison condition in GetIDsOfNames() and a new case statement in Invoke(). Rather than updating everything by hand, the COM library provides methods that will route DISPIDs to methods in your coclass. The only trick to making this work is to ensure that the functions of your dispinterface (i.e., all the methods with the [id] attribute) have a method in the supporting coclass with the same signature. Begin by modifying GetTypeInfo() and GetTypeInfoCount() to inform the client that we do indeed have type information:
// Inform client we have some type info & return a pointer to that info. STDMETHODIMP CoDualSquiggle::GetTypeInfoCount( UINT *pctinfo) { // We DO support type information in this object. *pctinfo = 1; return S_OK; } // Don't forget to AddRef()! We have some other user! STDMETHODIMP CoDualSquiggle::GetTypeInfo( UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo) { // Return pointer as we DO support type info. *ppTInfo = m_ptypeInfo; m_ptypeInfo->AddRef(); return S_OK; }
Now, reinvent your coding of GetIDsOfNames() to simply call the COM library function DispGetIDsOfNames(), sending in the client-supplied parameters:
// Client gives us a string name, so do a lookup // between the string and the cookie. STDMETHODIMP CoDualSquiggle::GetIDsOfNames( REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId) { // We still only support one name at a time. if(cNames > 1) return E_INVALIDARG; return DispGetIDsOfNames(m_ptypeInfo, rgszNames, cNames, rgDispId); }
Do the same sort of update with your Invoke() method, this time calling DispInvoke():
// Now, based on the DISPID, call the correct helper function // using type info. STDMETHODIMP CoDualSquiggle::Invoke( DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr) { return DispInvoke(this, m_ptypeInfo, dispIdMember, wFlags, pDispParams, pVarResult, pExcepInfo, puArgErr); }
Go ahead and recompile your server! That is all we need to do in order to build a better dispinterface. To prove the point, go into your project's IDL file and add in another method (or property) to your dual interface. For example:
// Now when we update our dual interfaces, we will not need to update our IDispatch // coding! [uuid(99E314A7-58C2-11d3-B926-0020781238D4), object, dual] interface ISquiggleTI : IDispatch { [id(1)] HRESULT DrawASquiggle(); [id(2)] HRESULT FlipASquiggle(); [id(3)] HRESULT EraseASquiggle(); [id(4)] HRESULT Explode(); };
Then, provide an implementation for your new method(s) (or properties):
// Again, early and late bound clients will each ultimately end up calling the // same method. STDMETHODIMP CoDualSquiggle::Explode() { // Oh rats! MessageBox(NULL, "OH NO!", "CoDualSquiggle has EXPLODED", MB_OK | MB_SETFOREGROUND); return S_OK; }
Finally, before testing our new dispinterface, we need to register the type information into the system. Add the following lines to your *.reg file (with your GUID and path, of course) and remerge:
; Need type information registered ; HKEY_CLASSES_ROOT\TypeLib\{99E314A0-58C2-11d3-B926-0020781238D4}\1.0 = Dual With Type Info Server Type Lib HKEY_CLASSES_ROOT\TypeLib\{99E314A0-58C2-11d3-B926-0020781238D4}\ 1.0\0\Win32 = E:\ATL\Labs\Chapter 10\DualWithTypeInfo\Debug\DualObjWithTI.tlb
Step Three: Use that Type Information Again!
Finally, we will update the previous VB client to call the new Explode() method (or whatever methods/properties you dreamed up). Simply add calls to both the early and late bound logic, and run the program.
So then, here is the summation of IDispatch programming thus far:
-
You may build your dispinterface using straight C++. This can become a bother if you have a large dispinterface, as you need to continuously update your GetIDsOfNames() and Invoke() logic.
-
You may build a dispinterface by leveraging your type information. In this case, you can simply update your IDL code and add methods to your coclass. The IDispatch logic now leverages your type information using the COM library.
For the next logical step, on to ATL!
| < Free Open Study > |
|