6/10/2008

Windows Service - How To

Part I - Why to invent so called "Windows Service"?
1. can automatically started when the computer boots, can be paused and restarted
2. can run in their own windows sessions, and do not show any user interface(no keyboard, mouse and monitor needed, background process)
3. similar to "Daemon" in *nix world

Part II - Windows Service Components
1. Service Database
- register based data store
- store configuration/attribute of each service

2. Service Control Manager
- a RPC server started at system boot
- manage service database
- control individual services directly

3. Individual Service
- Service Program, A program that provides executable code for one or more services
- Service Configuration Program, A program that queries or modifies the services database(install/delete/modify service programs)
- Service Control Program, A program that starts and controls services and driver services
- all the upper 3 prgms leverage functionalities from SCM

Part III - Steps to develop a Windows Service
1. composing service program, which includes:
- main function, called by SCM. It should start the service dispatch loop/thread and execute the servicemain function for each service it contains.
- servicemain function, it contains service specific logic.
- control handler, defines how the service serves the control messages from SCM.
- the thread model is "1 + N": 1 thread for control dispatching and N for each servicemain.

2. composing service configuration/control program
- can be in a shared/individual executable
- config program interact with Windows Service in a static way, while control program in a dynamic way
- both talks to Windows Service indirectly using SCM as the mediator

Part IV - How to debug Windows Service:

1. Use your debugger to debug the service while it is running. First, obtain the process identifier (PID) of the service process. After you have obtained the PID, attach to the running process. For syntax information, see the documentation included with your debugger.

2. Call the DebugBreak function to invoke the debugger for just-in-time debugging.
Specify a debugger to use when starting a program. To do so, create a key called Image File Execution Options in the following registry location:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion

3. Use Event Tracing to log information.

4. Write the service as console application first. After debugging/verification, converted it to Windows Service form.

Let's see an code example of a service, who writes an event record to Windows periodically.
Code Snippet I - Service Program
  1 #include <windows.h>
  2 #include <stdio.h>
  3 #include "msg.h"
  4
  5 BOOL g_isShutdown = FALSE;
  6 BOOL g_isPaused = FALSE;
  7
  8 SERVICE_STATUS g_servStat = {0};
  9 SERVICE_STATUS_HANDLE g_hServStat = NULL;
 10
 11 DWORD WINAPI TimerServiceHandler(DWORD  dwControl, DWORD  dwEventType, LPVOID lpEventData, LPVOID lpContext)
 12 {
 13     switch (dwControl)
 14     {
 15     case SERVICE_CONTROL_CONTINUE:
 16         g_isPaused = FALSE;
 17         break;
 18
 19     case SERVICE_CONTROL_PAUSE:
 20         g_isPaused = TRUE;
 21         break;
 22
 23     case SERVICE_CONTROL_SHUTDOWN:
 24         g_isShutdown = TRUE;
 25         g_servStat.dwCurrentState = SERVICE_STOPPED;
 26         break;
 27
 28     case SERVICE_CONTROL_STOP:
 29         g_isShutdown = TRUE;
 30         g_servStat.dwCurrentState = SERVICE_STOPPED;
 31         break;
 32
 33     default:
 34         break;
 35     }
 36
 37     SetServiceStatus(g_hServStat, &g_servStat);
 38
 39     return 0;
 40 }
 41
 42 // ServiceMain of Service Program
 43 void WINAPI TimerServiceMain(DWORD argc, LPWSTR *argv)
 44 {
 45     // Register service control handler
 46     SERVICE_STATUS_HANDLE g_hServStat = RegisterServiceCtrlHandlerEx(L"MyTimer", TimerServiceHandler, NULL);
 47
 48     // Set service initial status
 49     g_servStat.dwCheckPoint = 0;
 50     g_servStat.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE | SERVICE_ACCEPT_SHUTDOWN;
 51     g_servStat.dwCurrentState = SERVICE_START_PENDING;
 52     g_servStat.dwServiceSpecificExitCode = 0;
 53     g_servStat.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
 54     g_servStat.dwWaitHint = 2 * 1024;
 55     g_servStat.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
 56     if (!SetServiceStatus(g_hServStat, &g_servStat))
 57     {
 58         // report error in the way you like: disk file, windows event
 59         printf("failed to set service init status\n");
 60     }
 61
 62     // Your service specific logic here
 63     HANDLE hEvtSrc = RegisterEventSource(NULL, L"MyTimerService");
 64     if (hEvtSrc == NULL)
 65     {
 66         // use your own service info reporting mechanism
 67         printf("Cannot register the event source.");
 68         return;
 69     }
 70
 71     // Init done, report new service status to SCM
 72     g_servStat.dwCurrentState = SERVICE_RUNNING;
 73     SetServiceStatus(g_hServStat, &g_servStat);
 74
 75     wchar_t buf[MAX_PATH];
 76     LPCWSTR lpBuf = buf;
 77     UINT32 uiCounter = 0;
 78     while (!g_isShutdown)
 79     {
 80         if (!g_isPaused)
 81         {
 82             // see windows event documentation for how to use it in your own program
 83             swprintf_s(buf, MAX_PATH, L"Info from Gate Keeper Service: %u\n", uiCounter++);
 84             ReportEvent(hEvtSrc, EVENTLOG_SUCCESS, NULL, MSG_INFO_COMMAND, NULL, 1, NULL, &lpBuf, NULL);
 85         }
 86         Sleep(5 * 1000);
 87     }
 88    
 89     DeregisterEventSource(hEvtSrc);
 90
 91     g_servStat.dwCurrentState = SERVICE_STOPPED;
 92     SetServiceStatus(g_hServStat, &g_servStat);
 93
 94     return;
 95 }
 96
 97 // The Main Function of Service Program
 98 int main(int argc, char** argv)
 99 {
100     SERVICE_TABLE_ENTRY dispatchTable[] =
101     {
102         {L"MyTimer", TimerServiceMain},
103         {NULL, NULL}
104     };
105
106     // Register ServiceMain to Service Control Manager
107     if (!StartServiceCtrlDispatcher(dispatchTable))
108     {
109         // you can choose to write the info msg to disk file or windows event
110         printf("Failed to register service main to SCM.Please check system logs\n");
111         return -1;
112     }
113
114     return 0;
115 }
116

Code Snippet II - Service Configuration/Control Program

 1 #include <windows.h>
 2 #include <stdio.h>
 3
 4 int wmain(int argc, wchar_t** argv)
 5 {
 6     SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
 7     if (hSCM == NULL)
 8     {
 9         printf("failed to open sc manager, due to error:%d\n", GetLastError());
10     }
11
12     // Service Configuration Program - Install a new Windows Service
13     SC_HANDLE hServ = CreateService(hSCM, L"MyTimer",
14             L"Time Service",
15             SC_MANAGER_ALL_ACCESS,
16             SERVICE_WIN32_OWN_PROCESS,
17             SERVICE_DEMAND_START,
18             SERVICE_ERROR_NORMAL,
19             L"D:\\Dev\\Debug\MyTimer.exe",
20             NULL,
21             NULL,
22             NULL,
23             NULL,
24             NULL);
25     if (hServ == NULL)
26     {
27         printf("create windows service failed, due to error:%d\n", GetLastError());
28     }
29
30     // Service Configuration Program - Delete an existing Windows Service
31     //SC_HANDLE hServ = OpenService(hSCM, L"MyTimer", SERVICE_ALL_ACCESS);
32     //if (!DeleteService(hServ))
33     //{
34     //  printf("delete windows service failed, due to error:%d\n", GetLastError());
35     //}
36
37     // Service Control Program - Start/Pause/Resume/Stop the Windows Service
38     if (!StartService(hServ, argc, const_cast<LPCWSTR*>(argv)))
39     {
40         printf("failed to start service due to error:%d", GetLastError());
41     }
42
43     SERVICE_STATUS servStat;
44     QueryServiceStatus(hServ, &servStat);
45
46     if (!ControlService(hServ, SERVICE_CONTROL_PAUSE, &servStat))
47     {
48         printf("failed to control service due to:%d\n", GetLastError());
49     }
50
51     if (!ControlService(hServ, SERVICE_CONTROL_CONTINUE, &servStat))
52     {
53         printf("failed to control service due to:%d\n", GetLastError());
54     }
55
56     if (!ControlService(hServ, SERVICE_CONTROL_STOP, &servStat))
57     {
58         printf("failed to control service due to:%d\n", GetLastError());
59     }
60
61 }
62

Note:
1. You can see the main/servicemain/controlhandler of Service Program from the code comments.
2. You can also see the Service Configuration/Control Program from the second code snippet. These two programs are merged in one executable.
3. You should update service status each time a control command is processed in your control handler function, even if the status is not changed at all, as illustrated in line 37@code snippet I. This will let SCM aware that your service is in progress, not hang.
4. Windows Service applications run in a different window station than the interactive station of the logged-on user. The Windows service station is not interactive, so dialog boxes raised from within a Windows service application will not be seen and may cause your program to stop responding. Error/Debug messages should be logged in the Windows event log.

full source code package:
http://code4cs.googlecode.com/files/NotifyService4W.zip

No comments: