A Wrapper Class for NT Services

Like many aspects of Windows NT, talking to the Service Control Manager is a task best left to an expert.


August 01, 1998
URL:http://www.drdobbs.com/mobile/mobile/a-wrapper-class-for-nt-services/184403531

August 1998/A Wrapper Class for NT Services


Introduction

In this article, I introduce the concept of a service in the Win32 environment. I provide and discuss a C++ class, NTService, that encapsulates the basics of building an NT service. This class allows you to concentrate on only the application-specific needs of a service application, and not on the mechanics of creating a service.

About NT Services

Windows NT supports an application type known as a service. A Win32 service is conceptually similar to a Unix daemon. You can use services to enhance or add features to the operating system. One example of a Windows NT service is the Windows print spooler. The Service Control Manager (SCM) starts, stops, and controls services, which can be started either automatically at system boot time or manually by the user.

In NT, you can access the SCM user interface by selecting the Services icon from the Windows Control Panel. All services under NT must conform to the interface requirements of the SCM. A service must be installed into the SCM database before the SCM will recognize and register the service. Services have three function entry points: main, ServiceMain, and ServiceControl (sometimes called the ControlHandler). main registers the service with the SCM. Once the service is registered, the SCM starts the service by calling ServiceMain. ServiceMain must register the control handler so the SCM can retrieve status information and send control requests.

The NTService Class

I created the NTService class because I needed the ability to quickly create NT service programs, and to provide programmers with a way to create service programs without having to learn the details about how NT services operate within the Windows environment. I developed my NT service programs using Visual C++ v5.0.

To create the project, use File | New and the project tab to create a new Win32 console application. Since services can be started before any users are logged on to the system, user interface issues are immaterial. Therefore, I recommend avoiding MFC and other GUI libraries when possible to keep the service code small.

The NTService class provides all of the core functionality needed for a Windows NT service. ntservice.h (Listing 1) is the header file for the NTService class, and ntservice.cpp (Listing 2) is the C++ implementation file. The NTService class is a generic base class which cannot be instantiated by itself because of the pure virtual function Run. The NTService class also provides ten protected functions that are designed to be optionally overridden by the programmer in the child class. A new service program is created by deriving a child class that provides a Run member function from the NTService class. Run is a protected member function that performs only the actions needed to deliver the desired service capability.

The NTService class handles ServiceMain registration, the ServiceControl entry points, and any other generic service chores. It also provides methods to install and uninstall the service from the SCM.

The main Function

Like traditional C++ programs, but unlike most Windows programs, a service uses main instead of WinMain. To start the service, the SCM enters main, which in turn must register the service and identify its primary entry point, ServiceMain. ServiceMain must first register the entry point for ServiceControl. The SCM uses the service control function to monitor the status of the service and to suspend, resume, or stop the service. Listing 3 shows a portion of main.cpp.

For most applications, you'll need to make only two changes to main.cpp. Change the value of the SERVICE_NAME constant to the name of the service you're creating, and invoke the constructor of the derived class in the call to new. main.cpp consists of three functions: ServiceMain, ServiceControl, and main. main first creates an instance of the MyService class and assigns it to the global pointer ntService. The constructor for the NTService class takes three arguments: the service name, ServiceMain's address, and ServiceControl's address. These two functions provide the SCM with its interface to the derived service class. ServiceMain and ServiceControl must conform to the interface required by the SCM. They also must have access to the instance of the service class's ntService that was created in main. This is why they were implemented as globally visible functions. They could have been static member functions of the NTService class, but I felt I would have more flexibility to add multiple service support later if I implemented it this way.

main parses the command-line arguments and performs one of three actions. main will install or uninstall the service if a -i or a -u flag appear in the command-line argument, respectively. Install and Uninstall are member functions of NTService. If no arguments are present, then main assumes it is being called by the SCM and calls ntService->Startup.

Starting and Running the Service

An NT service is started by calling the Windows StartServiceCtrlDispatcher and passing it a dispatch table with the service name and the address of its ServiceMain. The NTService class does this in its Startup member function. The SCM then immediately calls the ServiceMain that was provided in the dispatch table (see Listing 1). My ServiceMain just calls the ntService->Service member function of my NTService class. ServiceMain receives two arguments from the SCM. Though these correspond to argc and argv from the command line, these arguments were not available to the command line. Instead, they come from the startup parameters entry from the SCM.

The first thing the Service member function does is register ServiceControl by calling RegisterServiceCtrlHandler and giving the service name and the function pointer that were used to create the NTService instance. The dwCurrentState member of the SERVICE_STATUS member (mStat) was set to
SERVICE_START_PENDING by the constructor. Once the SCM has the pointer to ServiceControl, it will retrieve this status value. NTService::Service calls Init, passing the argc and argv values. Init is stubbed in the NTService class but should be overridden to provide any initialization needed by the actual service instance. If the service was successfully initialized, the status word will be changed to SERVICE_RUNNING and the overloaded Run will be called. At this point the service is running.

Service Control

To examine or change the service's state, the SCM calls the SeviceControl function that was registered when the service was started. In my case, it calls ServiceControl in main.cpp, which in turn calls NTService::Control. Control can respond to any of the five commands listed in the table, or to any user command. User commands should have values that are greater than that of SERVICE_CONTROL_SHUTDOWN. The SCM does not send either user commands or the shutdown command. Only Windows can send SERVICE_CONTROL_SHUTDOWN, when Windows is preparing to shut down, and only Service Control programs can send user commands.

The control commands that a service recognizes are established when the service control function is registered. The NTService class initializes this in its constructor by assigning a value to mStat.dwControlsAccepted. The NTService class defaults to recognizing all of the commands. You can change this in the derived class's constructor. All services must respond to the interrogate command. NTService::Control itself should never be overridden. The Control member is implemented by using a switch statement that invokes functions for each command. Control also makes appropriate changes to the service status. The control member functions are listed in Table 1. Stubs are provided in NTService for any functions that require no action.

Applying the NTService Class

Creating a new service is simply a matter of creating a class derived from the NTService class that implements the Run member function, and changing main.cpp to give the service the correct name and to invoke the new operator for the derived class. Other functions are provided that can be overloaded for specific purposes. An example of an implementation of a new service (MyService) can be found in myserve.h (Listing 4) and myserve.cpp (Listing 5). MyService is a simple service that uses Windows mail slots for interprocess communications. Anything written to the mail slot that MyService creates is copied to the file outfile.txt in the c:\temp subdirectory. One use for this might be as the base of a global message logging service.

The MyService Constructor

The MyService constructor has little to do. If the new service will not accept all of the controls, then the constructor can change the values by changing mstat's contents using SetAcceptedControls. MyService accepts only the SERVICE_CONTROL_STOP command, so it makes this change in its constructor.

The Init Function

The NTService::Init member is stubbed in the NTService class, but should be overloaded to provide any program-specific initialization needed for the service. Init receives the argc and argv arguments that the SCM passes to ServiceMain. The service status is set to SERVICE_START_PENDING by NTService::Service before the Init member is called, and is changed to SERVICE_RUNNING before calling Run if Init completes successfully. Init should process the argc and argv parameters and read any registry values that are needed to support the application. If initialization is going to be lengthy, the status word has provision for checkpoints and wait hints to be provided to the SCM. You can change these by calling the ChangeStatus member function with the appropriate values.

MyService uses the Windows function Createmailslot to create the mail slot that will collect messages from other processes. It then creates the output file in the c:\temp subdirectory. The filenames are hard-coded for simplicity. Normally, I would not hard-code the names of the mail slot or the output file, but would instead read them from the registry. I would also read the registry values using the Init method.

Service Control Functions

There are six functions for dealing with service control actions (see Table 1). In my example, I have implemented only OnStop. OnStop should be implemented for all services. MyService::OnStop simply changes the state of Run's controlling loop variable. Run then performs any file closing and other cleanup chores needed to gracefully stop the service. OnPause could be implemented by setting a member variable that Run would poll. Run would then poll the variable and sleep until OnContinue caused the variable state to change again. I have not implemented OnShutdown because the stub version in the NTService class calls OnStop as the default implementation. Nothing additional is needed for OnInquire so it is not overloaded. Also, I do not have any user commands in this example; therefore, OnUserControl is not implemented. The NTService::Control method handles service status changes that are needed to inform the SCM, so this does not need to be done by the service control functions.

The Run Function

Run is a pure virtual function in the NTService class, and so must always be implemented. Run is the meat of any service, and does all the work. Effectively, Run is a loop that continues to loop until the service is stopped. The m_running variable in the MyService class controls the loop in the MyService service. MyService::Run examines the mailslot to determine if data is available. If data is available it reads the data and copies it to the output file, continuing until OnStop sets m_running to false.

Error and Event Logging

Unlike Unix, Windows NT does not have the concept of a master console. Services cannot count on a console being available to receive output or error messages (unless the service opens one). However, Windows NT does provide event logging capability. All applications can access the event log to record significant information, and the event log can be easily viewed from any machine on the network. Using the event log, however, requires that you develop a series of messages in advance and compile them using the message compiler, which can be a complex process. I have created a simplified universal message file and an application log class, TAppLog, which simplifies the use of the event log for applications that do not have extensive event reporting requirements.

The TAppLog class can control the level of messages that are logged and it can echo messages to the debugger. This class is shown in tapplog.h (Listing 6), tapplog.cpp (Listing 7), and mcmsg.mc (Listing 8). Compiling mcmsg.mc with the Windows message compiler creates mcmsg.rc and mcmsg.h. mcmsg.rc should be linked when the executable is built and Mcmsg.h should be included in at least one file. I have included the file in main.cpp.

The MyService class includes the pointer m_log to an instance of the TAppLog class. The pointer is initialized in the MyService constructor and the object is freed by the MyService destructor. TAppLog also provides an Install function, which is called from MyService::InstallAid and creates the needed registry entries for the TappLog class to work properly. An Uninstall function is stubbed for a future enhancement.

TAppLog provides two versions of the LogEvent method. The first argument of each is a log category that is one of LOG_ERROR, LOG_WARNING, or LOG_INFORM. These are mapped to generate the appropriate code for the Windows ReportEvent function. One version of LogEvent then takes arguments in the same fashion as printf. The other version takes a valist (variable argument list). The valist version is what allows the LogEvent function to be provided as a member of the MyService class. MyService also implements an Error member function which automatically cracks the Windows message number and logs the error message to the event log.

Debugging

A service is run by the SCM and cannot be started from the debugger in the IDE. The service can be debugged with the debugger by inserting a DebugBreak statement in the code. The code will break into the debugger when the DebugBreak statement is reached. Be aware that the SCM will time out if the service does not report back its status in a timely fashion. It may also be possible to debug some services as a console application. This requires avoiding or skipping over any calls to the SCM and modifying main to call ServiceMain and ServiceControl as needed for testing. I have been able to do all of my debugging by logging debug messages to the event log.

Installing and Removing a Service

Before a service can be started it must be installed into the SCM's database. The NTService::Install method does this. It first checks to see if the service is already installed. If so, it returns true. If the service needs to be installed, it gets a handle to the SCM and requests that the service be added. By default the service is installed for "demand start," meaning the service is not automatically started by the operating system, but instead starts when the user requests it by using the Services applet from the Control Panel.

Function InstallAid can be overloaded to add registry entries or perform any other installation tasks. It is stubbed to do nothing in NTService. You can also use the InstallAid method to change the effect of the defaulted arguments to the Windows CreateService function by calling the Windows ChangeServiceConfig function. A service can be removed from the SCM database by calling the NTService::UnInstall method. UnInstall calls UnInstallAid, which should be overloaded to clean up anything that was done by the overloaded InstallAid function. UnInstallAid is stubbed to do nothing in NTService.

Configuration Issues

Services should be configured to store any initialization parameters in the registry. A Control Panel applet also should be created to easily manipulate and change these values. This example is designed to emphasize the mechanics of building a service, and the issues of proper initialization and storing registry values is left for another exercise. For example, the mail-slot and the output filenames should be read in from values stored in the registry. There is a defined place in the registry for the parameter values needed by a service. It is:

HKEY_LOCAL_MACHINE\
   SYSTEM\
      CurrentControlSet\
         Services\
            <<ServiceName>>\
               Parameters

Default values for any parameters should be written by the overloaded InstallAid method. The registry entries should be removed by the UninstallAid method. The values should be read by the Init method when the service is started.

Summary

Services in NT exist to extend the capability of the operating system. Like daemons in Unix, NT services can be started automatically when the operating system is started, or on demand by the user or by an application program that talks to the Service Control Manager. The SCM starts and stops the service and forwards custom commands from application programs. An NT service program consists of three primary functions or entry points: main, ServiceMain, and ServiceControl. The NTService class can be used to create a new NT service by writing as few as three functions: Init, Run, and OnStop. This service can install and uninstall itself to and from the SCM database. The example MyService class implements a simple logging service by using mail slots.

James M. Bell has a Ph.D. in mathematics from Auburn University. He was a member of the mathematics faculty at Furman University for 12 years and for the last 14 years has been a mathematician in the Operations Research Department of Miliken & Company. Part of his job is as system administrator for the department's Unix network. James may be reached at [email protected].

August 1998/A Wrapper Class for NT Services/Listing 1

Listing 1: Header file for NTService class

#ifndef _ntservice_h_
#define _ntservice_h_

#include <windows.h>

class NTService
{
private:
    bool                    m_isStarted;
    bool                    m_isPaused;

protected:
    char                    m_name[36];
    DWORD                   m_err;

    LPSERVICE_MAIN_FUNCTION mfpSrvMain;
    LPHANDLER_FUNCTION      mfpSrvControl;

    SERVICE_TABLE_ENTRY     mDispatchTable[2];
    SERVICE_STATUS          mStat;
    SERVICE_STATUS_HANDLE   mh_Stat;

public:
//============================================================
//============================================================
//    static ServiceMain( DWORD ac, LPTSTR *av );
//    static ServiceControl( DWORD opCode );
//============================================================
//============================================================
private:
    NTService();                //== prevent a default constructor
    NTService( NTService& );    //== prevent a copy constructor

protected:

    virtual void SetAcceptedControls( DWORD controls );
    virtual void ChangeStatus( DWORD state, 
                     DWORD checkpoint = (DWORD)0, 
                     DWORD waithint = (DWORD)0 );
    
//== the protected functions below this line should be 
//   overridden as needed

    virtual DWORD Init( DWORD argc, LPTSTR* argv );
    virtual int   Run() = 0;
    
    virtual void  InstallAid( void );
    virtual void  UnInstallAid( void );

//== these are the 6 service control actions
    virtual DWORD OnPause( void );
    virtual DWORD OnContinue( void );

    virtual void  OnStop( void );
    virtual void  OnShutdown( void );

    virtual void  OnInquire( void );
    virtual void  OnUserControl( DWORD usercmd );

public:
//== it should not be necessary to overload these public functions
    NTService( const char *name, LPSERVICE_MAIN_FUNCTION, 
        LPHANDLER_FUNCTION );
    virtual       ~NTService( void );

    virtual DWORD Startup( void );
    virtual int   Service( DWORD argc, LPTSTR* argv );    
    virtual void  Control( DWORD opcode );

    virtual bool  IsInstalled( void );
    virtual bool  Install( void );
    virtual bool  UnInstall( void );

    virtual DWORD GetLastError( void );    
    virtual DWORD GetExitCode( void );    
};

inline DWORD NTService::GetExitCode( void )
{
    return mStat.dwWin32ExitCode;
}
#endif
/* End of File */
August 1998/A Wrapper Class for NT Services/Listing 2

Listing 2: Implementation file for NTService class

#include <windows.h>
#include "ntservice.h"

NTService::NTService( const char *name, 
    LPSERVICE_MAIN_FUNCTION funp_srvmain, 
    LPHANDLER_FUNCTION funp_ctrl)
{
    //== first initialize data members from the args
    //== set function pointers for later use
    mfpSrvMain = funp_srvmain;
    mfpSrvControl = funp_ctrl;

    //== copy the service name - if needed add UNICODE support here
    memset( m_name, 0, sizeof(m_name) );
    strncpy( m_name, name, sizeof(m_name)-1);

    //== clear the status flags
    m_isStarted = false;
    m_isPaused = false;

    //== clear the dispatch table
    memset( &mDispatchTable[0], 0, sizeof( mDispatchTable ) );

    //== clear the SERVICE_STATUS data structure
    memset( &mStat, 0, sizeof( SERVICE_STATUS ));
    mh_Stat = 0;

    //== Now initialize SERVICE_STATUS here so we can 
    //== call SetAcceptedControls() before Startup() or
    //== override in constructor of child class.
    mStat.dwServiceType         = SERVICE_WIN32; 
    mStat.dwCurrentState        = SERVICE_START_PENDING; 
    mStat.dwControlsAccepted    = SERVICE_ACCEPT_STOP 
                                | SERVICE_ACCEPT_PAUSE_CONTINUE
                                | SERVICE_ACCEPT_SHUTDOWN; 
}

NTService::~NTService( void )
{
}

DWORD NTService::Startup( void )
{
    //== initialize the dispatch table
    mDispatchTable[0].lpServiceName = m_name;     //== Note: these 
    mDispatchTable[0].lpServiceProc = mfpSrvMain; //   are pointers

    //== starts the service 
    if( !StartServiceCtrlDispatcher( mDispatchTable )){
        m_err = ::GetLastError();
        return m_err;
    }

    return NO_ERROR;
}

int NTService::Service( DWORD argc, LPTSTR* argv )
{
    mh_Stat = RegisterServiceCtrlHandler( TEXT( m_name ), 
                  mfpSrvControl );
    if( (SERVICE_STATUS_HANDLE)0 == mh_Stat )
    {
        m_err = ::GetLastError();
        return m_err;
    }
    
    if( Init( argc, argv ) != NO_ERROR )
    {
        ChangeStatus( SERVICE_STOPPED );
        return m_err;
    }
    
    ChangeStatus( SERVICE_RUNNING );
    return Run();
}

void NTService::Control( DWORD opcode )
{
    switch( opcode )
    {
    case SERVICE_CONTROL_PAUSE:
        ChangeStatus( SERVICE_PAUSE_PENDING );
        if( OnPause() == NO_ERROR )
        {
            m_isPaused = true;
            ChangeStatus( SERVICE_PAUSED );
        }
        break;

    case SERVICE_CONTROL_CONTINUE:
        ChangeStatus( SERVICE_CONTINUE_PENDING );
        if( OnContinue() == NO_ERROR )
        {
            m_isPaused = false;
            ChangeStatus( SERVICE_RUNNING );
        }
        break;

    case SERVICE_CONTROL_STOP:
        ChangeStatus( SERVICE_STOP_PENDING );
        OnStop();
        ChangeStatus( SERVICE_STOPPED );
        break;

    case SERVICE_CONTROL_SHUTDOWN:
        ChangeStatus( SERVICE_STOP_PENDING );
        OnShutdown();
        ChangeStatus( SERVICE_STOPPED );
        break;

    case SERVICE_CONTROL_INTERROGATE:
        OnInquire();
        SetServiceStatus( mh_Stat, &mStat );
        break;

    default:
        OnUserControl( opcode );
        SetServiceStatus( mh_Stat, &mStat );
        break;
    };
    return;
}


bool NTService::Install( void )
{
    //== check to see if we are already installled
    if( IsInstalled() )
        return true;

    //== get the service manager handle
    SC_HANDLE schSCMgr = 
        OpenSCManager( NULL, NULL, SC_MANAGER_ALL_ACCESS );
    if( schSCMgr == NULL )
    {
        m_err = ::GetLastError();
        return false;
    }

    //== get the full path of the executable
    char szFilePath[_MAX_PATH];
    ::GetModuleFileName(NULL, szFilePath, sizeof(szFilePath));


    //== install the service
    SC_HANDLE schSrv = CreateService(
        schSCMgr,
        m_name,
        m_name,
        SERVICE_ALL_ACCESS,
        SERVICE_WIN32_OWN_PROCESS,
        SERVICE_DEMAND_START,
        SERVICE_ERROR_NORMAL,
        szFilePath,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL
    );

    bool bRetval = true;
    if( schSrv == NULL )
    {
        m_err = ::GetLastError();
        bRetval = false;
    }
    else
        InstallAid(); //== overload to add registry entries 
                      //   if needed
    
    //== All done, so pick up our toys and go home!
    CloseServiceHandle( schSrv );
    CloseServiceHandle( schSCMgr );
    return bRetval;
}

bool NTService::UnInstall( void )
{
    if( !IsInstalled() )
        return true;

    //== get the service manager handle
    SC_HANDLE schSCMgr = 
        OpenSCManager( NULL, NULL, SC_MANAGER_ALL_ACCESS );
    if( schSCMgr == NULL )
    {
        m_err = ::GetLastError();
        return false;
    }

    //== Get the service handle
    SC_HANDLE schSrv = OpenService( schSCMgr, m_name, DELETE );
    if( schSrv == NULL )
    {
        m_err = ::GetLastError();
        CloseServiceHandle( schSCMgr );
        return false;
    }

    //== Now delete the service
    bool bRetval = true;
    if( !DeleteService( schSrv ) )
    {
        bRetval = false;
        m_err = ::GetLastError();
    }

    //== do any application specific cleanup
    UnInstallAid();

    //== Put away toys and go home!
    CloseServiceHandle( schSrv );
    CloseServiceHandle( schSCMgr );
    return bRetval;
}

DWORD    NTService::GetLastError( void )
{
    return m_err;
}

/*=================================================================
bool NTService::IsInstalled()

Purpose: Returns true if the service has already been installed.
=================================================================*/
bool NTService::IsInstalled( void )
{
    bool bResult = false;

    // Open the Service Control Manager
    SC_HANDLE hSCM = 
        ::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
    if (hSCM) 
    {
        // Try to open the service
        SC_HANDLE hService = 
            ::OpenService(hSCM, m_name, SERVICE_QUERY_CONFIG);
        if (hService) 
        {
            bResult = true;
            ::CloseServiceHandle(hService);
        }
        ::CloseServiceHandle(hSCM);
    }
    
    return bResult;
}

void NTService::SetAcceptedControls( DWORD controls )
{
    mStat.dwControlsAccepted = controls;
}

void NTService::ChangeStatus( DWORD state, DWORD checkpoint, 
         DWORD waithint )
{
    mStat.dwCurrentState  = state;
    mStat.dwCheckPoint    = checkpoint;
    mStat.dwWaitHint      = waithint;
    
    SetServiceStatus( mh_Stat, &mStat );
}

/*=================================================================
    Stubs
=================================================================*/
DWORD 
NTService::Init( DWORD argc, LPTSTR* argv ) { return NO_ERROR; }

void  NTService::InstallAid( void ) {}
void  NTService::UnInstallAid( void ) {}

DWORD NTService::OnPause( void ) { return NO_ERROR; }
DWORD NTService::OnContinue( void ) { return NO_ERROR; }

void  NTService::OnStop( void ) {}
void  NTService::OnShutdown( void ) { OnStop(); }

void  NTService::OnInquire( void ) {}
void  NTService::OnUserControl( DWORD ) {}
//End of File
August 1998/A Wrapper Class for NT Services/Listing 3

Listing 3: Contains the function main

#include <windows.h>

#include "myserve.h"
#include "mcmsg.h"

NTService *ntService = NULL;

static const char SERVICE_NAME[] = "MyServe";

void WINAPI serviceMain( DWORD ac, LPTSTR *av )
{
    ntService->Service( ac, av );
}

void WINAPI serviceControl( DWORD opcode )
{
    ntService->Control( opcode );
}

DWORD main( int argc, char *argv[] )
{
    ntService = 
        new MyService( SERVICE_NAME, serviceMain, serviceControl );
    
    if( argc <= 1)
        ntService->Startup();
    else if( stricmp( argv[1], "-i") == 0 )
        ntService->Install();
    else if( stricmp( argv[1], "-u") == 0 )
        ntService->UnInstall();

    DWORD exitcode = ntService->GetExitCode();

    delete ntService;

    return exitcode;
}
//End of File
August 1998/A Wrapper Class for NT Services/Listing 4

Listing 4: Header file for MyService class

#ifndef _myserve_h_
#define _myserve_h_

#define LOGGING

#include "ntservice.h"
#include "tapplog.h"

class TAppLog;

class MyService : public NTService
{
#ifdef LOGGING
    TAppLog*  m_log;
#endif    
    HANDLE    mhOutfile;
    HANDLE    mhMailslot;
    boolean   m_running;

private:
    MyService( void );
    MyService( MyService& );
    
protected:
//== override these functions from the parent class
    DWORD     Init( DWORD argc, LPTSTR* argv );
    int       Run();
    
#ifdef LOGGING
    void      InstallAid( void );
    void      LogEvent( LOG_LEVEL, char*, ... );
    void      Error( long line );
#endif

//== only support service control Stop
    void      OnStop( void );

public:
    MyService( const char* name, LPSERVICE_MAIN_FUNCTION, 
        LPHANDLER_FUNCTION );
    ~MyService( void );
};

#endif
/* End of File */

August 1998/A Wrapper Class for NT Services/Listing 5

Listing 5: Implementation file for MyService class

#include <windows.h>
#include <winsock.h>
#include <stdlib.h>
#include <stdio.h>
#include <process.h>

#include "myserve.h"
#include "tapplog.h"

static const char szRegBasePath[] = 
    "SYSTEM\\CurrentControlSet\\Services\\";

MyService::MyService( 
    const char*              srvname, 
    LPSERVICE_MAIN_FUNCTION  fpSrvMain, 
    LPHANDLER_FUNCTION       fpSrvCtrl
    )
    :    NTService( srvname, fpSrvMain, fpSrvCtrl )
{
    mStat.dwControlsAccepted = 
        SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
#ifdef LOGGING
    m_log = new TAppLog( m_name, LOG_ALL );
#endif
}

MyService::~MyService( void )
{
#ifdef LOGGING
    delete m_log;
#endif
}

DWORD MyService::Init( DWORD ac, LPTSTR *av )
{
    LogEvent( LOG_INFORM, "MyServe init..." );
    mhMailslot = CreateMailslot( 
        "\\\\.\\mailslot\\myserve", //== mailslot name
        4096,                       //== maximum message size
        200,                        //== milliseconds to timeout
        NULL                        //== security attributes
    );
    
    if( mhMailslot == INVALID_HANDLE_VALUE ){
        Error( __LINE__ );
        return false;
    }

    mhOutfile = CreateFile( 
        "c:\\temp\\outfile.txt",    //== filename
        GENERIC_WRITE,              //== access type
        FILE_SHARE_READ,            //== share mode
        NULL,                       //== security mode
        CREATE_ALWAYS,              //== always creates the file
        FILE_ATTRIBUTE_NORMAL,      //== sets file attributes
        NULL                        //== template file
    );

    if( mhOutfile == INVALID_HANDLE_VALUE ){
        Error( __LINE__ );
        return false;
    }

    LogEvent( LOG_INFORM, "MyServe starting..." );
    return NO_ERROR;
}

int MyService::Run( void )
{
    DWORD   msg_sz = 0;
    DWORD   bytes_read = 0; 
    DWORD   bytes_written = 0;
    BOOL    aok; 
    char    buff[4096];
 
    m_running = true;            //== changed by OnStop()

    while( m_running ){
        aok = GetMailslotInfo(
            mhMailslot,         // mailslot handle 
            NULL,               // no maximum message size 
            &msg_sz,            // size of next message 
            NULL,               // number of messages 
            NULL                // no read time-out 
        );
        if( !aok )
            Error( __LINE__ );
                    
        if(aok && (msg_sz != MAILSLOT_NO_MESSAGE)) 
        {
            if( ReadFile( mhMailslot, buff, msg_sz, 
                    &bytes_read, NULL ))
                WriteFile( mhOutfile, buff, bytes_read, 
                    &bytes_written, NULL);
        }
      }
    
    CloseHandle( mhOutfile );
    CloseHandle( mhMailslot );
    return NO_ERROR; 
}

void MyService::OnStop( void )
{
    m_running = false;
}

#ifdef LOGGING
void MyService::LogEvent( LOG_LEVEL level, char* argstr, ... )
{
    va_list vl;
    va_start( vl, argstr );
    
    if( m_log )
        m_log->LogEventVL( level, argstr, vl );
}

void MyService::InstallAid( void )
{
    //== install the logging information
    if( m_log )
        m_log->Install();
}

void MyService::Error( long line = 0 )
{
    DWORD errval = GetLastError();
    LPVOID lpMsgBuf;

    FormatMessage( 
        FORMAT_MESSAGE_ALLOCATE_BUFFER | 
        FORMAT_MESSAGE_FROM_SYSTEM | 
        FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,
        GetLastError(),
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default 
                                                   // language
        (LPTSTR) &lpMsgBuf,
        0,
        NULL 
    );
    LogEvent( LOG_ERROR, "line (%d): %s", line, lpMsgBuf );
    LocalFree( lpMsgBuf );
}
#endif
//End of File
August 1998/A Wrapper Class for NT Services/Listing 6

Listing 6: Header file for TAppLog class

#ifndef _TAPPLOG_H_
#define _TAPPLOG_H_

#include <stdarg.h>

enum LOG_LEVEL 
{ 
    LOG_NONE, 
    LOG_ERROR, 
    LOG_WARNING,
    LOG_INFORM, 
    LOG_ALL,
    LOG_DEBUG
};

class TAppLog
{
    HANDLE      m_hEventLog;
    LOG_LEVEL   m_loglevel;
    char        m_srcname[36];

//=================================================================
protected:
    TAppLog( void );
    TAppLog( TAppLog& );

    bool IsInstalled( void );

public:
    TAppLog( LPTSTR src_name, LOG_LEVEL loglevel = LOG_ALL );
    virtual ~TAppLog( void );

    virtual bool    Install( void );
    virtual bool    Uninstall( void );

    virtual bool LogEvent( LOG_LEVEL, char*, ...);
    virtual bool LogEventVL( LOG_LEVEL, char*, va_list );

    virtual LOG_LEVEL SetLogLevel( LOG_LEVEL loglevel );
};

inline LOG_LEVEL TAppLog::SetLogLevel( LOG_LEVEL loglevel )
{ 
    return (m_loglevel = loglevel);
}

#endif
/* End of File */
August 1998/A Wrapper Class for NT Services/Listing 7

Listing 7: Implementation file for TAppLog class

#include <windows.h>
#include <stdio.h>
#include <stdarg.h>

#include "tapplog.h"
#include "mcmsg.h"

static const char szRegBasePath[] = 
    "SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\";

TAppLog::TAppLog( char *src_name, LOG_LEVEL loglevel ) 
    : m_loglevel( loglevel )
    , m_hEventLog( 0 )
{
    memset( m_srcname, 0, sizeof( m_srcname ) );
    strncpy( m_srcname, src_name, sizeof(m_srcname)-1);
    //== do not register the service here because it may have 
    //== never been installed!
}

TAppLog::~TAppLog()
{
    if( m_hEventLog )
        DeregisterEventSource( m_hEventLog );
}

bool    TAppLog::IsInstalled( void )
{
    HKEY hKey = NULL;
    char szRegKey[1024];

    sprintf( szRegKey, "%s%s", szRegBasePath, m_srcname );

    if (::RegOpenKey(HKEY_LOCAL_MACHINE, szRegKey, &hKey) == 
              ERROR_SUCCESS)
    {
        RegCloseKey( hKey );
        return true;
    }
    return false;
}

bool    TAppLog::Install( void )
{
    if( IsInstalled() )
        return true;
    
    char szRegKey[1024];
    char szFilePath[ _MAX_PATH ];
    HKEY hKey            = NULL;
    DWORD dwMsgTypes    = EVENTLOG_ERROR_TYPE 
                        | EVENTLOG_WARNING_TYPE 
                        | EVENTLOG_INFORMATION_TYPE;


    // Get the executable file path
    ::GetModuleFileName(NULL, szFilePath, sizeof(szFilePath));

    sprintf( szRegKey, "%s%s", szRegBasePath, m_srcname );

    int err = 0;
    if ((err = ::RegCreateKey(HKEY_LOCAL_MACHINE, szRegKey, &hKey)) 
           != ERROR_SUCCESS)
    {
        //== write error to console here!
        fprintf( stderr, 
           "TAppLog reports error %d creating registery key: %s\n",
           err, szRegKey );
//      fprintf( stderr, "%s\n", szRegKey );
        return false;
    }

    ::RegSetValueEx(hKey,"EventMessageFile", 0,REG_SZ, 
        (CONST BYTE*)szFilePath, sizeof( szFilePath ));
    ::RegSetValueEx(hKey,"TypesSupported", 0, REG_DWORD, 
        (CONST BYTE*)&dwMsgTypes, sizeof( DWORD ));

    RegCloseKey( hKey );
    return true;
}

bool    TAppLog::Uninstall( void )
{
    return true;
}

bool    TAppLog::LogEvent( LOG_LEVEL level, char* argstr, ...)
{
    va_list vl;
    va_start( vl, argstr );
    
    return LogEventVL( level, argstr, vl );
}

bool TAppLog::LogEventVL( LOG_LEVEL level, char* argstr, 
         va_list vl )
{
    char msgbuff[1024];

    //== only log the messages asked for
    if( level > m_loglevel )
        return false;
    
    //== make sure we are installed
    if( !IsInstalled() )
        Install();
    
    //== have we registered the event source?
    if( !m_hEventLog )
    {
        if( (m_hEventLog =
                  ::RegisterEventSource( NULL, m_srcname )) == 0)
            return false;
    }

    //== build the message
    vsprintf( msgbuff, argstr, vl);

    //== determine ReportError type from msglevel
    //== get the base message from the resource file
    DWORD dwEventID = 0;
    WORD wType = 0;
    switch( level )
    {
    case LOG_ERROR:
        wType = EVENTLOG_ERROR_TYPE;
        dwEventID = NTSRV_ERROR;
        break;

    case LOG_WARNING:
        dwEventID = NTSRV_WARN;
        wType = EVENTLOG_WARNING_TYPE;
        break;

    case LOG_INFORM:
        dwEventID = NTSRV_INFO;
        wType = EVENTLOG_INFORMATION_TYPE;
        break;

    default:
        dwEventID = NTSRV_NOTE;
        wType = EVENTLOG_INFORMATION_TYPE;
    }
    const char *pp_msgs[1];
    pp_msgs[0] = msgbuff;
    
    //== Report the event
    BOOL bRetval =
        ReportEvent(  
            m_hEventLog, // handle returned by RegisterEventSource
            wType,       // event type to log
            0,           // event category
            dwEventID,   // event identifier
            NULL,        // user security identifier (optional)
            1,           // number of strings to merge with message
            0,           // size of binary data, in bytes
            pp_msgs,     // array of strings to merge with message
            NULL         // address of binary data
        );
    
    //== if we are debugging echo everything to the debugger
    if( m_loglevel == LOG_DEBUG )
    {
        ::OutputDebugString( msgbuff );
    }

    return (bRetval != FALSE);
}
//End of File
August 1998/A Wrapper Class for NT Services/Listing 8

Listing 8: Message definition file for event logging

;/*++ BUILD Version: 0001 // Increment this if a change 
;                         // has global effects
;Copyright 1998 RWD Technologies
;
;Module Name:
;
;    mcmsg.mc
;
;Abstract:
;
;    This file contains the error code definitions and message
;    for the ccfeServe program.
;
;Author:
;
;    John T. Bell
;
;--*/
;
;#ifndef _MCMSG_
;#define _MCMSG_
;
;

SeverityNames=(
    Note=0x0:APP_SEVERITY_NOTE
    Informational=0x1:APP_SEVERITY_INFORMATIONAL
    Warning=0x2:APP_SEVERITY_WARNING
    Error=0x3:APP_SEVERITY_ERROR
)

MessageId= 
Severity=Note
SymbolicName=NTSRV_NOTE
Language=English
Note: %1
.

MessageId= 
Severity=Informational
SymbolicName=NTSRV_INFO
Language=English
Info: %1
.
MessageId= 
Severity=Warning
SymbolicName=NTSRV_WARN
Language=English
Warning: %1
.
MessageId= 
Severity=Error 
SymbolicName=NTSRV_ERROR
Language=English
Error: %1
;#endif // _MCMSG_
// EOF
August 1998/A Wrapper Class for NT Services/Table 1

Table 1: The service dispatch table

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.