Writing a Windows Service in C++
In the Windows operating system, a service is a kind of application that runs in the background. A service can run even when no user is logged on, and can be started automatically at boot or manually on demand. Windows services are managed by the Service Control Manager (SCM), and must implement a specific API, so the SCM can interact with them. Let’s look at how to build one.
A traditional C++ Windows application starts in the wWinMain
function. The same applies for services, however, the only thing a service should do in its standard entrypoint is to register with the SCM.
Registering with the SCM is done by passing it a service table, which contains the address of the ServiceMain
function — a function that actually implements the logic of the service (“logic” here just being a fancy term for “the thing you want the service to do”).
Within ServiceMain
, you then have to register a ServiceCtrlHandler
function, which is responsible for handling commands from the SCM (such as stopping the service).
Initial Declarations
Let’s start with the forward declarations of the two aforementioned functions. It is not a good idea to put the entire logic of the service to ServiceMain
, so we additionally declare a third function called ServiceWorker
.
VOID WINAPI ServiceMain(DWORD argc, LPTSTR* argv);
VOID WINAPI ServiceCtrlHandler(DWORD ctrlCode);
void ServiceWorker(int& exitCode);
For the sake of simplicity, we’ll put the service table in a global variable, as well as the information about the current status of the service and a handle that we can use to inform the SCM about changes to the status.
TCHAR ServiceName[] = _T("");
SERVICE_TABLE_ENTRY ServiceTable[] = {
{ServiceName, (LPSERVICE_MAIN_FUNCTION) ServiceMain},
{NULL, NULL}
};
SERVICE_STATUS ServiceStatus = {0};
SERVICE_STATUS_HANDLE SvcStatusHandle = NULL;
Since we’ll be running the service in its own process, ServiceName
can be empty (it will be ignored anyways).
Program Entrypoint
In the program entrypoint, the service should immediately register with the SCM. The SCM will then start ServiceMain
in a new thread, while the main thread remains blocked at the call to StartServiceCtrlDispatcher
until the service stops.
int WINAPI wWinMain(HINSTANCE hInst, HINSTANCE hPrInst, PWSTR pCmdLn, int nCmdSh) {
if (!StartServiceCtrlDispatcher(ServiceTable)) {
DWORD errno = GetLastError();
if (errno == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) {
// executable launched outside Service Control Manager
}
return 1;
}
return 0;
}
You can compile a single executable to be used both as a service and a regular application: check the return value and error code of StartServiceCtrlDispatcher
to detect which way the executable was launched.
Main Loop
ServiceMain
must register a handler that responds to commands from the Service Control Manager (for example stopping the service). Then it must inform the SCM that the service has started and actually begin executing the service logic.
VOID WINAPI ServiceMain(DWORD argc, LPTSTR* argv) {
// register handler for commands from SCM
SvcStatusHandle = RegisterServiceCtrlHandler(ServiceName), ServiceCtrlHandler);
if (SvcStatusHandle == NULL) return;
// inform SCM the service is starting
ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
ServiceStatus.dwControlsAccepted = 0;
ServiceStatus.dwCurrentState = SERVICE_START_PENDING;
SetServiceStatus(SvcStatusHandle, &ServiceStatus);
// start service logic
int exitCode = 0;
std::thread worker(ServiceWorker, std::ref(exitCode));
// inform SCM the service has started
ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
ServiceStatus.dwCurrentState = SERVICE_RUNNING;
SetServiceStatus(SvcStatusHandle, &ServiceStatus);
// wait until the service has finished
worker.join();
// inform SCM the service has stopped
ServiceStatus.dwControlsAccepted = 0;
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
ServiceStatus.dwWin32ExitCode = exitCode;
SetServiceStatus(SvcStatusHandle, &ServiceStatus);
}
The SCM waits until the service reports SERVICE
. Defer any lengthy processing to after you set this status, especially if your service starts at boot, since all other services (and the entire boot process) will wait for your service. A service has 30 seconds to transition to the running state until the system terminates it.
In our architecture, ServiceWorker
is where you implement the actual thing you want the service to do.
void ServiceWorker(int& exitCode) {
while (ServiceStatus.dwCurrentState != SERVICE_STOP_PENDING) {
// do something instead of just sleeping
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
Control Handler
The service control handler receives and handles commands from the SCM. For a simple service it’s enough to support just the SERVICE
command.
VOID WINAPI ServiceCtrlHandler(DWORD ctrlCode) {
switch (ctrlCode) {
case SERVICE_CONTROL_STOP:
if (ServiceStatus.dwCurrentState != SERVICE_RUNNING) break;
// inform SCM the service has received the stop command
// and signal to the program it should stop
ServiceStatus.dwControlsAccepted = 0;
ServiceStatus.dwCurrentState = SERVICE_STOP_PENDING;
SetServiceStatus(SvcStatusHandle, &ServiceStatus);
break;
default:
break;
}
}
If you changed the implementation of ServiceWorker
, make sure ServiceCtrlHandler
is still able to stop it (maybe it’ll need to terminate the worker thread forcefully, for example).
Service Installation
After you stitch all code blocks above together one after another, compile the code to an .exe
file. Then, use the sc.exe
tool to install the service (note that the spaces after equals signs are necessary):
sc.exe create "mysvc" displayname= "My Service" binPath= "C:\mysvc.exe"
Now the service should be visible in the Services control panel and you should be able to start it.
References
Further information can be found on MSDN: