360 lines
11 KiB
C++
360 lines
11 KiB
C++
/****************************** 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 <assert.h>
|
|
#include <strsafe.h>
|
|
#pragma endregion
|
|
|
|
#pragma region Static Members
|
|
|
|
// Initialize the singleton service instance.
|
|
CServiceBase* CServiceBase::s_service = nullptr;
|
|
|
|
bool CServiceBase::Run(CServiceBase& service) {
|
|
s_service = &service;
|
|
|
|
SERVICE_TABLE_ENTRY serviceTable[] = { { service.m_name, ServiceMain }, { nullptr, nullptr } };
|
|
|
|
// 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) == TRUE;
|
|
}
|
|
|
|
void WINAPI CServiceBase::ServiceMain(DWORD argc, LPSTR* argv) {
|
|
assert(s_service != nullptr);
|
|
|
|
// Register the handler function for the service
|
|
s_service->m_statusHandle = RegisterServiceCtrlHandler(s_service->m_name, ServiceCtrlHandler);
|
|
if (s_service->m_statusHandle == nullptr) {
|
|
throw GetLastError();
|
|
}
|
|
|
|
// Start the service.
|
|
s_service->Start(argc, argv);
|
|
}
|
|
|
|
// the control code 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(char* serviceName, bool fCanStop, bool fCanShutdown, bool fCanPauseContinue) {
|
|
// Service name must be a valid string and cannot be nullptr.
|
|
m_name = (serviceName == nullptr) ? const_cast<char*>("") : serviceName;
|
|
|
|
m_statusHandle = nullptr;
|
|
|
|
// 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
|
|
|
|
void CServiceBase::Start(DWORD dwArgc, LPSTR* lpszArgv) {
|
|
try {
|
|
// Tell SCM that the service is starting.
|
|
SetServiceStatus(SERVICE_START_PENDING);
|
|
|
|
// Perform service-specific initialization.
|
|
OnStart(dwArgc, lpszArgv);
|
|
|
|
// 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::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 original service status.
|
|
SetServiceStatus(dwOriginalState);
|
|
} catch (...) {
|
|
// Log the error.
|
|
WriteEventLogEntry("Service failed to stop.", EVENTLOG_ERROR_TYPE);
|
|
|
|
// Set the original service status.
|
|
SetServiceStatus(dwOriginalState);
|
|
}
|
|
}
|
|
|
|
//
|
|
// 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::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::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);
|
|
}
|
|
}
|
|
|
|
#pragma region Helper Functions
|
|
|
|
// * 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);
|
|
}
|
|
|
|
// the type of event to be logged 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(const char* message, int wType) {
|
|
HANDLE hEventSource = nullptr;
|
|
LPCSTR lpszStrings[2] = { nullptr, nullptr };
|
|
|
|
hEventSource = RegisterEventSource(nullptr, m_name);
|
|
if (hEventSource) {
|
|
lpszStrings[0] = m_name;
|
|
lpszStrings[1] = message;
|
|
|
|
ReportEvent(hEventSource, // Event log handle
|
|
wType, // Event type
|
|
0, // Event category
|
|
0, // Event identifier
|
|
nullptr, // No security identifier
|
|
2, // Size of lpszStrings array
|
|
0, // No binary data
|
|
lpszStrings, // Array of strings
|
|
nullptr // No binary data
|
|
);
|
|
|
|
DeregisterEventSource(hEventSource);
|
|
}
|
|
}
|
|
|
|
void CServiceBase::WriteErrorLogEntry(const char* function, int error) {
|
|
char message[260];
|
|
StringCchPrintf(message, ARRAYSIZE(message), "%s failed w/err 0x%08lx", function, error);
|
|
WriteEventLogEntry(message, EVENTLOG_ERROR_TYPE);
|
|
}
|
|
|
|
#pragma endregion |