ATL Services

To create your ATL COM object so that it runs in a service, simply select Service from the list of server options in the ATL COM AppWizard. The wizard will then create a CServiceModule class to implement the service.

The first four sections of this article discuss the actions that occur during execution of CServiceModule member functions. These topics appear in the same sequence as the functions are typically called. To improve your understanding of these topics, it is a good idea to use the source code generated by the ATL COM AppWizard as reference. These first four sections are:

The last three sections of this article discuss concepts related to developing a service:


CServiceModule::Start

The WinMain routine handles both registration and installation, as well as deregistration and uninstallation. When the service is run, WinMain calls CServiceModule::Start.

CServiceModule::Start sets up an array of SERVICE_TABLE_ENTRY structures that map each service to its startup function. This array is then passed to the Win32 API function, StartServiceCtrlDispatcher. In theory, one EXE could handle multiple services and the array could have multiple SERVICE_TABLE_ENTRY structures. Currently, however, an ATL-generated service supports only one service per EXE. Therefore, the array has a single entry that contains the service name and _ServiceName as the startup function. _ServiceName is a static member function of CServiceModule that calls the non-static member function, ServiceName.

Note Failure of StartServiceCtrlDispatcher to connect to the service control manager (SCM) probably means that the program is not running as a service. In this case, the program calls CServiceModule::Run directly so that the program can run as a local server. For more information about running the program as a local server, see Debugging Tips.

Back to top


CServiceModule::ServiceMain

The SCM calls ServiceMain when you open the Services Control Panel application, select the service, and click Start.

After the SCM calls ServiceMain, a service must give the SCM a handler function. This function lets the SCM obtain the service's status and pass specific instructions (such as pausing or stopping). The SCM gets this function when the service passes _Handler to the Win32 API function, RegisterServiceCtrlHandler. (_Handler is a static member function that calls the non-static member function Handler.)

At startup, a service should also inform the SCM of its current status. It does this by passing SERVICE_START_PENDING to the Win32 API function, SetServiceStatus.

Now, CServiceModule::Run is called to perform the main work of the service. Run continues to execute until the service is stopped.

Back to top


CServiceModule::Run

After being called, Run first stores the service's thread ID. The service will use this ID to close itself by sending a WM_QUIT message using the Win32 API function PostThreadMessage.

Run then calls the Win32 API function, CoInitializeEx. By default, Run passes the COINIT_MULTITHREADED flag to the function. This flag indicates that the program is to be a free-threaded server.

Now you can specify security using CSecurityDescriptor. This class greatly simplifies the task of setting up and making changes to the discretionary access-control list (DACL)—a list of access-control entries (ACEs), where each ACE defines access to a Win32 object.

By default, the ATL COM AppWizard generates a call to the InitializeFromThreadToken member function of CSecurityDescriptor. This initializes the object's security descriptor to a null DACL, which means that any user has access to your object.

The easiest way to change user access is with the Deny and Allow member functions of CSecurityDescriptor. These functions add an ACE to the existing DACL. However, Deny always takes priority since Deny adds the ACE to the beginning of the DACL, while Allow adds it to the end. Both Deny and Allow pass the user name as the first parameter and the access rights (typically, COM_RIGHTS_EXECUTE) as the second.

Recall that the null DACL created by InitializeFromThreadToken grants all users access to the COM object. However, as soon as you call Allow to add an ACE, only that specified user will have access. The following code shows a call to Allow:

CSecurityDescriptor sd; 
sd.InitializeFromThreadToken( ); 
if (bAllowOneUser) 
{ 
   sd.Allow("MYDOMAIN\\myuser", COM_RIGHTS_EXECUTE); 
} 
CoInitializeSecurity(sd, -1, NULL, NULL, 
                     RPC_C_AUTHN_LEVEL_PKT, 
                     RPC_C_IMP_LEVEL_IMPERSONATE,
                     NULL, EOAC_NONE, NULL); 

If the variable, bAllowOneUser, is TRUE, then only the one specified user has access because only that user's ACE is in the DACL. If bAllowOneUser is FALSE, then all users have access because the DACL is null.

If you do not want the service to specify its own security, remove the call to the Win32 API function, CoInitializeSecurity, and COM will then determine the security settings from the registry. A convenient way to configure registry settings is with the DCOMCNFG utility discussed later in this article.

Once security is specified, the object is registered with COM so that new clients can connect to the program. Finally, the program tells the SCM that it is running and the program enters a message loop. The program remains running until it posts a quit message upon service shutdown.

For more information about Windows NT security, see the MSDN article, "Windows NT Security in Theory and Practice."

Back to top


CServiceModule::Handler

CServiceModule::Handler is the routine that the SCM calls to retrieve the status of the service and give it various instructions (such as stopping or pausing). The SCM passes an operation code to Handler to indicate what the service should do. A default ATL-generated service only handles the stop instruction. If the SCM passes the stop instruction, the service tells the SCM that the program is about to stop. The service then calls PostThreadMessage to post a quit message to itself. This terminates the message loop and the service will ultimately close.

To handle more instructions, you need to change the dwControlsAccepted data member initialized in the CServiceModule::Init member function. This data member tells the SCM which buttons to enable when the service is selected in the Services Control Panel application.

Back to top


Registry Entries

DCOM introduced the concept of Application IDs (AppIDs), which group configuration options for one or more DCOM objects into a centralized location in the registry. You specify an AppID by indicating its value in the AppID named value under the object's CLSID.

By default, an ATL-generated service uses its CLSID as the GUID for its AppID. Under HKEY_CLASSES_ROOT\AppID, you can specify DCOM-specific entries. Initially, two entries exist:

Any DCOM service also needs to create another key under HKEY_CLASSES_ROOT\AppID. This key is equal to the name of the EXE and acts as a cross-reference, as it contains an AppID value pointing back to the AppID entries.

Back to top


DCOMCNFG

DCOMCNFG is a Windows NT 4.0 utility that allows you to configure various DCOM-specific settings in the registry. The DCOMCNFG window has three pages: Default Security, Default Properties, and Applications.

The Default Security Page

You can use the Default Security page to specify default permissions for objects on the system. The Default Security page has three sections: Access, Launch, and Configuration. To change a section's defaults, click on the corresponding Edit Default button. These default security settings are stored in the registry under HKEY_LOCAL_MACHINE\Software\Microsoft\OLE.

The Default Properties Page

On the Default Properties page, you must select the Enable Distributed COM on this Computer check box if you want clients on other machines to access COM objects running on this machine. Selecting this option sets the HKEY_LOCAL_MACHINE\Software\Microsoft\OLE\EnableDCOM value to Y.

The Applications Page

You change the settings for a particular object with the Applications page. Simply select the application from the list and click the Properties button. The Properties window has four page:

Back to top


Debugging Tips

The following paragraphs outline some useful steps for debugging your service:

Using Task Manager

One of the simplest ways to debug a service is through the use of the Task Manager in Windows NT 4.0. While the service is running, start the Task Manager and click on the Processes tab. Use the right mouse button to click on the name of the EXE and then click Debug. This launches Developer Studio attached to that running process. Now, click Break on the Debug menu to allow you to set breakpoints in your code. Click Run to run to your selected breakpoints.

Using DebugBreak

You can have your program call the DebugBreak function at the point in your code that you want debugging to start. Calling this function causes the program to display a dialog box as if it had crashed. Click Cancel to start the debugger and continue on in debug mode.

Displaying Assertions

If the client connected to your service appears to hang, the service may have asserted and displayed a message box that you are not able to see. You can confirm this by using Developer Studio's debugger to debug your code (see Using Task Manager earlier in this section).

If you determine that your service is displaying an invisible message box, you may want to set the Allow Service to Interact with Desktop option before using the service again. This option is a startup parameter that permits any message boxes displayed by the service to appear on the desktop. To set this option, open the Services Control Panel application, select the service, click Startup, and then select the Allow Service to Interact with Desktop option.

Running the Program as a Local Server

If running the program as a service is inconvenient, you can temporarily change the registry so that the program is run as a normal local server. Simply rename the LocalService value under your AppID to _LocalService and ensure the LocalServer32 key under your CLSID is set correctly. (Note that using DCOMCNFG to specify that your application should be run on a different computer renames your LocalServer32 key to _LocalServer32.) Running your program as a local server takes a few more seconds on startup because the call to StartServiceCtrlDispatcher in CServiceModule::Start takes a few seconds before it fails.

Back to top