previous page
next page

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:


// ATL3Project.cpp : Implementation of WinMain
#include "stdafx.h"
#include "resource.h"
#include <initguid.h>
#include "ATL3Project.h"
#include "ATL3Project_i.c"

const DWORD dwTimeOut = 5000; const DWORD dwPause = 1000;

// Passed to CreateThread to monitor the shutdown event
static DWORD WINAPI MonitorProc(void* pv) {
  CExeModule* p = (CExeModule*)pv;
  p->MonitorShutdown();
  return 0;
}

LONG CExeModule::Unlock() {
  LONG l = CComModule::Unlock();
  if (l == 0) {
    bActivity = true;
    SetEvent(hEventShutdown); }
  return l;
}

// Monitors the shutdown event
void CExeModule::MonitorShutdown() {
  while (1) {
    WaitForSingleObject(hEventShutdown, INFINITE);
    DWORD dwWait=0;
    do {
      bActivity = false;
      dwWait = WaitForSingleObject(hEventShutdown, dwTimeOut);
    } while (dwWait == WAIT_OBJECT_0);
    // timed out
    if (!bActivity && m_nLockCnt == 0) { #if _WIN32_WINNT >= 0x0400 & defined(
_ATL_FREE_THREADED)
      CoSuspendClassObjects();
      if (!bActivity && m_nLockCnt == 0)
#endif
        break;
    }
  }
  CloseHandle(hEventShutdown);
    PostThreadMessage(dwThreadID, WM_QUIT, 0, 0);
  }
  bool CExeModule::StartMonitor() {
    hEventShutdown = CreateEvent(NULL, false, false, NULL);
    if (hEventShutdown == NULL) return false;
    DWORD dwThreadID;
    HANDLE h = CreateThread(NULL, 0, MonitorProc, this, 0,
      &dwThreadID);
    return (h != NULL);
  }

  CExeModule _Module;

  BEGIN_OBJECT_MAP(ObjectMap)
  END_OBJECT_MAP()

  LPCTSTR FindOneOf(LPCTSTR p1, LPCTSTR p2) {
    while (p1 != NULL && *p1 != NULL) {
      LPCTSTR p = p2;
      while (p != NULL && *p != NULL) {
        if (*p1 == *p) return CharNext(p1);
        p = CharNext(p);
      }
      p1 = CharNext(p1);
    }
    return NULL;
  }
  extern "C" int WINAPI _tWinMain(HINSTANCE hInstance,
    HINSTANCE /*hPrevInstance*/,
    LPTSTR lpCmdLine, int /*nShowCmd*/) {
    lpCmdLine = GetCommandLine();

  #if _WIN32_WINNT >= 0x0400 & defined(_ATL_FREE_THREADED)
    HRESULT hRes = CoInitializeEx(NULL, COINIT_MULTITHREADED);
  #else
    HRESULT hRes = CoInitialize(NULL);
  #endif
    _ASSERTE(SUCCEEDED(hRes));
    _Module.Init(ObjectMap, hInstance, &LIBID_ATL3PROJECTLib);
    _Module.dwThreadID = GetCurrentThreadId();
    TCHAR szTokens[] = _T("-/");
    int nRet = 0;
    BOOL bRun = TRUE;
  LPCTSTR lpszToken = FindOneOf(lpCmdLine, szTokens);
  while (lpszToken != NULL) {
    if (lstrcmpi(lpszToken, _T("UnregServer"))==0) {
      _Module.UpdateRegistryFromResource(IDR_ATL3Project, FALSE);
      nRet = _Module.UnregisterServer(TRUE);
      bRun = FALSE;
      break;
    }
    if (lstrcmpi(lpszToken, _T("RegServer"))==0) {
      _Module.UpdateRegistryFromResource(IDR_ATL3Project, TRUE);
      nRet = _Module.RegisterServer(TRUE);
      bRun = FALSE;
      break;
    }
    lpszToken = FindOneOf(lpszToken, szTokens);
  }

  if (bRun) {
    _Module.StartMonitor();
#if _WIN32_WINNT >= 0x0400 & defined(_ATL_FREE_THREADED)
    hRes = _Module.RegisterClassObjects(CLSCTX_LOCAL_SERVER,
      REGCLS_MULTIPLEUSE | REGCLS_SUSPENDED);
    _ASSERTE(SUCCEEDED(hRes));
    hRes = CoResumeClassObjects();
#else
    hRes = _Module.RegisterClassObjects(CLSCTX_LOCAL_SERVER,
      REGCLS_MULTIPLEUSE);
#endif
    _ASSERTE(SUCCEEDED(hRes));

    MSG msg;
    while (GetMessage(&msg, 0, 0, 0)) DispatchMessage(&msg);
    _Module.RevokeClassObjects();
    Sleep(dwPause); //wait for any threads to finish
  }

  _Module.Term();
  CoUninitialize();
  return nRet;
}

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:

// ATL8Project.cpp : Implementation of WinMain
#include "stdafx.h"
#include "resource.h"
#include "ATL8Project.h"

class CATL8ProjectModule :
  public CAtlExeModuleT< CATL8ProjectModule > {
public :
  DECLARE_LIBID(LIBID_ATL8ProjectLib)
  DECLARE_REGISTRY_APPID_RESOURCEID(IDR_ATL8PROJECT,
    "{06CE2511-F6E0-4F80-9BC9-852A433E7B25}")
};

CATL8ProjectModule _AtlModule;

extern "C" int WINAPI _tWinMain(
  HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/,
  LPTSTR /*lpCmdLine*/, int nShowCmd) {
  return _AtlModule.WinMain(nShowCmd);
}

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.


previous page
next page
Converted from CHM to HTML with chm2web Pro 2.75 (unicode)