Appendix C. Moving to ATL 8
This appendix covers the significant changes that ATL developers deal with when moving projects from ATL 3 to ATL 8. We talk specifically about externally visible changespotential porting issues, bug fixes, and new featuresrather than about internal changes (the rest of the book is for that). The changes from ATL 7 to ATL 8 are extremely minor (a couple of bug fixes and removal of some insecure string calls) and don’t require any discussion; moving from ATL 7 to ATL 8 just works.
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:
1void PrintMessage( LPCTSTR lpszMessage ) {
2 USES_CONVERSION;
3 std::cout << "Your message is " << T2A(lpszMessage) << endl;
4}
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:
1void PrintMessage( LPCTSTR lpszMessage ) {
2 std::cout << "Your message is " << CT2A(lpszMessage) << endl;
3}
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. [1] They’re named with an “_s” suffix, so they are functions such as strcat_s, and strcpy_s. [2]
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
1void BuildUrl( char *dest, size_t dest_len, char *page ) {
2 strncpy( dest, "http://localhost/Sample", dest_len );
3 strncat( dest, page, dest_len );
4 strncat( dest, "/default.srf", dest_len );
5}
now results in these compile-time warnings:
1c:\atlinternals2e\src\appendixc\unsafestringhandling\unsafestringhandling.cpp
2 (72) : warning C4996: 'strncpy' was declared deprecated
3c:\program files\microsoft visual studio 8\vc\include\string.h(156) :
4 see declaration of 'strncpy'
5Message: 'This function or variable may be unsafe. Consider using
6 strncpy_s instead. To disable deprecation, use _CRT_SECURE_NO_DEPRECATE.
7 See online help for details.'
8c:\atlinternals2e\src\appendixc\unsafestringhandling\unsafestringhandling.cpp
9 (73) : warning C4996: 'strncat' was declared deprecated
10c:\program files\microsoft visual studio 8\vc\include\string.h(143) :
11 see declaration of 'strncat'
12Message: 'This function or variable may be unsafe. Consider using
13 strncat_s instead. To disable deprecation, use _CRT_SECURE_NO_DEPRECATE.
14 See online help for details.'
15c:\atlinternals2e\src\appendixc\unsafestringhandling\unsafestringhandling.cpp
16 (74) : warning C4996: 'strncat' was declared deprecated
17c:\program files\microsoft visual studio 8\vc\include\string.h(143) :
18 see declaration of 'strncat'
19Message: 'This function or variable may be unsafe. Consider using
20 strncat_s instead. To disable deprecation, use _CRT_SECURE_NO_DEPRECATE.
21 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:
1void SafeBuildUrl( char * dest, size_t dest_len, char *page ) {
2 // _TRUNCATE is a special value that tells the function to
3 // copy as much of source as will fit in dest and still have a
4 // \0 terminator.
5 strncpy_s( dest, dest_len, "http://localhost/Sample",
6 _TRUNCATE );
7 strncat_s( dest, dest_len, page, _TRUNCATE );
8 strncat_s( dest, dest_len, "/default.srf", _TRUNCATE );
9}
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.
Implementing COM Servers
CComModule
Every ATL 3 project required a global instance
of the CComModule
class (or class derived from
CComModule
), and that global variable had to be named
_Module
. CComModule
was responsible for
maintaining global information about the COM server: what class
factories were active, managing module handles to get resources,
the lock count, the threading model, and so on. ATL 3 depended on
the Project Wizard to generate specialized versions of the
CComModule
class through inheritance to handle an EXE
versus a DLL versus a Windows Service.
In ATL 7, the CComModule
was broken up,
and several derived classes were introduced that reduced the amount
of code that needed to be generated. Every ATL project still has a
global module object; that object is now accessible through the
_pAtlModule
global variable (and, yes, the leading
p indicates that this is now a
pointer).
ATL 3 included a single CComModule
class, and the ATL Project Wizard created a derived class in your
project that handled being an EXE or DLL COM server. This was
problematic because Microsoft can’t fix or update the generated
code when it ships new versions of ATL. Thus, several different
module classes were introduced that pulled the COM server details
into the library and out of the generated code (where it should
have been in the first place).
These are the new module classes:
CatlModule
. Base class for the COM server type-specific modules belowCatlExeModuleT
. Module class for EXE COM serversCatlDllModuleT
. Module class for DLL COM serversCatlServiceModuleT
. Module class for Windows Service COM servers
The updated module classes take a burden off the Project Wizard. A typical starting file for an ATL 3 exe server looks like this:
1// ATL3Project.cpp : Implementation of WinMain
2#include "stdafx.h"
3#include "resource.h"
4#include <initguid.h>
5#include "ATL3Project.h"
6#include "ATL3Project_i.c"
7
8const DWORD dwTimeOut = 5000; const DWORD dwPause = 1000;
9
10// Passed to CreateThread to monitor the shutdown event
11static DWORD WINAPI MonitorProc(void* pv) {
12 CExeModule* p = (CExeModule*)pv;
13 p->MonitorShutdown();
14 return 0;
15}
16
17LONG CExeModule::Unlock() {
18 LONG l = CComModule::Unlock();
19 if (l == 0) {
20 bActivity = true;
21 SetEvent(hEventShutdown); }
22 return l;
23}
24
25// Monitors the shutdown event
26void CExeModule::MonitorShutdown() {
27 while (1) {
28 WaitForSingleObject(hEventShutdown, INFINITE);
29 DWORD dwWait=0;
30 do {
31 bActivity = false;
32 dwWait = WaitForSingleObject(hEventShutdown, dwTimeOut);
33 } while (dwWait == WAIT_OBJECT_0);
34 // timed out
35 if (!bActivity && m_nLockCnt == 0) {
36#if _WIN32_WINNT >= 0x0400 & defined(_ATL_FREE_THREADED)
37 CoSuspendClassObjects();
38 if (!bActivity && m_nLockCnt == 0)
39#endif
40 break;
41 }
42 }
43 CloseHandle(hEventShutdown);
44 PostThreadMessage(dwThreadID, WM_QUIT, 0, 0);
45 }
46 bool CExeModule::StartMonitor() {
47 hEventShutdown = CreateEvent(NULL, false, false, NULL);
48 if (hEventShutdown == NULL) return false;
49 DWORD dwThreadID;
50 HANDLE h = CreateThread(NULL, 0, MonitorProc, this, 0,
51 &dwThreadID);
52 return (h != NULL);
53 }
54
55 CExeModule _Module;
56
57 BEGIN_OBJECT_MAP(ObjectMap)
58 END_OBJECT_MAP()
59
60 LPCTSTR FindOneOf(LPCTSTR p1, LPCTSTR p2) {
61 while (p1 != NULL && *p1 != NULL) {
62 LPCTSTR p = p2;
63 while (p != NULL && *p != NULL) {
64 if (*p1 == *p) return CharNext(p1);
65 p = CharNext(p);
66 }
67 p1 = CharNext(p1);
68 }
69 return NULL;
70 }
71 extern "C" int WINAPI _tWinMain(HINSTANCE hInstance,
72 HINSTANCE /*hPrevInstance*/,
73 LPTSTR lpCmdLine, int /*nShowCmd*/) {
74 lpCmdLine = GetCommandLine();
75
76 #if _WIN32_WINNT >= 0x0400 & defined(_ATL_FREE_THREADED)
77 HRESULT hRes = CoInitializeEx(NULL, COINIT_MULTITHREADED);
78 #else
79 HRESULT hRes = CoInitialize(NULL);
80 #endif
81 _ASSERTE(SUCCEEDED(hRes));
82 _Module.Init(ObjectMap, hInstance, &LIBID_ATL3PROJECTLib);
83 _Module.dwThreadID = GetCurrentThreadId();
84 TCHAR szTokens[] = _T("-/");
85 int nRet = 0;
86 BOOL bRun = TRUE;
87 LPCTSTR lpszToken = FindOneOf(lpCmdLine, szTokens);
88 while (lpszToken != NULL) {
89 if (lstrcmpi(lpszToken, _T("UnregServer"))==0) {
90 _Module.UpdateRegistryFromResource(IDR_ATL3Project, FALSE);
91 nRet = _Module.UnregisterServer(TRUE);
92 bRun = FALSE;
93 break;
94 }
95 if (lstrcmpi(lpszToken, _T("RegServer"))==0) {
96 _Module.UpdateRegistryFromResource(IDR_ATL3Project, TRUE);
97 nRet = _Module.RegisterServer(TRUE);
98 bRun = FALSE;
99 break;
100 }
101 lpszToken = FindOneOf(lpszToken, szTokens);
102 }
103
104 if (bRun) {
105 _Module.StartMonitor();
106#if _WIN32_WINNT >= 0x0400 & defined(_ATL_FREE_THREADED)
107 hRes = _Module.RegisterClassObjects(CLSCTX_LOCAL_SERVER,
108 REGCLS_MULTIPLEUSE | REGCLS_SUSPENDED);
109 _ASSERTE(SUCCEEDED(hRes));
110 hRes = CoResumeClassObjects();
111#else
112 hRes = _Module.RegisterClassObjects(CLSCTX_LOCAL_SERVER,
113 REGCLS_MULTIPLEUSE);
114#endif
115 _ASSERTE(SUCCEEDED(hRes));
116
117 MSG msg;
118 while (GetMessage(&msg, 0, 0, 0)) DispatchMessage(&msg);
119 _Module.RevokeClassObjects();
120 Sleep(dwPause); //wait for any threads to finish
121 }
122
123 _Module.Term();
124 CoUninitialize();
125 return nRet;
126}
All this code was output from the Project Wizard. It includes the logic for managing shutdown of multiple threads, the server object count, command-line parsing, and the main Windows message loop. Although it’s nice to have the code out there to modify if you need it, it’s really too much for code generation; most of this should have been part of the library.
The picture is much better in ATL 8, where an EXE server starts out as follows:
1// ATL8Project.cpp : Implementation of WinMain
2#include "stdafx.h"
3#include "resource.h"
4#include "ATL8Project.h"
5
6class CATL8ProjectModule :
7 public CAtlExeModuleT< CATL8ProjectModule > {
8public :
9 DECLARE_LIBID(LIBID_ATL8ProjectLib)
10 DECLARE_REGISTRY_APPID_RESOURCEID(IDR_ATL8PROJECT,
11 "{06CE2511-F6E0-4F80-9BC9-852A433E7B25}")
12};
13
14CATL8ProjectModule _AtlModule;
15
16extern "C" int WINAPI _tWinMain(
17 HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/,
18 LPTSTR /*lpCmdLine*/, int nShowCmd) {
19 return _AtlModule.WinMain(nShowCmd);
20}
As you can see, the ATL 8 module class does a
lot more heavy lifting here; all the generated code does is declare
a module class derived from CAtlExeModuleT
, add the
DECLARE_LIBID
and
DECLARE_REGISTRY_APPID_RESOURCEID
macros to hook up some
metadata, and declare a one-line _tWinMain
function that
calls into the module class. This makes the code we have to edit
significantly shorter. The flexibility you need is still there,
however, because CAtlExeModuleT
has several functions that
you can override to intercept various parts of the startup and
shutdown process. This way, it’s obvious what you’re changing, and
your changes aren’t buried in 171 lines of generated code. Also, if
you’re using the ATL 8 module classes, if you need to move between
an out-of-process server and an in-proc server, it’s much easier to
do.
Having said that, there’s not much to be gained in removing your ATL 3 modules and replacing them with the ATL 8 modules when you migrate your project. With all the generated code, there’s a good possibility that you’ve modified at least some of it, and it’s hard to tell exactly what’s changed. If you need (on moral or ethical grounds, let’s say) to rewrite the module-related code in your ATL 3 server to match an ATL 8 server, your best bet is to run the new VS05 wizard with the names and settings you need and then move over your ATL 3 classes.
ATL 8 also includes several other module classes that deal with other common functionality, such as loading resources or managing window thunks. You typically don’t need to touch them in your code; they’re used as part of the internals of ATL 8. You will never need to create instances of these module classes because they’re defined as global variables and compiled into your project if they’re needed.
These additional module classes include the following:
CAtlBaseModule
. Contains information that most applications need. The most important is theHINSTANCE
of the module and the resource instance. Accessible as the global variable_AtlBaseModule
.CAtlComModule
. Contains information needed for all the COM classes in ATL. Accessible via the global variable_AtlComModule
.CAtlWinModule
. Contains information needed to manage window thunks created when you useCWindowImpl
. Accessible via the global variable_AtlWin-Module
.
Take a look at the documentation for “ATL Module Classes” for more details on what each of these doesand, of course, read Chapters 4, “Objects in ATL,” and 5, “COM Servers,” of this book.
The OBJECT_MAP
ATL 3 projects required a global
OBJECT_MAP
in each COM server project. This map provided a
central list of all the COM classes in the project and was used to
drive both self-registration and the class factories.
The OBJECT_MAP
worked, but it was yet
another piece of metadata in an ATL 3 project. When you added a new
COM object to your project, you had to remember to update the
OBJECT_MAP
. Forgetting meant lots of head-scratching
trying to figure out why you were getting “class not registered”
errors.
The centralized OBJECT_MAP
also had a
compile-time impact. To add the classes to the object map, you
needed to include every header for every COM object to your
project’s main file. With the complexity of the average ATL header
file, this could result in a significant compile-time slowdown.
ATL 8 rectifies all these problems with some
clever linker tricks (described in Chapter 5). Instead of a central
OBJECT_MAP
, each class’s header file has an
OBJECT_ENTRY_AUTO
macro in it. This macro is set up so
that the linker automatically builds the OBJECT_MAP
data
structure.
Projects can have a combination of the old
OBJECT_MAP
and the OBJECT_ENTRY_AUTO
macros. As
you upgrade your code to ATL 8, it’s a good idea to add the
OBJECT_ENTRY_AUTO
macros because it will make maintenance
easier. After you add the last one, you can get rid of the
OBJECT_MAP
entirely.
ActiveX Controls and Control Hosting
ATL is still the best way to implement ActiveX controls, and you don’t need to change anything to get your ATL 3 ActiveX controls to compile under ATL 8. In addition, the ActiveX control-hosting support has been improved significantly in ATL 8.
Readers of the previous edition of this book might remember that the chapters on ActiveX controls and control hosting spent several pages describing bugs in ATL 3, and the code downloads included functions and classes to work around these errors. I’m very happy to report that somebody at Microsoft apparently read a copy of ATL Internals – most of those bugs have been fixed.
Stock properties now provide a callback function
that you can override to detect when the stock property is changed.
This means that you no longer have to completely replace the
put_XXXX
stock property methods that the
CStockPropImpl
base class provides.
The AtlIPersistPropertyBag_Load
method
has been fixed, so you no longer need the updated function included
with the first edition downloads.
The ActiveX hosting classes have been
significantly cleaned up. The first edition of this book included a
class named CAxWindow2
that fixed several memory leaks in
the control hosting. These leaks have now been fixed. ATL 8 also
includes a class named CAxWindow2
; luckily, it’s a drop-in
replacement for the ATL Internals class of the same name, so all
you need to do is remove the patched version from your project. ATL
8 ActiveX Hosting also now supports the creation of licensed
ActiveX controls. The new CAxWindow2
class provides the
support for creating licensed controls, but it works just fine for
nonlicensed controls as well.
Take a look at Chapters 11, “ActiveX Controls,” and 12, “Control Containment,” in this edition for the full scoop on ActiveX controls.
ATL_MIN_CRT Changes
The infamous ATL_MIN_CRT
preprocessor
switch has been the source of controversy for many years. In ATL 7,
Microsoft made some improvements to the ATL startup code that made
ATL_MIN_CRT
compile a little more useful. The most
important change is that ATL_MIN_CRT
builds now support
correct construction and destruction of static and global C++
objects at module startup and shutdown.
Here’s another important thing to note when
starting an ATL project: In ATL 3, debug projects had
ATL_MIN_CRT
turned off, and release projects had it turned
on. In ATL 8, both debug and release projects default to having
ATL_MIN_CRT
turned off. This is a useful change in my
opinionjust about every ATL 3 project got the dreaded “cannot find
_main” error the first time it did a Release mode build.
Summary
The ATL team has done a remarkable job in maintaining backward-compatibility, while improving the design and capabilities of ATL in Version 8. Most ATL 3 code simply compiles and works right out of the box. The projects that don’t compile can be corrected very quickly, and ATL 8 offers significant new features to make building COM objects in C++ easier than ever.