Using ATL to Create an Unmanaged COM Component

Working in COM can be intimidating when you first start, but parts of it move quickly from intimidating to boring. For example, every COM component must implement IUnknown . That's not hard after you have done it oncein fact, the code for IUnknown is the same in every COM component. So each project can start with a copy-and-paste session in which you bring over all the code that will be identical in this project. Yuck!

Both the intimidation and the boredom can be lessened dramatically with a library of some kind. Library code can provide the starting point or backbone for your project. If you don't know how to write that code, the library provides a reassuring starting point. If you know how to write the code and have written it many times before, the library spares you the pain of copying it from an old project into a new one.

This was the motivation behind ATL, the Active Template Library. It provides implementations for "stock" interfaces and makes it simple to build small, lightweight, fast COM components and controls. As you can probably tell from the name , it's a library of templates, but even if you're not an experienced template user , you'll find ATL quite straightforward.

A Sample ATL Component

ATL components should be simple and small. Typically they encapsulate business rules as part of a multi-tiered design. For demonstration purposes, a simple validation rule will do, such as for the format of a phone number. The component will have one method, ValidatePhoneNumber() , that takes a string as an input parameter and gives back two output parameters: an error code and an error message.

To create the component, you first create a project, and then add a component to it. Open Visual Studio and choose File, New, Project. Select Visual C++ projects and then ATL Project, and name the project PhoneFormat .

The ATL Project Wizard includes only one tab other than the Overview. By default, it creates the component as a DLL, so the component will be in-process with its client, for fastest possible execution. Click Finish to create the project.

The next step is to add a component to the project. Right-click the PhoneFormat project in Class View and choose Add, Add Class. Expand the Visual C++ node and select ATL underneath it. Select ATL Simple Object and click Open. On the ATL Simple Object Wizard's Names tab, enter a short name of PhoneNumber . All the other boxes will fill themselves in as you type. Click Finish to create the object.

The wizard gave this object a simple interface called IPhoneNumber . You need to add a method, ValidatePhoneNumber() , to the interface. Expand CPhoneNumber in the Class View and then expand Bases and Interfaces underneath that. Right-click IPhoneNumber and choose Add, Add Method.

The Add Method Wizard appears. Fill in the method name, ValidatePhoneNumber . Then add the parameters one at a time. First, add the input stringselect the In check box, choose a type of BSTR (the string type for COM programming), and a name of Number . Click Add. Next, add the first output parameterchoose a type of BYTE* , select the Out check box, enter a name of pError , and click Add. For the last parameter, choose a type of BSTR* , select the Out check box, enter a name of pErrorString , and click Add. The completed dialog box should resemble Figure 8.1. Click Finish.

Figure 8.1. Adding the method and its three parameters.

graphics/08fig01.gif

OUT AND RETVAL PARAMETERS

Until you have chosen a type that is a pointer, such as BSTR* or BYTE* , the Out and Retval check boxes cannot be selected.


In the Class View, find ValidatePhoneNumber underneath CPhoneNumber and double-click it to edit the code. There are lots of ways you can validate that a string is in a certain format. Because this is an ATL project, it makes sense to use the ATL regular expressions class, CAtlRegExp . Here's an implementation of the ValidatePhoneNumber() method:

 
 #include <atlrx.h> // CPhoneNumber STDMETHODIMP CPhoneNumber::ValidatePhoneNumber(BSTR Number, BYTE* pError,                                                 BSTR* pErrorString) {   USES_CONVERSION;   CAtlRegExp<> regexp;   CAtlREMatchContext<> Context; //companion for match   char* number = W2A(Number);   regexp.Parse( "\([0-9][0-9][0-9]\) [0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]"  );   if (regexp.Match( number , &Context ))   {       *pError = 0;       *pErrorString = SysAllocString(L"OK");   }   else   {       *pError = 1;       *pErrorString = SysAllocString(L"Bad Format");   }     return S_OK; } 

Most of what's going on here is pretty obvious, but the way COM handles strings is not obvious at all. This function takes a BSTR a binary string. The Match() function takes a C-style string: a char* . To convert between them, you can use the W2A macro. The macro USES_CONVERSION that you see at the top of the function defines it. A BSTR always uses wide characters , whereas an ordinary char* string uses ASCII charactershence the macro name, W2A for wide-to-ASCII.

This code returns the two output parameters by de-referencing the pointers passed into the function. The error code is simple: This function is given a BYTE* and you can simply de-reference it, writing *pError=0 or *pError=1 as appropriate. The BSTR* is more difficult: You can't simply assign a literal string to the BSTR . The function SysAllocString takes care of allocating the memory in which the string is to be held while it is returned to the calling program. The L macro converts an ordinary quoted string to wide characters.

COM components indicate success or failure by returning specific values. The return type of all COM methods in C++ is HRESULT . Returning the predefined S_OK value means that there is no error; the component was able to determine whether the phone number is valid or not. If, for example, the component needed to look up information in a database to perform the validation, when the database could not be reached the component could return a failure value, which is any value other than zero. Several such values are predefined, including E_INVALIDARG and E_FAIL , in winerror.h.

Enter this code into the implementation file and build the project. Don't forget to add the #include statement so that the ATL Regular Expressions header is used. Now the component is complete.



Microsoft Visual C++. NET 2003 Kick Start
Microsoft Visual C++ .NET 2003 Kick Start
ISBN: 0672326000
EAN: 2147483647
Year: 2002
Pages: 141
Authors: Kate Gregory

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