| < Free Open Study > |
|
Now that we have a coclass supporting IDraw, we need to learn how to add methods to the interface itself. To add a method to an interface, right-click on the interface from ClassView (Figure 6-19) to activate the Add Method Wizard.
Figure 6-19: Accessing the Add Method Wizard.
Note | You may always choose to add support for a COM method by hand. The ATL wizards simply save you typing. |
This tool allows you to specify the name, return value, and method parameters for a given interface method. You will be writing IDL code when defining the method parameters, so don't forget to use the [in], [out], [in, out], and [out, retval] attributes. Figure 6-20 shows the setup for the Draw() method of IDraw:
Figure 6-20: Adding methods to your interfaces.
The Attributes button allows you to specify additional attributes for an IDL method definition from a drop-down list box (Figure 6-21). Every method will automatically have a [helpstring] attribute. If you find that you wish to modify your IDL method definitions after closing down the Add Method Wizard, you will need to edit the IDL file by hand, as you cannot edit previously defined interface methods with this dialog box.
Figure 6-21: Adding additional method attributes.
Once you hit the OK button, the Add Method Wizard will do the following on your behalf (which you can always elect to do by hand):
Update your IDL file to specify the new interface method.
Add a C++ prototype for the method in your coclass header file.
Add stub code for the method in your coclass implementation file.
Here is the updated IDraw IDL definition:
// Updated IDraw definition interface IDraw : IUnknown { [helpstring("method Draw")] HRESULT Draw(); };
Your CoHexagon definition and implementation file define the method in C++ syntax:
// Cohexagon.h class ATL_NO_VTABLE CCoHexagon : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CCoHexagon, &CLSID_CoHexagon>, public IDraw { ... public: STDMETHOD(Draw)(); };
Note | The STDMETHOD macro will be used in your header file, as your class is not the most derived; therefore, the virtual suffix is still technically permissible. Our previous coclasses used STDMETHODIMP in the header and CPP files, as we were the most derived, and therefore did not need the virtual keyword. |
// Cohexagon.cpp STDMETHODIMP CCoHexagon::Draw() { // TODO: Add your implementation code here return S_OK; }
At this point, all you need to do is give your interface methods some implementation code. I always recommend calls to MessageBox():
// Cohexagon.cpp STDMETHODIMP CCoHexagon::Draw() { // De facto implementations begin with MessageBox. MessageBox(NULL, "I am drawing a hexagon", "CoHex Says", MB_OK | MB_SETFOREGROUND); return S_OK; }
What would CoHexagon be without support for IShapeEdit? When you wish to add support for additional interfaces to an ATL coclass, you have two options. First, you may elect to make all code edits by hand. This is not a very painful process at all, and until ATL 3.0, this was the only choice you had. You may instead choose to make use of the Implement Interface Wizard. To illustrate what this wizard will do on your behalf, we will add support for IShapeEdit by hand and then turn our attention to the Implement Interface Wizard. Be sure you understand the following steps, as you will need to do them time and time again when developing ATL COM objects. Below are the steps to take to add a new COM interface to an existing ATL class.
Note | Both approaches require that you write (and understand) IDL code. There is no "Write All the IDL Code For Me" Wizard as of ATL 3.0. |
Note | There is no "Remove the Interface I Just Added" Wizard. If you use the Implement Interface Wizard and then decide to remove it, you will need to do so by hand. Simply reverse the steps outlined below to do so. |
Begin with the IDL. Open your project's IDL file and define a bare bones COM interface. As you remember, COM interfaces must at minimum have the [object] and [uuid] keywords. As before, use guidgen.exe to generate a new IID, and be sure to write the definition outside your library statement (this forces MIDL to generate the C and C++ language bindings). When you are finished, save the IDL file. You should see the new interface appear in the ClassView tab.
// Minimal IDL interface definition, curiosity of copy/paste reuse techniques. // [ object, uuid(297B9B10-119D-11d3-B8F2-0020781238D4), oleautomation, pointer_default(unique), helpstring("IShapeEdit Interface") ] interface IShapeEdit : IUnknown { };
At this point you can use the New Method Wizard to add the methods to your new interface. If you prefer, you can directly edit the IDL file rather than using the New Method Wizard. Assume this IDL code (with the FILLTYPE enumeration) is exactly the same as we saw from Chapter 4:
// Our little enum. typedef [uuid(442F32E0-E7EE-11d2-B8D2-0020781238D4), v1_enum] enum FILLTYPE { HATCH = 0, SOLID = 1, POLKADOT = 2 } FILLTYPE; [...] interface IShapeEdit : IUnknown { [helpstring("method Inverse")] HRESULT Inverse(); [helpstring("method Stretch")] HRESULT Stretch([in] int factor); [helpstring("method Fill")] HRESULT Fill([in] FILLTYPE fType); };
Next, edit your coclass's IDL definition to specify support for the new interface. You are free to change the [default] interface if you wish (the Object Wizard always marks the first interface as the [default]).
// Updated IDL coclass definition [ uuid(4A01DD03-066C-11D3-B8E5-0020781238D4), helpstring("CoHexagon Class") ] coclass CoHexagon { [default] interface IDraw; interface IShapeEdit; };
Now that the IDL code has been completed, open up your coclass's header file and derive from your new interface. Next, add a COM_INTERFACE_ENTRY for this interface to your COM map and finally define the prototypes for the methods (using the STDMETHOD macro). Here are the relevant changes made to CoHexagon:
// Cohexagon.h class ATL_NO_VTABLE CCoHexagon : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CCoHexagon, &CLSID_CoHexagon>, public IDraw, public IShapeEdit { ... BEGIN_COM_MAP(CCoHexagon) COM_INTERFACE_ENTRY(IDraw) COM_INTERFACE_ENTRY(IShapeEdit) END_COM_MAP() ... // IShapeEdit STDMETHOD(Stretch)(int factor); STDMETHOD(Fill)(FILLTYPE fType); // FILLTYPE is defined in the // MIDL-generated header file. STDMETHOD(Inverse)(); };
Finally, implement each new interface method. For example:
// Cohexagon.cpp STDMETHODIMP CCoHexagon::Stretch(int factor) { for(int i = 0; i < factor; i++) MessageBox(NULL, "Stretching!", "CoHex Says", MB_OK | MB_SETFOREGROUND); return S_OK; }
Not too bad, huh? The only thing you need to be on the lookout for is the dreaded copy and paste error. Be sure your header file uses the STDMETHOD macro, while your CPP file uses STDMETHODIMP (remember that the syntax of each macro is unique). If you copy your prototypes from the header file and do not readjust for the STDMETHODIMP syntax in your *.cpp files, you are bound to get a number of bizarre errors.
By the same token, be sure you remove the semicolon from the header file prototypes when pasting your interface methods into the implementation file (this one is very easy to forget). You will know you did forget to remove the semicolon if you see the following compile error:
error C2447: missing function header (old-style formal list?)
As mentioned, ATL 3.0 provides a second way to implement additional interfaces on a coclass, using the Implement Interface Wizard, which automates the above steps on your behalf. Let's check this tool out next.
This CASE tool, new to ATL 3.0, helps automate the process outlined above. As before, you begin by writing the minimal IDL code for the new interface and adding support for it in the coclass. To illustrate the Implement Interface Wizard, let's add support for a new interface named IErase. Here is the updated IDL file:
// Definition of, and support for, IErase. [ object, uuid(13AE1FBD-1582-11D3-B8F2-0020781238D4), helpstring("IErase Interface"), oleautomation, pointer_default(unique) ] interface IErase : IUnknown { }; [...] coclass CoHexagon { [default] interface IDraw; interface IShapeEdit; interface IErase; };
Next, you must recompile your IDL file to recompile the *.tlb file. If you do not refresh your type information, the Implement Interface Wizard cannot see your unimplemented interfaces.
Note | When using the Implement Interface Wizard for your custom interfaces, do not add methods or properties to your interface until you have first run the wizard. Read on to see why. |
Assuming all is well with your IDL code, right-click on the coclass from ClassView and select the Implement Interface menu selection. The resulting dialog box (Figure 6-22) will list all interfaces defined in your IDL file which are not currently supported by your coclass (notice that IDraw and IShapeEdit do not appear as options). If you have updated the IDL for a new interface but do not see it listed in the Interfaces pane, you forgot to recompile your type library. Check off the IErase interface and click OK.
Figure 6-22: The Implement Interface Wizard.
The Add Typelib button allows you to implement interfaces defined in other type libraries. As you may suspect, HKCR\TypeLib is consulted when you click this button (thus the slight delay). If you wish to implement, say, CoCar interfaces in your CoHexagon (which would most likely be a very bad design), just select the type lib from the list box. We will not do this, but Figure 6-23 illustrates the process:
Figure 6-23: Implementing interfaces defined outside your project.
Here is the result of using the Implement Interface Wizard: Your coclass will be derived from the new interface, and your COM map is automatically updated with a COM_INTERFACE_ENTRY listing.
If you used this tool to implement an interface already containing IDL method definitions, each method in the interface will be listed inline in your header file. Assume that we added an Erase() method to IErase before using the wizard. If this were the case, we would see the following listed in the CoHexagon header file once we ran the tool:
// Implement Interface Wizard generated code class ATL_NO_VTABLE CCoHexagon : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CCoHexagon, &CLSID_CoHexagon>, public IDraw, public IShapeEdit, public IErase { ... BEGIN_COM_MAP(CCoHexagon) COM_INTERFACE_ENTRY(IDraw) COM_INTERFACE_ENTRY(IShapeEdit) COM_INTERFACE_ENTRY(IErase) END_COM_MAP() ... // IErase // The Wizard will inline any methods found in the IDL interface definition. STDMETHOD(Clear)() { return E_NOTIMPL; } };
If you do not define your methods before using this CASE tool, you may then use the Add Method tool after running the wizard. This will place prototypes in your header file and method stub code in your implementation file. You are free to mix and match both approaches in your projects. The best approach is to define an empty interface definition in your IDL, activate the Implement Interface Wizard, and then fill your interface with methods as usual.
| < Free Open Study > |
|