Craig McQueen
COM components should be small and fast. ATL meets these goals but has no provision for data structures and algorithms. The Standard Template Library fills this void while maintaining COM design goals.
The Visual C++ programmer has many tools to choose from to solve a programming problem. There's the language itself, the C runtime library, ATL, MFC, and a recent addition, the C++ Standard Library.
The C++ Standard Library has two parts: the C++ language itself and its standard library. The C++ Standard Library is relatively new to Visual C++, and in fact Microsoft has only gotten the bugs shaken out with the release of Visual C++ 5.0. The standard library provides standard input/output, strings, containers (such as vectors, lists, and maps), non-numerical algorithms (such as sort, search, and merge), and support for numeric computation. The part of the C++ Standard Library that implements containers and algorithms is the Standard Template Library (STL), and it's this library that partners well with ATL (and not just because the names are similar).
Why use ATL?ATL has been designed to be a fast, easy way to create COM components while maintaining a small memory footprint. C++ templates, which provide a type-safe way of implementing generic interfaces, are part of the key to accomplishing this. In fact, the design of ATL follows STL in its template use. Fortunately, Microsoft provides some wizards for generating ATL code, so you don't need to know about templates and you don't need to know the COM details for simple components. ATL components don't need DLLs, so you don't have to ship DLLs with the application or statically link, which increases the memory size.
MFC does rely on additional runtime DLLs (or statically linking) and also produces larger, slower code than ATL. So if you're building non-visual COM components (such as those used with Active Server Pages), ATL is a much better choice than MFC. But what about those data structures? You don't want to bring in all of MFC just because you need CString or CArray or another simple MFC class.
What is STL?STL is a framework of data structures, like vectors, lists, arrays, and algorithms, for searching, copying, and sorting those data structures. In July 1994, the ANSI/ISO C++ standards committee voted to adopt STL as part of the C++ Standard Library. The proposal was based on generic programming and generic software library research of Alex Stepanov, Meng Lee, and David Musser. STL was created with the design goal of generality with no performance penalty.
STL made its debut in Visual C++ 4.2. Unfortunately, this implementation was so buggy it was unusable. Some people (including me) got around this problem by using the Hewlett-Packard or SGI implementation of STL. Since STL is implemented as an ISO/ANSI standard, it's cross-platform (as long as the compiler supports the constructs required by STL). However, Microsoft's STL implementation with Visual C++ 5.0 is stable and easier to use.
STL is the perfect complement to ATL. ATL doesn't have any data structures or algorithms. STL and ATL have a similar implementation strategy—templates. Neither requires additional DLLs to be linked in, either statically or dynamically.
StringHelper componentWe're going to create a simple COM component that can reverse a string and trim a string. It can be tested in Visual Basic. Internally, we'll use the Standard C++ string template class for demonstration purposes. As well, we'll use a STL algorithm to do the reverse work for us. Strictly speaking, the string class isn't part of STL but is part of the Standard C++ library: The Standards Committee hasn't officially adopted the string template. However, Microsoft has fully implemented the string class in its implementation of the C++ Standard Library.
To build the sample, create a new Visual C++ project selecting "ATL COM AppWizard" and call it STLExample1. Leave the default server type "DLL", and leave the check boxes unchecked. Add a new ATL object to the component, select "Simple Object", and type "StringHelper" for the Short Name. The other fields will automatically fill in. If you want, you can change the first part of the Prog Id to VCD so you'll remember where you read about this. Leave the defaults on the attribute property page. Now you have an object to work with.
To use C++ exceptions, your control will need the C runtime library. To keep ATL components small, exceptions are disabled by default. STL uses exceptions, so we'll need to enable them. In the project settings on the C/C++ property page, drop down the category box to C++ language. Check the check box that says, "Enable exception handling."
Now, to give the component some functionality, you'll add methods. In the ClassView, right-click on the IStringHelper interface. Choose "Add method" from the menu. Fill out the dialog box as shown in Figure 1.
Figure 1. Add the Reverse() method
Change the beginning of StringHelper.cpp to look like the following:
// StringHelper.cpp : Implementation of CStringHelper
#include "stdafx.h"
#include "STLExample1.h"
#include "StringHelper.h"
#pragma warning(disable:4786)
#include <string>
#include <algorithm>
using namespace std;
There are a few important points to notice here. First, the #pragma statement disables warning 4786, an annoying warning that you'll see a lot of when you use STL. Microsoft recommends suppressing the warning this way rather than adjusting your warning level. Second, there's not a .h extension on the new header files. The STL is used by developers on a variety of platforms and compilers, some of whom name their header files with an .h extension, others with .hpp, .hxx, and the like. C++ Standard Library include files don't have any extensions at all, so everybody can be equally confused. This code includes the headers for the string template and an STL algorithm. Third, we're using the "std" namespace. Namespaces allow developers to use common class names like "string" without concern that someone else has named a class the same thing. All of the C++ Standard Library (including STL) is in the "std" namespace. You can call things by their full names—for example, std::string—or tell the compiler to use the namespace std and then just use the class name.
Now fill out the Reverse() method to look like the following:
STDMETHODIMP CStringHelper::Reverse(BSTR inString,
BSTR * outString)
{
wstring stlString = inString;
reverse( stlString.begin(), stlString.end() );
CComBSTR bStrRet( stlString.c_str() );
*outString = bStrRet.Detach();
return S_OK;
}
This code makes a wstring (wide-character string) from the BSTR passed to it. Both wstring and string are templated on basic_string, so they have the same methods and use the same code. The assignment is actually performed by the assignment operator overload provided for wstring. To reverse the order of the characters, just call the STL algorithm reverse() and specify the start and end positions to reverse. The methods begin() and end() specify the beginning and end of a STL string, respectively. The method c_str() returns the string as a const char *, and you can create an ATL CComBSTR object from that. Finally, detach the BSTR from CComBSTR and assign it to outString so it can be returned.
There are plenty of other useful string methods, and adding a Trim() method to the StringHelper component will show them off. Add the method to the interface as before, and fill out the dialog box as shown in Figure 2. This method will remove leading and trailing whitespace from a string. Somewhere at the top of StringHelper.cpp, add this line:
Figure 2. Add the Trim() method
#define SPACE_CHARS L" \n\t\r"
By using a preprocessor constant, you can change the characters you trim away quickly and easily.
Add this code for the Trim() method:
STDMETHODIMP CStringHelper::Trim(BSTR inString,
BSTR * outString)
{
wstring stlString = inString;
wstring::size_type startPos;
// Trim Left
startPos = stlString.find_first_not_of(SPACE_CHARS);
stlString = stlString.substr( startPos );
// Trim Right
reverse( stlString.begin(), stlString.end() );
startPos = stlString.find_first_not_of(SPACE_CHARS);
stlString = stlString.substr( startPos );
reverse( stlString.begin(), stlString.end() );
CComBSTR bStrRet( stlString.c_str() );
*outString = bStrRet.Detach();
return S_OK;
}
Again, the passed in BSTR is converted to a wstring. size_type is a typedef to be used for the size of a string or the position in a string. To trim left, use the method find_first_not_of(), and pass in the characters that are to be skipped. The actual trimming is done with the string method substr(). The easy route to trim the right side is to reverse the string and trim it left, then reverse it back. Detach and return the string as before.
StringHelper in actionOne way to test this little object is in VB. Make a form with two text fields and two buttons, labeled Trim and Reverse. Here's the code for their Click handlers:
Private Sub Reverse_Click()
Dim test As Object
Set test = CreateObject("VCD.StringHelper.1")
Text2.Text = test.Reverse(Text1.Text)
End Sub
Private Sub Trim_Click()
Dim test As Object
Set test = CreateObject("VCD.StringHelper.1")
Text2.Text = test.Trim(Text1.Text)
End Sub
Make sure you've built the object and that the registration succeeded. If you used a different object name, adjust this code. Figure 3 shows this test in action.
Figure 3. A VB app can use your StringHelper object.
ConclusionIf all you need is a little string manipulation in your ATL component, there's no need to use all of MFC. The Standard Template Library fits very well with ATL for creating COM components. In future articles, I'll introduce you to the data structures and algorithms of STL.
Download sample code here.Craig McQueen, MSc, led the development of InContext WebAnalyzer, product of the year award winner from PCComputing. He's currently a senior associate at Sage Information Consultants Inc. designing middle-tier COM components for e-commerce solutions. cmcqueen@sageconsultants.com.
Further Information