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.

Shared Classes with MFC

The Supports MFC check box on the ATL Project Wizard was always a temptation to the ATL 3 developer. We generally didn’t want the doc/view architecture, or windowing classes, or print preview framework, but we were sometimes willing to pay the price for them for the useful little utility classes, CString being first among them.

Thankfully, Microsoft has heard the ATL developer’s pain. As of ATL 7, the classes in Table C.1 are all available for use in both MFC and ATL projects, without having to drag the rest of MFC along with them.

Table C.1. Shared MFC and ATL Classes

CFileTime

CFileTimeSpan

CImage

COleDateTime

COleDateTimeSpan

CPoint

CRect

CSize

CTime

CTimeSpan

CString

Several new classes also are used in implementation or customization of CString. The CString class itself is now a template, CStringT, that can be parameterized by character type.

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 below

  • CatlExeModuleT. Module class for EXE COM servers

  • CatlDllModuleT. Module class for DLL COM servers

  • CatlServiceModuleT. 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 the HINSTANCE 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 use CWindowImpl. 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.