Friday, April 20, 2012

Writing Windows Services in C

Overview

This is a quick guide for writing windows services in C; which includes introduction, an example, short manual for service control manager (SCM), and references for more information.

Presumptions

1- You have a good C programming background.
2- You are planning to write your own windows service. I guess it is good to start
from here.

Introduction

A service is a console application that runs in the background and performs tasks that don't require user interaction. The Windows NT/2000/XP operating systems offer special support for service programs. For windows Vista and 7 please read the notes below.

In order to create a windows service, we need three additional functions

1) Service Main: commonly called “ServiceMain” and the standard prototype for it is

void WINAPI ServiceMain(DWORD argc, LPTSTR *argv);

It sends information to Service Control Manager (SCM) - which will take care of the status of the service - and it will register the service in the SCM.In the end of this function we will have a loop which will have our service.

2) Service CTRL Handler: this function will handle the standard requests from SCM and will response for its requests. The prototype of this function is:

void WINAPI ServiceCtrlHandler(DWORD opcode);

3) Service Initialization function: this function will initialize the Service topically global variables, cleaning up stuff if needed, and preparation for the service. Note that this function should return a value to tell the SCM that Service successfully initialized.The prototype is:

DWORD ServiceInitialization(DWORD argc, LPTSTR *argv, DWORD *specificError);

Notes

Before showing an example I would like you to consider the following notes:

1- Service name should not contain spaces and it should not be long. In the given example below it was named “MyService”.

2- WINAPI is same as __stdcall and it is not really needed on some compiler.  Also DWORD is equivalent for unsigned long.

3- The outputs of the service on x64 systems will be under “c:\Windows\SysWOW64”, and on x32 systems under “C:\Windows\System32”.

4- If the service requires an interactive process (like sending messages or loading forms) then go and read more about this in the references below. On some versions of windows in control panel --> system and security --> administrative tools --> services, then right click on the service and click on properties, then in the “Log On” tap check “Allow this service to interact with desktop” this may solve the problem.

5- Sometime service seems to be running nicely but it is not behaving as it should, well in this case we should consider the permissions give to this service, specially if we are reading/writing, a great tool called “Procmon” available on Microsoft website allow us to monitor the process so we can debug it, after installing run it and select PID filter to display your service behaviors only.

6- Each service executes in the security context of a user account, and it can be “LocalService”, “NetworkService”, or LocalSystem, every account has different privileges. These accounts do not have passwords.

7- A command-line utility, sc.exe also can be used to control services.  Its commands correspond to the functions provided by the SCM. It Service (like example below) does not have “ServiceInstall” function then we would use this tool to install it.

8- Windows Vista/7 has a security feature called “Session 0 Isolation” which means service runs on Session 0 and regular user runs on session 1, and they can’t send each other messages, so it could be a problem for some services, as solution CreateProcessAsUser function may does the job.

9- The example compiled and tested under windows 7 ultimate x64 with minGW- GCC version 4.6.1.

10- If compiler did not detect the ” _WIN32_WINNT”  declaration automatically then for windows 7 it is (0x0601), Vista (0x0600), and XP (0x0501).

11- Text editors can read and write files using at least the different ASCII CR/LF conventions. The text editor Notepad is not one of them so it would be better to use Wordpad if  “myRamInfo.txt” does not seem to have proper format.

 

Example

/**************************************************************
In this example, the function WriteMemInfo() will write information about available physical memory(RAM) on the system. The file myRamInfo.txt will be updated every 5 seconds.
**************************************************************/

//#define _WIN32_WINNT 0x0501 // for XP
//#define _WIN32_WINNT 0x0601 // for 7
//#define _WIN32_WINNT 0x0600 // for Vista

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


SERVICE_STATUS ServiceStatus;
SERVICE_STATUS_HANDLE ServiceStatusHandle;

DWORD ServiceInitialization(DWORD argc, LPTSTR *argv, DWORD *specificError);

void WINAPI ServiceMain(DWORD argc, LPTSTR *argv);
void WINAPI ServiceCtrlHandler(DWORD opcode);
void WriteMemInfo(void);


int main(){

SERVICE_TABLE_ENTRY DispatchTable[] = {{"MyService", ServiceMain}, {NULL, NULL}};

if (!StartServiceCtrlDispatcher(DispatchTable))
//WriteMemInfo("StartServiceCtrlDispatcher() failed, error: %d.\n", GetLastError());
printf("StartServiceCtrlDispatcher() failed, error: %ld.\n", GetLastError());
else
printf("StartServiceCtrlDispatcher() looks OK.\n");

return 0;
}

// Stub initialization function...
DWORD ServiceInitialization(DWORD argc, LPTSTR *argv, DWORD *specificError){
// These statments have no effect!
*argv;
argc;
specificError;

return 0;
}

void WINAPI ServiceMain(DWORD argc, LPTSTR *argv){

DWORD status;
DWORD specificError;

// Type of service, application or driver...
ServiceStatus.dwServiceType = SERVICE_WIN32;

// The service is starting...
ServiceStatus.dwCurrentState = SERVICE_START_PENDING;

// The service can be stopped & can be paused and continued.
ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;
ServiceStatus.dwWin32ExitCode = 0;
ServiceStatus.dwServiceSpecificExitCode = 0;
ServiceStatus.dwCheckPoint = 0;
ServiceStatus.dwWaitHint = 0;

ServiceStatusHandle = RegisterServiceCtrlHandler("MyService", ServiceCtrlHandler);

if (ServiceStatusHandle == (SERVICE_STATUS_HANDLE)0){

printf("RegisterServiceCtrlHandler() failed, error: %ld.\n", GetLastError());
return;
}else
printf("RegisterServiceCtrlHandler() looks OK.\n");

// Initialization code goes here...return the status...
status = ServiceInitialization(argc, argv, &specificError);

// Handle error condition
if (status != NO_ERROR){

// The service is not running...
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
ServiceStatus.dwCheckPoint = 0;
ServiceStatus.dwWaitHint = 0;
ServiceStatus.dwWin32ExitCode = status;
ServiceStatus.dwServiceSpecificExitCode = specificError;
SetServiceStatus(ServiceStatusHandle, &ServiceStatus);
return;
}

// Initialization complete - report running status.
ServiceStatus.dwCurrentState = SERVICE_RUNNING;
ServiceStatus.dwCheckPoint = 0;
ServiceStatus.dwWaitHint = 0;

if (!SetServiceStatus(ServiceStatusHandle, &ServiceStatus)){
status = GetLastError();
printf("SetServiceStatus() error: %ld\n", status);
}
else
printf("SetServiceStatus() looks OK.\n");

// This is where the service does its work...

while(ServiceStatus.dwCurrentState == SERVICE_RUNNING){

WriteMemInfo();

Sleep(5000);
}
return;
}

// Handler function - receives Opcode, calls SetServiceStatus()
void WINAPI ServiceCtrlHandler(DWORD Opcode){

DWORD status;

switch(Opcode){

case SERVICE_CONTROL_PAUSE:

// Do whatever it takes to pause here...
ServiceStatus.dwCurrentState = SERVICE_PAUSED;
break;

case SERVICE_CONTROL_CONTINUE:

// Do whatever it takes to continue here...
ServiceStatus.dwCurrentState = SERVICE_RUNNING;
break;

case SERVICE_CONTROL_STOP:

// Do whatever it takes to stop here...
ServiceStatus.dwWin32ExitCode = 0;
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
ServiceStatus.dwCheckPoint = 0;
ServiceStatus.dwWaitHint = 0;

if (!SetServiceStatus(ServiceStatusHandle, &ServiceStatus)){

status = GetLastError();
printf("[MY_SERVICE] SetServiceStatus() error: %ld\n", status);
}

printf("Leaving MyService.\n");
return;

case SERVICE_CONTROL_INTERROGATE:

// Fall through to send current status.
break;

default:
// else
printf("Unrecognized opcode %ld.\n", Opcode);
}

// Send current status.
if (!SetServiceStatus(ServiceStatusHandle, &ServiceStatus)){

status = GetLastError();
printf("SetServiceStatus error %ld.\n", status);
return;
}

else
printf("SetServiceStatus() is OK.\n");

return;
}

// Some RAM info ...
void WriteMemInfo(void){

MEMORYSTATUS MemState;
MemState.dwLength = sizeof (MemState);
// Get Memory info
GlobalMemoryStatus(&MemState);

// Get Time info
SYSTEMTIME timeInfo;
GetLocalTime(&timeInfo);

FILE *fp;
char Wbuffer[32]=" Free RAM is: ";

sprintf(Wbuffer+strlen(Wbuffer),"%ld MB - ",(MemState.dwAvailPhys)/(1024*1024));
sprintf(Wbuffer+strlen(Wbuffer),"%d:%d:%d \r\n",timeInfo.wHour,timeInfo.wMinute,timeInfo.wSecond);

fp=fopen("myRamInfo.txt", "ab");
fwrite(Wbuffer, sizeof(Wbuffer[0]), sizeof(Wbuffer)/sizeof(Wbuffer[0]), fp);
fclose(fp);

return;
}

Short manual for SC tool


Service Control Manager (SCM) is a special system process under Windows NT family of operating systems, which starts, stops and interacts with Windows service processes.The command-line tool SC.exe allows us to communicate with SCM. Here is a small how to:

Create the service 
sc create MyServiceName binPath= YOUR_EXE_PATH
example
sc create MyServiceName binPath= "c:\Users\username\Desktop\service_test.exe

Start the Service
sc start MyServiceName

Stop the Service
sc stop MyServiceName

Delete the Service
sc delete MyServiceName  


References


[1] An excellent tutorial about windows Services
http://www.tenouk.com/cnwin32tutorials.html

[2] a Five Steps to Writing Windows Services in C
http://www.devx.com/cplus/Article/9857

No comments:

Post a Comment