/****************************** Module Header ******************************\ * Module Name: ServiceBase.cpp * Project: CppWindowsService * Copyright (c) Microsoft Corporation. * * Provides a base class for a service that will exist as part of a service * application. CServiceBase must be derived from when creating a new service * class. * * This source is subject to the Microsoft Public License. * See http://www.microsoft.com/en-us/openness/resources/licenses.aspx#MPL. * All other rights reserved. * * THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, * EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. \***************************************************************************/ #pragma region Includes #include "ServiceBase.h" #include #include #include #pragma endregion #pragma region Static Members // Initialize the singleton service instance. CServiceBase *CServiceBase::s_service = NULL; // // FUNCTION: CServiceBase::Run(CServiceBase &) // // PURPOSE: Register the executable for a service with the Service Control // Manager (SCM). After you call Run(ServiceBase), the SCM issues a Start // command, which results in a call to the OnStart method in the service. // This method blocks until the service has stopped. // // PARAMETERS: // * service - the reference to a CServiceBase object. It will become the // singleton service instance of this service application. // // RETURN VALUE: If the function succeeds, the return value is TRUE. If the // function fails, the return value is FALSE. To get extended error // information, call GetLastError. // BOOL CServiceBase::Run(CServiceBase &service) { s_service = &service; SERVICE_TABLE_ENTRYA serviceTable[] = { { service.m_name, ServiceMain }, { NULL, NULL } }; // Connects the main thread of a service process to the service control // manager, which causes the thread to be the service control dispatcher // thread for the calling process. This call returns when the service has // stopped. The process should simply terminate when the call returns. return StartServiceCtrlDispatcher(serviceTable); } // // FUNCTION: CServiceBase::ServiceMain(DWORD, PWSTR *) // // PURPOSE: Entry point for the service. It registers the handler function // for the service and starts the service. // // PARAMETERS: // * dwArgc - number of command line arguments // * lpszArgv - array of command line arguments // void WINAPI CServiceBase::ServiceMain(DWORD dwArgc, PSTR *pszArgv) { assert(s_service != NULL); // Register the handler function for the service s_service->m_statusHandle = RegisterServiceCtrlHandler( s_service->m_name, ServiceCtrlHandler); if (s_service->m_statusHandle == NULL) { throw GetLastError(); } // Start the service. s_service->Start(dwArgc, pszArgv); } // // FUNCTION: CServiceBase::ServiceCtrlHandler(DWORD) // // PURPOSE: The function is called by the SCM whenever a control code is // sent to the service. // // PARAMETERS: // * dwCtrlCode - the control code. This parameter can be one of the // following values: // // SERVICE_CONTROL_CONTINUE // SERVICE_CONTROL_INTERROGATE // SERVICE_CONTROL_NETBINDADD // SERVICE_CONTROL_NETBINDDISABLE // SERVICE_CONTROL_NETBINDREMOVE // SERVICE_CONTROL_PARAMCHANGE // SERVICE_CONTROL_PAUSE // SERVICE_CONTROL_SHUTDOWN // SERVICE_CONTROL_STOP // // This parameter can also be a user-defined control code ranges from 128 // to 255. // void WINAPI CServiceBase::ServiceCtrlHandler(DWORD dwCtrl) { switch (dwCtrl) { case SERVICE_CONTROL_STOP: s_service->Stop(); break; case SERVICE_CONTROL_PAUSE: s_service->Pause(); break; case SERVICE_CONTROL_CONTINUE: s_service->Continue(); break; case SERVICE_CONTROL_SHUTDOWN: s_service->Shutdown(); break; case SERVICE_CONTROL_INTERROGATE: break; default: break; } } #pragma endregion #pragma region Service Constructor and Destructor // // FUNCTION: CServiceBase::CServiceBase(PWSTR, BOOL, BOOL, BOOL) // // PURPOSE: The constructor of CServiceBase. It initializes a new instance // of the CServiceBase class. The optional parameters (fCanStop, /// fCanShutdown and fCanPauseContinue) allow you to specify whether the // service can be stopped, paused and continued, or be notified when system // shutdown occurs. // // PARAMETERS: // * pszServiceName - the name of the service // * fCanStop - the service can be stopped // * fCanShutdown - the service is notified when system shutdown occurs // * fCanPauseContinue - the service can be paused and continued // CServiceBase::CServiceBase(PSTR pszServiceName, BOOL fCanStop, BOOL fCanShutdown, BOOL fCanPauseContinue) { // Service name must be a valid string and cannot be NULL. m_name = (pszServiceName == NULL) ? "" : pszServiceName; m_statusHandle = NULL; // The service runs in its own process. m_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS; // The service is starting. m_status.dwCurrentState = SERVICE_START_PENDING; // The accepted commands of the service. DWORD dwControlsAccepted = 0; if (fCanStop) dwControlsAccepted |= SERVICE_ACCEPT_STOP; if (fCanShutdown) dwControlsAccepted |= SERVICE_ACCEPT_SHUTDOWN; if (fCanPauseContinue) dwControlsAccepted |= SERVICE_ACCEPT_PAUSE_CONTINUE; m_status.dwControlsAccepted = dwControlsAccepted; m_status.dwWin32ExitCode = NO_ERROR; m_status.dwServiceSpecificExitCode = 0; m_status.dwCheckPoint = 0; m_status.dwWaitHint = 0; } // // FUNCTION: CServiceBase::~CServiceBase() // // PURPOSE: The virtual destructor of CServiceBase. // CServiceBase::~CServiceBase(void) { } #pragma endregion #pragma region Service Start, Stop, Pause, Continue, and Shutdown // // FUNCTION: CServiceBase::Start(DWORD, PWSTR *) // // PURPOSE: The function starts the service. It calls the OnStart virtual // function in which you can specify the actions to take when the service // starts. If an error occurs during the startup, the error will be logged // in the Application event log, and the service will be stopped. // // PARAMETERS: // * dwArgc - number of command line arguments // * lpszArgv - array of command line arguments // void CServiceBase::Start(DWORD dwArgc, PSTR *pszArgv) { try { // Tell SCM that the service is starting. SetServiceStatus(SERVICE_START_PENDING); // Perform service-specific initialization. OnStart(dwArgc, pszArgv); // Tell SCM that the service is started. SetServiceStatus(SERVICE_RUNNING); } catch (DWORD dwError) { // Log the error. WriteErrorLogEntry("Service Start", dwError); // Set the service status to be stopped. SetServiceStatus(SERVICE_STOPPED, dwError); } catch (...) { // Log the error. WriteEventLogEntry("Service failed to start.", EVENTLOG_ERROR_TYPE); // Set the service status to be stopped. SetServiceStatus(SERVICE_STOPPED); } } // // FUNCTION: CServiceBase::OnStart(DWORD, PWSTR *) // // PURPOSE: When implemented in a derived class, executes when a Start // command is sent to the service by the SCM or when the operating system // starts (for a service that starts automatically). Specifies actions to // take when the service starts. Be sure to periodically call // CServiceBase::SetServiceStatus() with SERVICE_START_PENDING if the // procedure is going to take long time. You may also consider spawning a // new thread in OnStart to perform time-consuming initialization tasks. // // PARAMETERS: // * dwArgc - number of command line arguments // * lpszArgv - array of command line arguments // void CServiceBase::OnStart(DWORD dwArgc, PSTR *pszArgv) { } // // FUNCTION: CServiceBase::Stop() // // PURPOSE: The function stops the service. It calls the OnStop virtual // function in which you can specify the actions to take when the service // stops. If an error occurs, the error will be logged in the Application // event log, and the service will be restored to the original state. // void CServiceBase::Stop() { DWORD dwOriginalState = m_status.dwCurrentState; try { // Tell SCM that the service is stopping. SetServiceStatus(SERVICE_STOP_PENDING); // Perform service-specific stop operations. OnStop(); // Tell SCM that the service is stopped. SetServiceStatus(SERVICE_STOPPED); } catch (DWORD dwError) { // Log the error. WriteErrorLogEntry("Service Stop", dwError); // Set the orginal service status. SetServiceStatus(dwOriginalState); } catch (...) { // Log the error. WriteEventLogEntry("Service failed to stop.", EVENTLOG_ERROR_TYPE); // Set the orginal service status. SetServiceStatus(dwOriginalState); } } // // FUNCTION: CServiceBase::OnStop() // // PURPOSE: When implemented in a derived class, executes when a Stop // command is sent to the service by the SCM. Specifies actions to take // when a service stops running. Be sure to periodically call // CServiceBase::SetServiceStatus() with SERVICE_STOP_PENDING if the // procedure is going to take long time. // void CServiceBase::OnStop() { } // // FUNCTION: CServiceBase::Pause() // // PURPOSE: The function pauses the service if the service supports pause // and continue. It calls the OnPause virtual function in which you can // specify the actions to take when the service pauses. If an error occurs, // the error will be logged in the Application event log, and the service // will become running. // void CServiceBase::Pause() { try { // Tell SCM that the service is pausing. SetServiceStatus(SERVICE_PAUSE_PENDING); // Perform service-specific pause operations. OnPause(); // Tell SCM that the service is paused. SetServiceStatus(SERVICE_PAUSED); } catch (DWORD dwError) { // Log the error. WriteErrorLogEntry("Service Pause", dwError); // Tell SCM that the service is still running. SetServiceStatus(SERVICE_RUNNING); } catch (...) { // Log the error. WriteEventLogEntry("Service failed to pause.", EVENTLOG_ERROR_TYPE); // Tell SCM that the service is still running. SetServiceStatus(SERVICE_RUNNING); } } // // FUNCTION: CServiceBase::OnPause() // // PURPOSE: When implemented in a derived class, executes when a Pause // command is sent to the service by the SCM. Specifies actions to take // when a service pauses. // void CServiceBase::OnPause() { } // // FUNCTION: CServiceBase::Continue() // // PURPOSE: The function resumes normal functioning after being paused if // the service supports pause and continue. It calls the OnContinue virtual // function in which you can specify the actions to take when the service // continues. If an error occurs, the error will be logged in the // Application event log, and the service will still be paused. // void CServiceBase::Continue() { try { // Tell SCM that the service is resuming. SetServiceStatus(SERVICE_CONTINUE_PENDING); // Perform service-specific continue operations. OnContinue(); // Tell SCM that the service is running. SetServiceStatus(SERVICE_RUNNING); } catch (DWORD dwError) { // Log the error. WriteErrorLogEntry("Service Continue", dwError); // Tell SCM that the service is still paused. SetServiceStatus(SERVICE_PAUSED); } catch (...) { // Log the error. WriteEventLogEntry("Service failed to resume.", EVENTLOG_ERROR_TYPE); // Tell SCM that the service is still paused. SetServiceStatus(SERVICE_PAUSED); } } // // FUNCTION: CServiceBase::OnContinue() // // PURPOSE: When implemented in a derived class, OnContinue runs when a // Continue command is sent to the service by the SCM. Specifies actions to // take when a service resumes normal functioning after being paused. // void CServiceBase::OnContinue() { } // // FUNCTION: CServiceBase::Shutdown() // // PURPOSE: The function executes when the system is shutting down. It // calls the OnShutdown virtual function in which you can specify what // should occur immediately prior to the system shutting down. If an error // occurs, the error will be logged in the Application event log. // void CServiceBase::Shutdown() { try { // Perform service-specific shutdown operations. OnShutdown(); // Tell SCM that the service is stopped. SetServiceStatus(SERVICE_STOPPED); } catch (DWORD dwError) { // Log the error. WriteErrorLogEntry("Service Shutdown", dwError); } catch (...) { // Log the error. WriteEventLogEntry("Service failed to shut down.", EVENTLOG_ERROR_TYPE); } } // // FUNCTION: CServiceBase::OnShutdown() // // PURPOSE: When implemented in a derived class, executes when the system // is shutting down. Specifies what should occur immediately prior to the // system shutting down. // void CServiceBase::OnShutdown() { } #pragma endregion #pragma region Helper Functions // // FUNCTION: CServiceBase::SetServiceStatus(DWORD, DWORD, DWORD) // // PURPOSE: The function sets the service status and reports the status to // the SCM. // // PARAMETERS: // * dwCurrentState - the state of the service // * dwWin32ExitCode - error code to report // * dwWaitHint - estimated time for pending operation, in milliseconds // void CServiceBase::SetServiceStatus(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwWaitHint) { static DWORD dwCheckPoint = 1; // Fill in the SERVICE_STATUS structure of the service. m_status.dwCurrentState = dwCurrentState; m_status.dwWin32ExitCode = dwWin32ExitCode; m_status.dwWaitHint = dwWaitHint; m_status.dwCheckPoint = ((dwCurrentState == SERVICE_RUNNING) || (dwCurrentState == SERVICE_STOPPED)) ? 0 : dwCheckPoint++; // Report the status of the service to the SCM. ::SetServiceStatus(m_statusHandle, &m_status); } // // FUNCTION: CServiceBase::WriteEventLogEntry(PWSTR, WORD) // // PURPOSE: Log a message to the Application event log. // // PARAMETERS: // * pszMessage - string message to be logged. // * wType - the type of event to be logged. The parameter can be one of // the following values. // // EVENTLOG_SUCCESS // EVENTLOG_AUDIT_FAILURE // EVENTLOG_AUDIT_SUCCESS // EVENTLOG_ERROR_TYPE // EVENTLOG_INFORMATION_TYPE // EVENTLOG_WARNING_TYPE // void CServiceBase::WriteEventLogEntry(PSTR pszMessage, WORD wType) { HANDLE hEventSource = NULL; LPCSTR lpszStrings[2] = { NULL, NULL }; hEventSource = RegisterEventSource(NULL, m_name); if (hEventSource) { lpszStrings[0] = m_name; lpszStrings[1] = pszMessage; ReportEvent(hEventSource, // Event log handle wType, // Event type 0, // Event category 0, // Event identifier NULL, // No security identifier 2, // Size of lpszStrings array 0, // No binary data lpszStrings, // Array of strings NULL // No binary data ); DeregisterEventSource(hEventSource); } } // // FUNCTION: CServiceBase::WriteErrorLogEntry(PWSTR, DWORD) // // PURPOSE: Log an error message to the Application event log. // // PARAMETERS: // * pszFunction - the function that gives the error // * dwError - the error code // void CServiceBase::WriteErrorLogEntry(PSTR pszFunction, DWORD dwError) { char szMessage[260]; StringCchPrintf(szMessage, ARRAYSIZE(szMessage), "%s failed w/err 0x%08lx", pszFunction, dwError); WriteEventLogEntry(szMessage, EVENTLOG_ERROR_TYPE); } #pragma endregion