Creating a Windows NT Service by Using ATL

Environment: VC++, ATL

Purpose

The purpose of this article is to create a Windows NT Mail slot repository server service, which is a placeholder for all the mail slot client messages sent by mail slot repository clients.

Mail Slot Repository Service

A mail slot offers a one-way form of inter-process communication. A mail slot is first created on one machine by a creating process. This is a mail slot server. This process receives all the messages from mail slot clients. In our application, all the messages that are received by this process are written to a local log file.

Mail slot repository server can receive the message from the local client process or from network machines. Messages sent to local mail slots or mail slots located on the local machine can be of any size. But, messages sent to remote mail slots have to be a maximum size of 400 bytes.

NOTE: If delivery of the mail message is important, use other forms of inter-process communications, such as shared memory, rpc, pipes, or sockets.

To create MailslotRepositrySrv, follow these steps:

  1. Open Microsoft Visual C++ 6.0 and select ATL COM AppWizard projects. Enter the project name, .MailslotRepositrySrv., and click OK.
  2. In ATL COM AppWizard, step 1 of 1, select the server type as Service (EXE).
  3. ATL COM Wizard generates the following file information:
    • COM Server name: MailslotRepositrySrv.exe
    • Initialization code in MailslotRepositrySrv.cpp
    • IDL source in MailslotRepositrySrv.idl
    • ProxyStub makefile in MailslotRepositrySrvps.mk

Figure 1

MailslotRepositrySrv.cpp contains all the service-related register, unregister, install, and uninstall code. COM Wizard also provides the service main and its handler code.

Figure 2

WinMain

WinMain is the entry point function of the process. The primary thread in the process executes this function, which is responsible for performing the initialization for the whole process.

CServiceModule::Start()

The main thread of the service calls the StartServiceCtrlDispatcher function to connect to the service control manager (SCM), which causes the thread to be the service control dispatcher thread for the calling process and does not return until all running services in the process have terminated. The SCM uses this connection to send control and service start requests to the main thread of the service process.

The main thread acts as a dispatcher by invoking the appropriate Handler() function to handle control requests, or by creating a new thread to execute the appropriate ServiceMain() function when a new service is started.

NOTE: Once all services inside the executable have stopped running, the primary thread performs cleanup for the process as a whole before the process terminates.

CServiceModule::Run()

Run performs COM initialization and sets up the security for the service. We can specify the security by using CSecurityDescriptor. 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 discretionary access-control list (DACL), which means that any user has access to your object.

Logging the Service Details

All the service- and application-related activities are logged to the Windows application event log. The event log information is registered with the help of the RegisterEventSource function. This function retrieves a registered handle to an event log based on the UNCServerName. If this value is NULL, the local machine event log is used.

The source name is the application name for which the event log is generated. Programmatically, the handle returned by RegisterEventSource() points to this source name. The source name must be a subkey of a logfile entry under the EventLog key in the registry; in our application, this is HKEY_LOCAL_MACHINE\System\
CurrentControlSet\Services\EventLog\Application\ MailslotRepositrySrv
.

The DeregisterEventSource function closes a write handle to the specified event log. The actual event information is written to the application log by using the ReportEvent function (handle returned by the RegisterEventSource()).

I have modified the LogEvent() function to take different EventTypes, EventID, and string that are to be written.


void CServiceModule::LogEvent(WORD wType, DWORD dwID,LPCTSTR
    pFormat)
{
    TCHAR    chMsg[4096];
    HANDLE   hEventSource;
    LPTSTR   lpszStrings[1];
    va_list pArg;

    va_start(pArg, pFormat);
    _vstprintf(chMsg, pFormat, pArg);
    va_end(pArg);

    lpszStrings[0] = chMsg;

    if (m_bService)
    {
        // Get a handle to use with ReportEvent().
        hEventSource = RegisterEventSource(NULL, m_szServiceName);
        if (hEventSource != NULL)
        {
            // Write to event log.
      ReportEvent(hEventSource,wType,0,dwID,NULL,1,0,(LPCTSTR*)
         &lpszStrings[0],NULL);
            DeregisterEventSource(hEventSource);
        }
    }
    else
    {
        // As we are not running as a service, just write the error
        // to the console.
        _putts(chMsg);
    }
}

Reading the Messages from the Message File

We can externalize the messages that are supplied to the application log files by creating message files. Here’s a step-by-step guide:

  1. Create a text file with the extension .MC containing the descriptions of the messages. I called mine .MailslotRepositrySrv_extMC.mc..
  2. Run the message compiler (MC.EXE) against your source file, which by default creates an output file called MSG00001.BIN. The compiler also creates a header file (in my case, MailslotRepositrySrv_extMC.h) and an .RC file (MailslotRepositrySrv_extMC.rc).
    You need to repeat this step any time you change the .MC file in your project.

  3. mc MailslotRepositrySrv_extMC.mc

  4. Include the header file produced by the message compiler in your main project header file so all modules have access to the symbolic message names.

MailslotRepositrySrv_extMC.mc contains all the service-specific messages. I have divided the messaging in two categories, as follows.

  1. Service specific (start, stop, pause, and so forth).
  2. Mail slot creation (create).

MessageId=100
Severity=Success
SymbolicName=EVMSG_INSTALLED
Language=English
The %1 service was installed.
Category Service
.
MessageId=
Severity=Success
SymbolicName=EVMSG_MAILSLOT_CREATE
Language=English
MailSlot Created Successfully
Category Mailslot
.

Each entry has an ID value that, if not specifically set, is simply one more than the value assigned to the message before it. Each entry also has a severity, symbolic name for use in your code (EVMSG_MAILSLOT_CREATE), a language identifier, and the text of the actual message. Messages can span more than one line and are terminated by a line containing a single period on its own.

Include MailslotRepositrySrv_extMC.h file.


//
// MessageId: EVMSG_INSTALLED
//
// MessageText:
//
// The %1 service was installed.
// Category Service
//
#define EVMSG_INSTALLED                  0x00000064L
//
// MessageId: EVMSG_MAILSLOT_CREATE
//
// MessageText:
//
//  MailSlot Created Successfully
//  Category Mailslot
//
#define EVMSG_MAILSLOT_CREATE            0x0000006CL

Message DLL

Each event source should register message files that contain description strings for each event identifier, event category, and parameter. Register these files in the EventMessageFile, CategoryMessageFile, and ParameterMessageFile registry values for the event source. You can create one message file that contains descriptions for the event identifiers, categories, and parameters, or create three separate message files. Several applications can share the same message file. Without the message DLL, the event viewer gives the following description:

The description for Event ID (106) in Source (MailslotRepositrySrv) cannot be found. The local computer may not have the necessary registry information or message DLL files to display messages from a remote computer. The following information is part of the event: Service started.

This error description is very annoying for the user who wants to find out the status of the service.

Figure 3

Creating a Message DLL

  1. Create a .RES file. This can be achieved by running the following command against the .RC file created in Step 2 of reading the message from the message file section.

    rc -r -fo filename.res filename.rc.

  2. Use the linker to create a .DLL file. Use the following command line:

    link -dll -noentry -out:filename.dll filename.res.

To Register the Message DLL

To register the message DLL, create the CategoryMessageFile, EventMessageFile and ParameterMessageFile keys under the
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EventLog\
Application\MailslotReposirySrv”
entry. Set all keys value to the message DLL name (MailslotRepositrySrv_extmc.dll).


char szPath[_MAX_PATH];
char lpKeyValue[4096];
getAppPath(szPath);
strcpy(lpKeyValue,szPath);
strcat(lpKeyValue,"MailslotRepositrySrv_extmc.dll");
CRegKey keyMessageFilterDLLID;
lRes = keyMessageFilterDLLID.Create(HKEY_LOCAL_MACHINE,
_T("SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application
          \\MailslotRepositrySrv"));

// CREATE CATEGORYMESSAGEFILE KEY
lRes = keyMessageFilterDLLID.SetValue(lpKeyValue,_T
                                     ("CategoryMessageFile"));

// CREATE EVENTMESSAGEFILE KEY
lRes = keyMessageFilterDLLID.SetValue(lpKeyValue,_T
                                     ("EventMessageFile"));

// CREATE PARAMTERMESSAGEFILE KEY
lRes = keyMessageFilterDLLID.SetValue(lpKeyValue,_T
                                     ("ParameterMessageFile"));
// CREATE TYPESSUPPORTED KEY(DWORD)
DWORD dwTypesSupported = 7;
lRes = keyMessageFilterDLLID.SetValue(dwTypesSupported,_T
                                     ("TypesSupported"));
// CREATE CATEGORYCOUNT KEY(DWORD)
DWORD dwCategoryCount = 3;
lRes = keyMessageFilterDLLID.SetValue(dwCategoryCount,_T
                                     ("CategoryCount"));

// ADD DESCRIPTION REGISTRY ENTRY KEY
CRegKey keyDescription;
lRes = keyDescription.Open(HKEY_LOCAL_MACHINE,_T("SYSTEM\\
       CurrentControlSet\\Services\\MailslotRepositrySrv"));
// CREATE CATEGORYMESSAGEFILE KEY
lRes = keyDescription.SetValue(_T("Mailslot Repositry Services"),
                               _T("Description"));

Figure 4

Creating the Mail Slot

The CreateMailslot function creates a mail slot with the specified name and returns a handle that a mail slot server can use to perform operations on the mail slot.

NOTE: If lpSecurityAttributes is NULL, the client application cannot inherit the mail slot handle. This results in an access denied error. More on this can be found on Windows NT Security by Christopher Nefcy, September 1994.

The mail slot is local to the computer that creates it. An error occurs if a mail slot with the specified name already exists. I have created .mailslot_repository.. The function returns a handle with which you can read all messages.

Code snippet


BOOL FAR PASCAL createRepositryServerMailslot ()
{
  // MAILSLOT NAME
        LPSTR lpszSlotName = "\\\\.\\mailslot\\mailslot_repositry";
  SECURITY_ATTRIBUTES saMailSlotSecurity;
  PSECURITY_DESCRIPTOR pMailSlotSD = NULL;
    hMailslot = CreateMailslot(lpszSlotName,
      0,                             // no maximum message size
      MAILSLOT_WAIT_FOREVER,         // no time-out for operations
      &saMailSlotSecurity);

  return TRUE;
}

Mail slot repository server reads the mail messages in a background thread, (MailMessageListenerThread()), at an interval of two minutes. The purpose of the MailMessageListenerThread() function is to call readRepositryServerMailslot(), which calls GetMailslotInfo to retrieve the total number of messages, maximum size of a message, size of the next message, and time-out for reading mail messages in the mail slot. If the GetMailslotInfo returns that there is at least one message in the mail slot, the ReadFile function can be used to read the message. Log this message by calling LogMailSlotMessage().

NOTE: If ReadFile function is called with only a partial read of the mail message, the rest of the message is lost.

Code snippet


BOOL WINAPI readRepositryServerMailslot()
{
   // GETMAILSLOT INFO
    fResult = GetMailslotInfo(hMailslot, // mailslot handle
        (LPDWORD) NULL,                  // no maximum message size
        &cbMessage,                      // size of next message
        &cMessage,                       // number of messages
        (LPDWORD) NULL);                 // no read time-out

    if (!fResult)
        return FALSE;

    if (cbMessage == MAILSLOT_NO_MESSAGE)
        return TRUE;

    cAllMessages = cMessage;

    while (cMessage != 0)                // retrieve all messages
    {
    // Allocate memory for the message.
       lpszBuffer = (LPSTR) GlobalAlloc(GPTR,lstrlen((LPSTR) achID)
                    + cbMessage);
       lpszBuffer[0] = '\0';
       fResult = ReadFile
                 (hMailslot,lpszBuffer,cbMessage,&cbRead,&ov);
       if (!fResult)
       {
           GlobalFree((HGLOBAL) lpszBuffer);
           return FALSE;
        }
        // Log the message.
       LogMailSlotMessage(lpszBuffer);
        GlobalFree((HGLOBAL) lpszBuffer);

        fResult = GetMailslotInfo(hMailslot, // mailslot handle
            (LPDWORD) NULL,                  // no maximum
                                             // message size
            &cbMessage,                      // size of next message
            &cMessage,                       // number of messages
            (LPDWORD) NULL);                 // no read time-out

        if (!fResult)
        {
            return FALSE;
        }
    }
    return TRUE;
}

Installing and Configuring the Service

To install MailslotRepositrySrv.exe as a service, execute the following command:

C:\MailSlot\MailslotRepositrySrv\Debug\ MailslotRepositrySrv.exe . Service

To configure, open Windows NT service and start the service. A successful start of the service logs the following messages to the Windows Application Log:

  1. Mail slot Created Successfully Category Mail slot
  2. The service was started. Category Service

Client Application

The mail slot repository client application writes the mail messages to the server. The client application can be local or remote. For simplicity, I have tested on my local machine.

Figure 5

References

  1. Creating a Simple Win32 Service in C++—Nigel Thompson, Microsoft Developer Network Technology Group, November 1995
  2. Manipulate Windows NT Services by Writing a Service Control Program—Jeffrey Richte.
  3. Windows NT Security—Christopher Nefcy, September 1994

Downloads

MailslotRepositrySrv and MailslotRepositryClient Source Code – 161 Kb

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read