Windows services are designed for applications that need to run in the background and to implement tasks that do not have user interaction. To learn the basics of this console application, C (not C + +) is the best choice. This article establishes and implements a simple service program that queries the amount of physical memory available in the system and then writes the results to a text file. Finally, you can write your own Windows services with the knowledge you have learned.
When I wrote the first NT service, I looked for an example on MSDN. There I found an article Nigel Thompson wrote: "Creating a simple Win32 Service in C + +", this article comes with a C + + example. Although this article is a good explanation for the development of the service, I still feel the lack of important information I need. I want to understand what framework to invoke, what functions to call, and when to call, but C + + doesn't make me much easier in this regard. The Object-oriented method is convenient, but it is not good for learning the basic knowledge of the service program because it encapsulates the low-level Win32 function call with class. That's why I think C is better suited to the services of writing a primary service program or implementing a simple background task. After you have a thorough understanding of the service program, write in C + + to be able to excel. When I left my old job and had to transfer my knowledge to another person, using the example I wrote in C was a very easy explanation for the NT service.
A service is a console program that runs in the background and implements tasks that do not require user interaction. The Windows NT/2000/XP operating system provides specialized support for service programs. People can use the Service Control Panel to configure the installed service program, which is the Windows 2000/xp Control Panel | "Services" in Administrative tools (or at the start | Run dialog box, enter the services.msc/s--translator note). You can configure the service to start automatically when the operating system starts, so that you do not have to manually start the service each time you reboot the system.
This article first explains how to create a service that periodically queries the available physical memory and writes the results to a text file. It then guides you through the process of building, installing, and implementing the service.
First step: main function and global definition
First, include the header file that you want. Example to invoke the Win32 function (windows.h) and Disk File writes (stdio.h):
Copy Code code as follows:
#include <windows.h>
#include <stdio.h>
Next, define two constants:
Copy Code code as follows:
#define SLEEP_TIME 5000
#define LOGFILE "C://myservices//memstatus.txt"
SLEEP_TIME Specifies the millisecond interval between the available memory for two consecutive queries. Use this constant when you write a service work cycle in the second step.
LOGFILE defines the path to the log file, you will use the WriteToLog function to output the results of the memory query to the file, and the WriteToLog function is defined as follows:
Copy Code code as follows:
int WriteToLog (char* str)
{
file* log;
Log = fopen (LOGFILE, "A +");
if (log = = NULL)
return-1;
fprintf (log, "%s/n", str);
fclose (log);
return 0;
}
Declare several global variables to share their values among multiple functions in a program. Also, do a forward definition of a function:
Copy Code code as follows:
Service_status ServiceStatus;
Service_status_handle Hstatus;
void ServiceMain (int argc, char** argv);
void Controlhandler (DWORD request);
int Initservice ();
Now that the preparation is ready, you can start coding. A subset of the Service program console programs. So, you can start by defining a main function, which is the entry point of the program. For a service program, main's code is surprisingly short because it creates only the dispatch table and starts the control dispatcher.
Copy Code code as follows:
void Main ()
{
Service_table_entry servicetable[2];
Servicetable[0].lpservicename = "Memorystatus";
Servicetable[0].lpserviceproc = (lpservice_main_function) ServiceMain;
Servicetable[1].lpservicename = NULL;
Servicetable[1].lpserviceproc = NULL;
Start the control dispatcher thread for the service
StartServiceCtrlDispatcher (servicetable);
}
A program may contain several services. Each service must be listed in a dedicated dispatch table (this program defines an array of servicetable structures). Every item in this table is in the service_table_entry structure. It has two domains:
Lpservicename: Pointer to a string representing a service name; when more than one service is defined, the domain must be specified;
LPSERVICEPROC: Pointer to service main function (service entry point);
The last item in the dispatch table must be a NULL pointer to the service name and service main function field, and the text example program hosts only one service, so the definition of the service name is optional.
The Service Control Manager (Scm:services Controls manager) is a process that manages all the services of the system. When the SCM starts a service, it waits for the main thread of a process to call the StartServiceCtrlDispatcher function. Pass the dispatch table to StartServiceCtrlDispatcher. This converts the main thread of the calling process into a control dispatcher. The dispatcher launches a new thread that runs the ServiceMain function for each service in the dispatch table (in this case, only one service) dispatcher also monitors the execution of all services in the program. The dispatcher then passes the control request from the SCM to the service.
Note: If the StartServiceCtrlDispatcher function is not invoked for 30 seconds, an error is given, and in order to avoid this, we must initialize the service Dispatch table in the ServiceMain function (see the example in this article) or in a separate thread of a non-primary function. The services described in this article do not need to guard against such a situation.
After all the services in the dispatch table have been executed (for example, when the user stops them through the Services Control Panel program), or when an error occurs. The StartServiceCtrlDispatcher call returns. The main process then terminates.
Step Two: ServiceMain function
Listing 1 shows the code for the ServiceMain. This function is the entry point for the service. It runs in a separate thread, which is created by the control dispatcher. ServiceMain should register the control processor as early as possible for the service. This is done by calling the Registerservicectrlhadler function. You will pass two arguments to this function: the service name and the pointer to the controlhandlerfunction.
It instructs the control dispatcher to call the Controlhandler function to handle the SCM control request. After registering the control processor, obtain the status handle (Hstatus). By calling the SetServiceStatus function, the status of the service is reported to the SCM using Hstatus.
Listing 1 shows how to specify the service characteristics and its current state to initialize the servicestatus structure, and each domain of the SERVICESTATUS structure has its purpose:
Dwservicetype: Indicates the service type and creates the Win32 service. assigning value service_win32;
Dwcurrentstate: Specifies the current state of the service. Because the initialization of the service is not done here, the state here is service_start_pending;
Dwcontrolsaccepted: This domain notifies the SCM service which domain to accept. The example in this article is to allow STOP and SHUTDOWN requests. Processing control requests will be discussed in the third step;
Dwwin32exitcode and Dwservicespecificexitcode: These two domains are useful when you terminate the service and report out details. The service does not exit when it is initialized, so their value is 0;
Dwcheckpoint and Dwwaithint: These two fields represent more than 30 seconds to initialize a service process. This article example service initialization process is very short, so both fields have a value of 0.
Call the SetServiceStatus function to report the state of the service to the SCM. To provide hstatus handles and servicestatus structures. Note ServiceStatus A global variable, so you can use it across multiple functions. In the ServiceMain function, you assign values to several fields of the structure, and they remain unchanged throughout the service's operation, such as: Dwservicetype.
After reporting the service state, you can call the Initservice function to complete the initialization. This function simply adds a descriptive string to the log file. As shown in the following code:
Copy Code code as follows:
Service initialization
int Initservice ()
{
int result;
result = WriteToLog ("monitoring started.");
return (result);
}
In ServiceMain, check the return value of the Initservice function. If there is an error in initialization (because it is possible to write a log file), the service state is set to terminate and exit ServiceMain:
Copy Code code as follows:
Error = Initservice ();
if (Error)
{
Initialization failed, terminating service
Servicestatus.dwcurrentstate = service_stopped;
Servicestatus.dwwin32exitcode =-1;
SetServiceStatus (Hstatus, &servicestatus);
Exit ServiceMain
Return
}
If the initialization succeeds, the status is reported to the SCM:
Copy Code code as follows:
Report running status to SCM
Servicestatus.dwcurrentstate = service_running;
SetServiceStatus (Hstatus, &servicestatus);
Next, start the work cycle. Queries one available physical memory every five seconds and writes the results to the log file.
As shown in Listing 1, the loop continues until the service's state is service_running or the log file write error occurs. The State may be modified when the Controlhandler function responds to SCM control requests.
Step three: Process Control requests
In the second step, you register the control processor function with the ServiceMain function. The control processor is very similar to the window callback function that handles various Windows messages. It examines what requests the SCM has sent and takes action accordingly.
Each time you call the SetServiceStatus function, you must specify that the service receives the STOP and SHUTDOWN requests. Listing 2 demonstrates how to handle them in the Controlhandler function.
The stop request is sent when the SCM terminates the service. For example, if the user terminates the service manually in the Services control Panel. The SHUTDOWN request is a request sent by the SCM to all running services when the machine is shut down. The two cases are handled the same way:
Write log file, monitor stop;
Report service_stopped status to SCM;
Because the SERVICESTATUS structure is global for the entire program, the work cycle in servicestatus stops after the current state changes or the service terminates. Other control requests such as: PAUSE and CONTINUE are not dealt with in the examples in this article.
The control processor function must report the service state, even though the SCM status remains the same each time the control request is sent. Therefore, the SetServiceStatus is invoked regardless of what request is being answered.
Figure I shows the Service Control Panel for the Memorystatus service
Fourth Step: Install and configure services
The program is prepared, and will be compiled into an EXE file. This example creates a file called MemoryStatus.exe and copies it to the C:/myservices folder. In order to install this service on the machine, it is necessary to use SC. EXE executable file, which is a tool included with the Win32 Platform SDK. This tool is also available in the Visaul Studio. NET 2003 IDE Environment, located in: C:/Program files/microsoft Visual Studio. NET 2003/common7/tools/bin/ Winnt). Use this utility to install and remove services. Other control actions will be performed through the Service Control Panel.
Here's how to install the Memorystatus service using the command line:
Copy Code code as follows:
SC Create memorystatus binpath= c:/myservices/memorystatus.exe
Issue this creation command. Specifies the path of the service name and binary file (note the space between the binpath= and the path). Once the installation is successful, you can use the Service Control Panel to control the service (see figure I). Use the Control Panel toolbar to start and terminate this service.
Figure II Memorystatus Service Properties window
The Memorystatus startup type is manual, which means that the service is started as needed. Right-click the service and select the Properties menu item in the context menu to display the Properties window for the service. You can modify the startup type and other settings here. You can also start/stop the service from the "General" tab.
The following methods are used to remove a service from the system:
Copy Code code as follows:
Specify the delete option and the service name. This service will be marked for deletion, and the service will be completely removed the next time the West Pass is restarted.
Step Fifth: Testing services
Start the Memorystatus service from the Service Control Panel. If there is no error in initialization, it indicates a successful start. Stop the service in a few minutes. Check the service output of the Memstatus.txt file in the C:/myservices folder. The output on my machine is this:
Copy Code code as follows:
Monitoring started.
273469440
273379328
273133568
273084416
Monitoring stopped.
To test the behavior of the Memorystatus service in the event of an error, you can set the Memstatus.txt file to read-only. This way, the service should not start.
Remove the read-only property, start the service, and set the file to read-only. The service will stop executing because the log file write failed at this time. If you update the contents of the Service Control Panel, you will find that the service status is stopped.
Develop a larger and better service program
Understand the basic concepts of WIN32 services so that you can better design packaging classes in C + +. The wrapper class hides the call to the underlying WIN32 function and provides a comfortable universal interface. Modify the Memorystatus program code to create a service that meets your needs! To achieve the more complex tasks illustrated by this example, you can create multithreaded services that divide jobs into several worker threads and monitor their execution from the ServiceMain function.