Strings, Character
Sets, and Conversions
Several important changes have been made in the
way that ATL handles strings. Everything you know from ATL 3 still
works, but you should consider upgrading your code to use the new
features.
Character-Conversions Macros
ATL 3 provided a set of macros that make it
easier to convert among various character sets: ANSI, Multibyte,
Wide, and the mutable TCHAR type. To use the macros, you first had
to include the USES_CONVERSION macro in the code above the
first use:
void PrintMessage( LPCTSTR lpszMessage ) {
USES_CONVERSION;
std::cout << "Your message is " << T2A(lpszMessage) << endl;
}
The conversion macros are convenient, but the
implementation had a couple of problems. The storage for the
conversion buffer was allocated on the program stack (via the
_alloca function), which made cleanup automatic.
Unfortunately, if you called the conversion macros in a loop, none
of the memory was cleaned up until the function containing that
loop returned. As a result, you could overrun your stack if you
weren't careful. The use of _alloca also changed the stack
layout enough that if you used a conversion macro inside a C++
catch { } block, your program would probably crash.
Finally, if you forgot the USES_CONVERSION macro, you got
some strange error messages.
To
address these problems, ATL 7 introduced a new set of conversion
classes. Each one has the same
name as the old conversion macro, with a C on the front. The previous example becomes
this with the class:
void PrintMessage( LPCTSTR lpszMessage ) {
std::cout << "Your message is " << CT2A(lpszMessage) << endl;
}
The conversion classes do not require the
USES_CONVERSION macro and are actually C++ classes instead
of macro trickery. Each object has a small internal buffer that is
used to store the converted string if the string is short; for
longer conversions, a heap allocation is done. The conversion
class's destructor makes sure to clean up any allocation.
Using the conversion classes instead of the
macros avoids doing weird things to the stack, and as a result, you
get more stable code and fewer weird corner cases (such as the
crash in catch blocks). See Chapter 2, "Strings and Text," for more
information on the new conversion classes.
Security Enhanced
C Standard Library
This one isn't really an ATL change, but it's a
new feature of Visual C++ 8. It's also something that all C and C++
developers should pay attention to, especially web developers,
because this feature is now well supported in ATL via ATL Server.
The Internet is a hostile place and is only getting more so as time
goes on. One of the most common security holes in unmanaged code is
the buffer overrun; data is copied into a fixed size array that's
too small, and the data runs over the end of the array and puts
carefully crafted bytes on the stack that do whatever the attacker
wants them to do.
If you use the standard C library, particularly
the string functions (the old standbys such as strcpy and
strcat), it turns out to be remarkably difficult to avoid
buffer overflows. These factors contribute to buffer overflows:
-
No buffer
sizes. strcpy provides no way to indicate how large
the buffer is that you're copying into.
-
Inconsistent string
termination. strncpy does let you specify how long
the destination buffer is, but if your buffer is too long, it
doesn't actually put the \0 byte at the end to terminate
the string. In general, the C string functions are either
underspecified about string termination or just unpredictable.
-
Inadequate parameter
checking. Most of these functions handle NULL
pointers unpredictably.
-
Interface
inconsistency. Some functions put the destination in the
first parameter; others put it in the second. It's easy to mix up
source and destination buffers because they're both variables of
type char *.
As part of the recent security push at
Microsoft, a new set of secure string functions was written and
included in the C runtime library.
They're named with an "_s" suffix, so they are functions such as
strcat_s, and strcpy_s.
Why does this concern ATL developers? There are
two reasons. The first, less interesting one is that the majority
of changes from ATL 7 to ATL 8 were replacements of string function
calls with these new secure string functions. The second, and more
important, one is that when you compile your old code with ATL 8,
you now get compiler warnings for calls to the old insecure
functions saying that they're deprecated and what function to
replace them with. For example, this code
void BuildUrl( char *dest, size_t dest_len, char *page ) {
strncpy( dest, "http://localhost/Sample", dest_len );
strncat( dest, page, dest_len );
strncat( dest, "/default.srf", dest_len );
}
now results in these compile-time warnings:
c:\atlinternals2e\src\appendixc\unsafestringhandling\unsafestringhandling.cpp
(72) : warning C4996: 'strncpy' was declared deprecated
c:\program files\microsoft visual studio 8\vc\include\string.h(156) :
see declaration of 'strncpy'
Message: 'This function or variable may be unsafe. Consider using
strncpy_s instead. To disable deprecation, use _CRT_SECURE_NO_DEPRECATE.
See online help for details.'
c:\atlinternals2e\src\appendixc\unsafestringhandling\unsafestringhandling.cpp
(73) : warning C4996: 'strncat' was declared deprecated
c:\program files\microsoft visual studio 8\vc\include\string.h(143) :
see declaration of 'strncat'
Message: 'This function or variable may be unsafe. Consider using
strncat_s instead. To disable deprecation, use _CRT_SECURE_NO_DEPRECATE.
See online help for details.'
c:\atlinternals2e\src\appendixc\unsafestringhandling\unsafestringhandling.cpp
(74) : warning C4996: 'strncat' was declared deprecated
c:\program files\microsoft visual studio 8\vc\include\string.h(143) :
see declaration of 'strncat'
Message: 'This function or variable may be unsafe. Consider using
strncat_s instead. To disable deprecation, use _CRT_SECURE_NO_DEPRECATE.
See online help for details.'
You
can turn off the deprecation warnings (using the
_CRT_SECURE_NO_DEPRECATE preprocessor flag, as shown in
the warning message), but it's generally a better idea to fix the
code. For example, the previous code using the secure string
functions looks like this:
void SafeBuildUrl( char * dest, size_t dest_len, char *page ) {
// _TRUNCATE is a special value that tells the function to
// copy as much of source as will fit in dest and still have a
// \0 terminator.
strncpy_s( dest, dest_len, "http://localhost/Sample",
_TRUNCATE );
strncat_s( dest, dest_len, page, _TRUNCATE );
strncat_s( dest, dest_len, "/default.srf", _TRUNCATE );
}
The strncat_s function also has the
nice feature of automatically figuring out how much room is left in
the destination buffer, and it avoids many off-by-one (or
off-by-many, in the case of the broken code shown previously)
errors.
Chapter
2 discusses use of these functions more.
|