A Licensed Class Factory and IClassFactory2

 < Free Open Study > 



The final issue we will address is the ability to build a licensed control. Recall that the IClassFactory2 interface is a COM interface that derives from IClassFactory and adds support to test for a valid LIC file before loading your control:

// IClassFactory2 ensures the end user of your control has a valid *.LIC file // before creating the object. [object,uuid(B196B28F-BAB4-101A-B69C-00AA00341D07),pointer_default(unique)] interface IClassFactory2 : IClassFactory {      typedef struct tagLICINFO {           LONG cbLicInfo;           BOOL fRuntimeKeyAvail;           BOOL fLicVerified;      } LICINFO;      HRESULT GetLicInfo([out] LICINFO * pLicInfo);      HRESULT RequestLicKey([in] DWORD dwReserved, [out] BSTR * pBstrKey);      HRESULT CreateInstanceLic([in] IUnknown * pUnkOuter,                              [in] IUnknown * pUnkReserved,                              [in] REFIID riid,                              [in] BSTR bstrKey,                              [out, iid_is(riid)] PVOID * ppvObj); }; 

ATL provides a stock implementation of this interface with the CComClassFactory2<> template. Also recall that when you want to override the default class factory provided by CComCoClass, you simply insert the following alternative class factory macro in your class definition:

// We now support a licensed class object. class ATL_NO_VTABLE CShapesControl : ... { ... // IClassFactory2 DECLARE_CLASSFACTORY2(CTheLicense) ... };

Once you have specified support for IClassFactory2, you will need to write a helper class, which ATL expects to implement the following functions:

  • VerifyLicenseKey(): Tests the incoming BSTR against the known value.

  • GetLicenseKey(): Returns the *.LIC file.

  • IsLicenseValid(): Returns TRUE if the machine using this control contains a valid license.

If you are serious about licensing your ActiveX controls, you will need to download the (free) License Package Authoring Tool from the MS Site Builder Network web page. This tool can be used to create license files that are packaged into a binary format to prevent tampering from web-based clients. Furthermore, you would obviously want to create some encryption algorithm when creating the key itself. To keep things simple, here is a simple licensing class named CTheLicense, which implements the methods required by CComClassFactory2<>:

// This helper class implements the necessary methods required by // CComClassFactory2<> class CTheLicense { public:      CTheLicense();      virtual ~CTheLicense();      static BOOL VerifyLicenseKey(BSTR bstr)      {           return wcscmp(bstr, L"YouGottaHaveThis") == 0;      }      static BOOL GetLicenseKey(DWORD reserved, BSTR* pBSTR)      {           *pBSTR = SysAllocString(OLESTR("YouGottaHaveThis"));           return TRUE;      }      static BOOL IsLicenseValid()      {           DWORD result;           result = GetFileAttributes("C:\\ Test.lic") == (DWORD)-1;           if(!result)           {                MessageBox(NULL, "All systems go", "Creating object",                          MB_OK | MB_SETFOREGROUND);                return TRUE;           }           else           {                MessageBox(NULL, "Uh oh", "Can't make object",                          MB_OK | MB_SETFOREGROUND);                return FALSE;           }      } };

 On the CD   To complete this test, you need to create a file indeed named Test.lic which resides in your C drive (the path used for the sample solution is E:\ATL\Labs\Chapter 14\AXShapesSer- ver\Test.lic). When a user attempts to use your control, IClassFactory2 is obtained by the client, and if a valid license key is not where it should be, the creation process is terminated. For example, if we delete (or rename) Test.lic and attempt to use ShapesServer from VB, we are shown the following error (Figure 14-42). If you replace the deleted Test.lic file, your control will be created as usual.

click to expand
Figure 14-42: The result of not having a valid *.lic file.

Lab 14-2: Adding Property Pages and Property Persistence

This lab extends the CAxCar control you built in the previous lab. First, we will retrofit your custom methods to support the [bindable] and [requestedit] attributes and update our [propput] methods accordingly. We also update our control to handle persistence using a COM property bag. Finally, we create a custom property page to allow the end user to configure our control, and update our property map to associate the Animate, Speed, and PetName properties to this new page.

 On the CD   The solution for this lab can be found on your CD-ROM under:
Labs\Chapter 14\AXCarServer
Labs\Chapter 14\AXCarServer\VBClient

Step One: Enhance Our Custom Properties

As you have learned, COM provides a unique interface named IPropertyNotifySink, which allows a container to approve or disapprove of a property reassignment, as well as be informed when a property has changed. In IDL, we mark our properties as [requestedit] and/or [bindable] to enable this behavior. Let's retrofit our properties to do this very thing. Begin by adding the correct IDL attributes to each property in your [default] IAxCar interface. For example:

// Binding our properties to the client's property sink. [object, uuid(65A0EEBF-745F-11D3-B92E-0020781238D4), dual, helpstring("IAxCar Interface"),pointer_default(unique) ] interface IAxCar : IDispatch { ...          [propget, id(3), helpstring("Should I animate?"), bindable, requestedit]      HRESULT Animate([out, retval] AnimVal *newVal);      [propput, id(3), helpstring("Should I animate?"), bindable, requestedit]      HRESULT Animate([in] AnimVal newVal); ... }; 

Once you have updated each property in this way, you now must go and reengineer your [propput] methods to make use of the FireOnEditRequest() and FireOnChanged() methods. Here is the updated implementation for the Animate property (Speed, MaxSpeed, and PetName would have similar tweaks):

// Request and notify. STDMETHODIMP CAxCar::put_Animate(AnimVal newVal) {      // Is it OK to update the Animate property?      if(FAILED(FireOnRequestEdit(3)))          // [id(3)] = Animate           return S_FALSE;      // Set the animation property.      m_bAnimate = newVal;      if(::IsWindow(m_hWnd) && (m_bAnimate == Yes))           SetTimer(1, 250);      else if(::IsWindow(m_hWnd))           KillTimer(1);      // Tell container we have changed the value.      FireOnChanged(3);                         // [id(3)] = Animate      return S_OK; }

When you are making use of [bindable] and/or [requestedit] properties, you may wish to define your own DISPID constants to reference in your source code, rather than the magic numbers seen above.

In any case, realize that the parameter sent into the FireOnRequestEdit() and FireOnChanged() methods is the DISPID of your custom property (i.e., the [id] attribute in your IDL definition).

Step Two: Support Property Persistence

Our previous lab did not incorporate property persistence in order to keep things focused on the animation logic. Here we will need to make some adjustments so a container can remember the values behind our custom properties between sessions. ATL provides automatic support for persistence into a stream or storage using the IPersistStorageImpl<> and IPersistStreamInitImpl<> templates (and the required COM map entries). We currently have no support for IPersistPropertyBag.

A property bag is just what its name implies: a name/value pair that can be used by clients such as MS Internet Explorer to persist your control's state. By default, the ATL Object Wizard does not add the necessary support, so we need to update the generated code. To do so is simple: Extend your class's inheritance chain with an entry for IPersistPropertyBagImpl< > and add a COM map entry for IPersistPropertyBag.

Now that our control has full support for all types of COM persistence, we should edit our [propput] methods with one final addition. When you make a new assignment to the underlying variable of a COM property, call SetDirty() to mark that this property has changed. In this way, the client "understands" which items need to be persisted. Here is the final iteration of put_Animate(). Update the logic for put_PetName(), put_MaxSpeed(), and put_Speed() in the same way:

// Mark this property as dirty when we assign a new value. STDMETHODIMP CAxCar::put_Animate(AnimVal newVal) {      if(FAILED(FireOnRequestEdit(3)))     // [id(3)] = Animate           return S_FALSE;      // Set the animation property.      m_bAnimate = newVal;      if(::IsWindow(m_hWnd) && (m_bAnimate == Yes))           SetTimer(1, 250);      else if(::IsWindow(m_hWnd))           KillTimer(1);      // Set dirty flag.      SetDirty(TRUE);      // Tell container we have changed the value.      FireOnChanged(3);                    // [id(3)] = Animate      return S_OK; }

Now that we provide the ability to save and load our information, we must edit our property map. As of ATL 3.0, there is no "Edit Property Map Wizard," so we need to do this by hand (which is not a big deal). Open up your class's header file, find the property map, and update accordingly:

// Persisting our properties. BEGIN_PROP_MAP(CAxCar)      PROP_DATA_ENTRY("_cx", m_sizeExtent.cx, VT_UI4)      PROP_DATA_ENTRY("_cy", m_sizeExtent.cy, VT_UI4)      PROP_ENTRY("Animate", 3, CLSID_NULL)      PROP_ENTRY("Speed", 4, CLSID_NULL)      PROP_ENTRY("PetName", 5, CLSID_NULL)      PROP_ENTRY("MaxSpeed", 6, CLSID_NULL) END_PROP_MAP()

Step Three: Create a Custom Property Page

Our control has custom properties, and to be as user friendly as possible, we should create a custom property page to allow the end user to edit them at design time. First we need to decide which properties to make available. Let's allow the user to access the Animate, MaxSpeed, and PetName property from our custom page (but not the current speed). When you are building a page with ATL, the first step is to access the ATL Object Wizard and insert a new PropertyPage object from the Controls category. Using this tool, add a new page using ConfigureCar as the short name. From the String tab, edit the Title string to your liking. Recall that this string is used as the caption for the tab itself, and will be placed in your project's string table should you ever wish to change it. Close down the wizard when you are done. Next, we need to build a GUI front end using the new dialog template. We have three custom properties to provide access to. Given their functionality, it makes sense to add two edit boxes and a single check box. Here is a possible design:

click to expand
Figure 14-43: A custom property page.

To associate these widgets to the correct properties of IAxCar, begin by routing an EN_CHANGE notification for each edit box, as well as a BN_CLICKED notification to the check box, using the Add Windows Message Handler CASE tool. In the implementation of each handler, enable the Apply button by calling SetDirty():

// This SetDirty() method enables or disables the Apply button of the dialog box // hosting your page. LRESULT OnClickedCheckanim(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled) {      SetDirty(TRUE);      return 0; }

As for the logic behind the Apply() method, we need to fetch the values contained in each GUI widget, and send them into the correct property of the selected controls. Recall that IProperyPageImpl<> is maintaining an array of IUnknown pointers (m_ppUnk), which represents each control selected within the container. Use this array to update each object. The count of currently selected controls is maintained in the m_nObjects data member. Here is the necessary logic:

// Transfer the information in the widgets back to each control. STDMETHOD(Apply)(void) {      // Loop over all objects.      for (UINT i = 0; i < m_nObjects; i++)      {           // First set the pet name.           IAxCar* pCar = NULL;           CComBSTR temp;           GetDlgItemText( IDC_EDITNAME, temp.m_str);           m_ppUnk[i]->QueryInterface(IID_IAxCar, (void**)&pCar);           pCar->put_PetName(temp);           // Now set the animate prop.           pCar->put_Animate(                (AnimVal)IsDlgButtonChecked(IDC_CHECKANIM));           // And the max speed.           pCar->put_MaxSpeed(GetDlgItemInt(IDC_EDITSPEED));           pCar->Release();      }      m_bDirty = FALSE;      return S_OK; } 

Next, we need to "move data the other direction." When the user pulls up your property page, it would be nice it the current values in the control are placed in the correct widget. For example, the PetName should be plopped into the IDC_EDITNAME edit box, the Animate check box should reflect the current value, and so on. A common place to update these widgets is within the handler for a WM_INITDIALOG message, which is sent just before the dialog is made visible. Add a WM_INITDIALOG entry to your message map, and the following code:

// Set the widgets to the current values of the first selected control. LRESULT OnInitDialog(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) {      USES_CONVERSION;      // Show the values from the first selected item.      if (m_nObjects > 0)      {           IAxCar* pCar = NULL;           short tempSP;           CComBSTR temp;           // Get the first item selected.           m_ppUnk[0]->QueryInterface(IID_IAxCar, (void**)&pCar);           // Set the max Speed.           pCar->get_MaxSpeed(&tempSP);           SetDlgItemInt(IDC_EDITSPEED, tempSP);           // Now check Animate selection.           AnimVal curAnim;           pCar->get_Animate(&curAnim);           CheckDlgButton(IDC_CHECKANIM, (BOOL)curAnim);           // Finally, set the Pet Name.           pCar->get_PetName(&temp.m_str);           SetDlgItemText(IDC_EDITNAME, W2A(temp.m_str));           // All done!           pCar->Release();           SetDirty(FALSE);      }      return 0; }

For the final step, we need to once again go back to our property map and edit each exposed property to use our custom page, rather than CLSID_NULL (no page). The ATL Object Wizard has generated a CLSID for this page, which can be found at the top of the class's header file. Using this constant, update your control's property map as so:

// Rigging our properties to the correct page. BEGIN_PROP_MAP(CAxCar)      PROP_DATA_ENTRY("_cx", m_sizeExtent.cx, VT_UI4)      PROP_DATA_ENTRY("_cy", m_sizeExtent.cy, VT_UI4)      PROP_ENTRY("Animate", 3, CLSID_ConfigureCar)      PROP_ENTRY("Speed", 4, CLSID_NULL)      PROP_ENTRY("PetName", 5, CLSID_ConfigureCar)      PROP_ENTRY("MaxSpeed", 6, CLSID_ConfigureCar) END_PROP_MAP() 

Finally, we can test our new page. Place an instance of your updated (and recompiled) control into a VB project, and locate the (Custom) property from the VB Property Window (Figure 14-44):


Figure 14-44: Launching our page.

With this, the chapter comes to a close. If you want to extend the functionality of your ATL control, you may wish to put together a web-based test client, or use MFC, Delphi, or even MS Word to test your control (recall we did set the Insertable option). To insert your controls into a MS Office product, access the Insert | Object menu item and find your control (Figure 14-45). You can then create a VBA macro to exercise your CAxCar.

click to expand
Figure 14-45: Inserting our CAxCar into MS Word.

If you like, you may want to create a licensed class factory for your AXCar control (feel free to steal the initial code from the ShapesServer project found on your CD-ROM). As well, you may wish to add a category map and build a web-based tester.

Hopefully this chapter pulled together all the information you have gained throughout this book. You have seen how the ActiveX control requires a deep understanding of core COM, and how ATL can make your life easier.



 < Free Open Study > 



Developer's Workshop to COM and ATL 3.0
Developers Workshop to COM and ATL 3.0
ISBN: 1556227043
EAN: 2147483647
Year: 2000
Pages: 171

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