Source: creating a simple Win32 service in C ++
Download the source code of the ntservice example
Download the source code of the ntservcpl example
Download the source code of the ntservctrl example
Summary
This document describes how to use visual c ++ to create a Windows NT Service Program. Only one C ++ class is used to create the service. This class provides a simple interface between the service and the operating system. Using this class to implement your own services is very simple. You only need to rewrite the virtual functions in a few basic classes. There are three source code reference examples in this article:
- Ntservice is a simple Win32 service, which is created using the method described in this article;
- Ntservcpl is a control panel program used to control ntservice services;
- Ntservctrl is an example of an independent program that can be used to monitor a Win32 service;
Introduction
A service in Windows NT is actually a program. As soon as the computer operating system is started, the service can run. It does not require user login. A service program is a user-independent task, such as Directory replication, process monitoring, or services on the network for other machines, such as HTTP support.
It is not difficult to create a Windows NT Service Program. However, debugging a service program is not easy. For myself, I like to use VISUAL C ++ to write my own C ++ program. Most Win32 services are written in C, so I think it is interesting to use a C ++ class to implement basic functions of Win32 services. With this c ++ class, it is very easy to create a Win32 service using C ++. I have developed a C ++ base class for this purpose. using it as the starting point for compiling Win32 services should be fine.
In addition to writing service code, you must also perform some additional coding work to create a service program:
- Reporting warning and error information in system logs or application logs cannot be output to the screen because the user has not logged in.
- The control of service programs can be either through a separate application or through the control panel program. It depends on what communication mechanism your Service implements.
- Install and uninstall services from the system
Most service programs are installed using one installer and uninstalled using another one. In this article, I have built these functions into the service program itself to integrate them so that only one. EXE file can be distributed. You can run the service program directly from the command line, and install and uninstall or report the version information as needed. Ntservice supports the following command line parameters:
- -V: Name and version number of the report service;
- -I. Install the service;
- -U: uninstall the service;
By default, no command line parameters are passed when the system starts the service.
Create an application framework
I have always created an MFC-based application. When I first came into contact with the Win32 service program, I first used Visual C ++ Appwizard to create an SDI/mfc program. Remove the documents and view classes, icons, and other useless things, leaving only the framework. The result is removed at the end, including the main window (the service program cannot have this stuff), and nothing is left. It is very stupid. I had to go back to Appwizard and use a single source file to create a console program. This source file contains the main entry function. I named this file ntservapp. cpp. I use this CPP extension instead of C, because I only want to use C ++ to write programs, rather than directly using C. We will discuss the implementation of the file code later.
Because I want to use the C ++ class to build the service, I created the ntservice. h and ntservice. cpp files and used them to implement the cntservice base class. I also created myservice. h and myservice. cpp files to implement my own service class (cmyservice), which is derived from cntservice. We will see the code later.
When creating a new project, I like to see the running results as soon as possible, so the first thing I decided to do for the service program was to create a system application log record. With this logging mechanism, I can track when the service is started and stopped. I can also record any error information in the service. Creating this log record is much more complicated than I thought.
Create Logging
I think, since log files are part of the operating system, there must be an application programming interface (API) to support the creation of log records. So I started searching for the msdn CD until I found the reportevent function. If you are not familiar with this function, you may think that this function should know which log file to create a record and the text information you want to insert. Yes, this is all it has to do, but in order to simplify the internationalization of error information, this function has a message ID as a parameter and searches for messages in the message table you provide. So the question is nothing more than what messages you want to put into the log and how to add these messages to your application. Next we will do it step by step:
- Create a text file with the. Mc extension containing the Message description. I name it ntservmsg. MC. The file format is very special. For details, see the Platform SDK documentation;
- Run the message editor (mc.exe) on your source file. By default, it creates an output file named msg00001.bin. The compiler also creates a header file (in my example program, the header file is ntservmsg. H) and A. RC file (ntservmsg. Rc ). As long as you modify the. Mc file of the project, you must repeat this step. Therefore, it is convenient to add the tool to the tool menu of Visual C ++;
- Create a. RC file for the project and include the windows. h header file and the. RC file generated by the message compiler;
- Include the header file generated by the message compiler in the header file of the main project so that the module can access the symbolic message name;
Next, let's take a closer look at these files to understand what you need to create and what the message compiler will create for you. We don't need to study the entire message set. Just look at how one or two of them work. The following is the first part of the sample program message source file ntservmsg. MC:
MessageId=100SymbolicName=EVMSG_INSTALLEDLanguage=EnglishThe %1 service was installed..MessageId=SymbolicName=EVMSG_REMOVEDLanguage=EnglishThe %1 service was removed..MessageId=SymbolicName=EVMSG_NOTREMOVEDLanguage=EnglishThe %1 service could not be removed..
Each message has a message id. If this parameter is not set, the ID value is the value assigned previously. Each line also contains the symbol name, language identifier, and message text used in the code. A message can be terminated across multiple rows and with a separate line containing a full period.
The message compiler outputs a library file that is used as the application resource. In addition, it outputs two files to be included in the Code. The following is my. RC file:
// Ntservapp. RC # include <windows. h> // contains the message table resource script generated by the message Compiler (MC) # include "ntservmsg. RC "here's. RC file the message compiler generated: Language 0x9, 0x11 11 msg00001.bin
As you can see, these files do not contain much content!
The last file generated by the message compiler is the header file that you want to include in the Code. below is part of the header file:
[..........]//// MessageId: EVMSG_INSTALLED//// MessageText://// The %1 service was installed.//#define EVMSG_INSTALLED 0x00000064L//// MessageId: EVMSG_REMOVED//// MessageText://// The %1 service was removed.//#define EVMSG_REMOVED 0x00000065L[...........]
You may have noticed several messages that contain parameter substitution items (for example, % 1 ). Let's take a look at how to use message IDs and parameter substitution items in code when writing messages to a system log file. Take the installation code that records the successful installation information in the event log as an example. That is, the cntservice: isinstalled function section:
[....]LogEvent(EVENTLOG_INFORMATION_TYPE, EVMSG_INSTALLED, m_szServiceName);[....]
Logevent is another cntservice function that uses the event type (information, warning, or error), the ID of the event message, and an alternative string that forms a maximum of three parameters of the log message:
// This function makes an entry into the application event log.void CNTService::LogEvent(WORD wType, DWORD dwID,const char* pszS1,const char* pszS2,const char* pszS3){const char* ps[3];ps[0] = pszS1;ps[1] = pszS2;ps[2] = pszS3;int iStr = 0;for (int i = 0; i < 3; i++) {if (ps[i] != NULL) iStr++;}// Check to see if the event source has been registered,// and if not then register it now.if (!m_hEventSource) {m_hEventSource = ::RegisterEventSource(NULL, // local machinem_szServiceName); // source name}if (m_hEventSource) {::ReportEvent(m_hEventSource,wType,0,dwID,NULL, // sidiStr,0,ps,NULL);}}
As you can see, the main task is to be processed by the reportevent system function.
Now, we can record events in system logs by calling cntservice: logevent. Next, we will consider creating some code for the service itself.
Write Service Code
To construct a simple Win32 service, most of the information you need to know can be found in the Platform SDK. The sample code is written in C language and is easy to understand. My cntservice class is based on this code.
A service mainly includes three functions:
- Main function, which is the entry to the code. It is here that we parse any command line parameters and install, remove, and start the service.
- In this example, the entry function that provides the real service code is servicemain. You can call it anything. When the service is started for the first time, the address of the function is passed to the Service Manager.
- A function that processes command messages from the Service Manager. In the example, this function is called handler, which can be obtained at will.
Service callback function
Because both servicemain and handler functions are called by the system, they must follow the parameter passing and calling specifications of the operating system. That is to say, they cannot simply be a member function of a C ++ class. This makes encapsulation inconvenient, because we want to encapsulate the functions of the Win32 service in a C ++ class. To solve this problem, I create the servicemain and handler functions as static MEMBERS OF THE cntservice class. This allows me to create functions that can be called by the operating system. However, this does not completely solve the problem, because the system cannot transmit any form of user data to the called function, therefore, we cannot determine the call to servicemain or handler of a specific instance of the C ++ object. It uses a very simple but limited method to solve this problem. I create a static variable that contains the C ++ object pointer. This variable is initialized when this object is created for the first time. In this way, each service application has only one C ++ object. I don't think this restriction is too much. The declaration in the ntservice. h file is as follows:
Class cntservice {[...] // static data static cntservice * m_pthis; // nasty hack to get object PTR [...]};
The following method initializes the m_pthis pointer:
CNTService::CNTService(const char* szServiceName){ // Copy the address of the current object so we can access it from // the static member callback functions. // WARNING: This limits the application to only one CNTService object. m_pThis = this; [...]}
Cntservice class
When I create a C ++ object to encapsulate Windows functions, I try to create a member function for each encapsulated Windows API, I tried to make objects easier to use and reduce the number of lines of code required to implement a specific project. Therefore, my object is based on "What do I want this object to do ?" Instead of "What does Windows use these Apis ?"
The cntservice class contains some member functions used to parse command lines. To handle service installation and removal and event log records, you have to rewrite some virtual functions in the derived class to process the requests of the Service Control Manager. We will use the example service implementation in this article to study the usage of these functions.
To create a service that is as simple as possible, you only need to rewrite cntservice: Run, which is where you write code to implement specific service tasks. You also need to implement the main function. If the service needs to be initialized. To read data from the Registry, you also need to rewrite cntservice: oninit. If you want to send a command message to the service, you can use the system function controlservice in the service and rewrite cntservice: onusercontrol to process the request.
Use cntservice In the example application
Ntservice implements most of its functions in the cmyservice class. cmyservice is derived from cntservice. The myservice. h header file is as follows:
// myservice.h#include "ntservice.h"class CMyService : public CNTService{public: CMyService(); virtual BOOL OnInit(); virtual void Run(); virtual BOOL OnUserControl(DWORD dwOpcode); void SaveStatus(); // Control parameters int m_iStartParam; int m_iIncParam; // Current state int m_iState;};
As you can see, cmyservice overrides oninit, run, and onusercontrol of cntservice. It also has a function called savestatus, which is used to write data to the Registry and the member variables are used to save the current state. The example service incrementally processes an integer variable at a certain time. Both the start value and increment value exist in the Registry parameters. There is no intention to do so. Just for simple demonstration. Let's take a look at how this service is implemented.
Implement the main function
With cmyservice derived from cntservice, implementing the main function is very simple. See the ntservapp. cpp file:
Int main (INT argc, char * argv []) {// create a service object cmyservice myservice; // parse standard parameters (installation, uninstall, version, etc.) if (! Myservice. parsestandardargs (argc, argv) {// No standard parameters are found, so start the service, // cancel the following debugbreak code line comment, // enter the debugger after the service is started, // debugbreak (); myservice. startservice ();} // The Service has stopped returning myservice. m_status.dwwin32exitcode ;}
There is not much code here, but a lot of things happen after execution. Let's take a look at it step by step. First, create an instance of the myservice class. The constructor sets the initialization status and service name (myservice. cpp ):
CMyService::CMyService():CNTService("NT Service Demonstration"){ m_iStartParam = 0; m_iIncParam = 1; m_iState = m_iStartParam;}
Then, call parsestandardargs to check whether the command line contains a request for service installation (-I), uninstallation (-U), and reporting its version number (-V. Cntservice: parsestandardargs respectively calls cntservice: isinstalled, cntservice: Install and cntservice: uninstall to process these requests. If no recognizable command line parameter exists, the Service Control Manager tries to start the service and call startservice. This function is not returned until the service stops running. After debugging the code, you can comment out or delete the code lines for debugging.
Install and uninstall services
The installation of the service is handled by cntservice: Install. It registers the service with Win32 service manager and creates an entry in the Registry to support log messages during service running.
The uninstallation of a service is handled by cntservice: Uninstall. It only notifies the service manager that the service is no longer needed. Cntservice: uninstall does not delete the actual executable files of the service.
Write Service Code
Now let's write the specific code to implement the service. For the ntservice example, there are three functions to write. They involve initialization, running service details, and responding to control requests.
Initialization
The Registry provides a place for the service to store parameters:
HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services
I chose here to store my service configuration information. I created a Parameters key and stored the value I want to save here. Therefore, when the service is started, the oninit function is called. This function reads the initial settings from the registry.
BOOL CMyService::OnInit(){ // Read the registry parameters. // Try opening the registry key: // HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services//Parameters HKEY hkey; char szKey[1024]; strcpy(szKey, "SYSTEM//CurrentControlSet//Services//"); strcat(szKey, m_szServiceName); strcat(szKey, "//Parameters"); if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, szKey, 0, KEY_QUERY_VALUE, &hkey) == ERROR_SUCCESS) { // Yes we are installed. DWORD dwType = 0; DWORD dwSize = sizeof(m_iStartParam); RegQueryValueEx(hkey, "Start", NULL, &dwType, (BYTE*)&m_iStartParam, &dwSize); dwSize = sizeof(m_iIncParam); RegQueryValueEx(hkey, "Inc", NULL, &dwType, (BYTE*)&m_iIncParam, &dwSize); RegCloseKey(hkey); } // Set the initial state. m_iState = m_iStartParam; return TRUE;}
Now that we have service parameters, we can run the service.
Running Service
When the run function is called, the main code of the service is executed. The example in this article is simple:
void CMyService::Run(){ while (m_bIsRunning) { // Sleep for a while. DebugMsg("My service is sleeping (%lu)...", m_iState); Sleep(1000); // Update the current state. m_iState += m_iIncParam; }}
Note that this function will not exit as long as the service is not terminated. When a request for service termination is sent, the cntservice: m_bisrunning flag is set to false. If you want to clear the service when it is terminated, you can rewrite onstop and/or onshutdown.
Response control request
You can use any suitable method to communicate with services-named pipelines, ideas, notes, etc.-for some simple requests, it is easy to use the system function controlservice. Cntservice provides a processor dedicated to sending non-standard messages (that is, messages sent by users) through the controlservice function ). This example uses a single message to save the status of the current service in the registry so that other applications can see it. I do not recommend using this method to monitor the service, because it is not the best method, it is easier to implement encoding. The user messages that controlservice can process must be in the range of 128 to 255. I have defined a constant service_control_user, 128 as the base value. A user message within the specified range is sent to cntservice: onusercontrol. In the example service, the details for processing the message are as follows:
BOOL CMyService::OnUserControl(DWORD dwOpcode){ switch (dwOpcode) { case SERVICE_CONTROL_USER + 0: // Save the current status in the registry. SaveStatus(); return TRUE; default: break; } return FALSE; // say not handled}
Savestatus is a local function used to store service statuses in the registry.
Debug Win32 service
The main function contains a call to debugbreak. when the service is started for the first time, it activates the system debugger. You can monitor service debugging information from the debugger command window. You can use cntservice: debugmsg in the service to report events of interest during debugging.
To debug the service code, you must install the system debugger (windbg) as required in the Platform SDK documentation ). You can also use the self-built debugger of Visual Studio to debug the Win32 service.
It is very important that when it is controlled by the Service Manager, you cannot terminate the service or perform one-step operations, because the service manager will let the service request time out and terminate the service thread. Therefore, you can only let the service spit out messages, track their processes, and view them in the debugger window.
After the service is started (for example, from the "service" in the control panel), the debugger starts after the service thread is suspended. You need to click "go" or press F5 to continue running. Then observe the service running process in the debugger.
The following is an example of the debug output for starting and terminating the service:
Module Load: WinDebug/NTService.exe (symbol loading deferred)Thread Create: Process=0, Thread=0Module Load: C:/NT351/system32/NTDLL.DLL (symbol loading deferred)Module Load: C:/NT351/system32/KERNEL32.DLL (symbol loading deferred)Module Load: C:/NT351/system32/ADVAPI32.DLL (symbol loading deferred)Module Load: C:/NT351/system32/RPCRT4.DLL (symbol loading deferred)Thread Create: Process=0, Thread=1*** WARNING: symbols checksum is wrong 0x0005830f 0x0005224f for C:/NT351/symbols/dll/NTDLL.DBGModule Load: C:/NT351/symbols/dll/NTDLL.DBG (symbols loaded)Thread Terminate: Process=0, Thread=1, Exit Code=0Hard coded breakpoint hitHard coded breakpoint hit[](130): CNTService::CNTService()Module Load: C:/NT351/SYSTEM32/RPCLTC1.DLL (symbol loading deferred)[NT Service Demonstration](130): Calling StartServiceCtrlDispatcher()Thread Create: Process=0, Thread=2[NT Service Demonstration](174): Entering CNTService::ServiceMain()[NT Service Demonstration](174): Entering CNTService::Initialize()[NT Service Demonstration](174): CNTService::SetStatus(3026680, 2)[NT Service Demonstration](174): Sleeping...[NT Service Demonstration](174): CNTService::SetStatus(3026680, 4)[NT Service Demonstration](174): Entering CNTService::Run()[NT Service Demonstration](174): Sleeping...[NT Service Demonstration](174): Sleeping...[NT Service Demonstration](174): Sleeping...[NT Service Demonstration](130): CNTService::Handler(1)[NT Service Demonstration](130): Entering CNTService::Stop()[NT Service Demonstration](130): CNTService::SetStatus(3026680, 3)[NT Service Demonstration](130): Leaving CNTService::Stop()[NT Service Demonstration](130): Updating status (3026680, 3)[NT Service Demonstration](174): Leaving CNTService::Run()[NT Service Demonstration](174): Leaving CNTService::Initialize()[NT Service Demonstration](174): Leaving CNTService::ServiceMain()[NT Service Demonstration](174): CNTService::SetStatus(3026680, 1)Thread Terminate: Process=0, Thread=2, Exit Code=0[NT Service Demonstration](130): Returned from StartServiceCtrlDispatcher()Module Unload: WinDebug/NTService.exeModule Unload: C:/NT351/system32/NTDLL.DLLModule Unload: C:/NT351/system32/KERNEL32.DLLModule Unload: C:/NT351/system32/ADVAPI32.DLLModule Unload: C:/NT351/system32/RPCRT4.DLLModule Unload: C:/NT351/SYSTEM32/RPCLTC1.DLLThread Terminate: Process=0, Thread=0, Exit Code=0Process Terminate: Process=0, Exit Code=0>
Summary
It may not be ideal to use C ++ to create a Win32 service, but using a single class to derive your own service is indeed convenient for your service development work.