Windows服务程序就是运行在任务管理器中‘服务’选项卡下的程序,可以通过服务控制程序(SCM)进行启动、停止、暂停等操作;
SCM程序的打开方式为:单击“开始搜索”框,键入 services.msc,然后按 Enter。截图如下:
SCM(service control manager),它是一个系统服务(就是我们上面的截图),我们开发的程序的一生都是通过它控制的,如启动、停止等。
一个服务程序主要包含两个系统回调函数和一个系统调用函数,分别是:
1、服务主函数SvcMain(),回调函数,该函数完成我们需要服务程序进行的工作;
2、服务控制函数SvcControl(),回调函数,该函数接收SCM发来的控制请求,完成对服务程序的控制,如启动、停止;
3、与SCM通信系统调用StartServiceCtrlDispatcher(),该函数启动Dispatcher线程,该线程通过SvcControl()调度SCM发送来的请求,当SCM发送来的是start请求时,Dispatcher新启动一个线程,该线程的入口函数是SvcMain();当SCM发送来的是其控制命令如stop、pause等,Dispatcher调用SvcControl()完成对服务程序的控制。
具体的控制流程如图2所示:
图2
如图2所示,
对一个服务程序的完整的使用流程是:
1、编码生成服务exe可执行程序;
2、通过命令行工具sc 创建服务;
3、通过命令行工作sc、或者图形化工具SCM(图1)启动、停止服务。
命令行工具‘sc’的使用方法如下:
sc create svrName binpath = d://test.exe //创建服务(注:‘=’后面有一个空格)
sc start svrName //启动服务
sc query svrName //查询服务状态
sc stop svrName //停止服务
sc delete svrName //删除服务
注:有时候sc create创建的服务不可以正常启动,使用"*.exe -service", 命令代替
MSDN中关于服务程序的介绍如下:
SCM(Service Control Manager)
The service control manager (SCM) maintains a database of installed services and driver services, and provides a unified and secure means of controlling them. The database includes information on how each service or driver service should be started. It also enables system administrators to customize security requirements for each service and thereby control access to the service.
Service Functions:
1.ServiceMain()
VOID WINAPI ServiceMain(
_In_ DWORD dwArgc,
_In_ LPTSTR *lpszArgv
);
ServiceMain是一个回调函数,是被service dispatch调用的函数。
Then the service control manager sends a start request to the service control dispatcher for this service process. The service control dispatcher creates a new thread to execute the ServiceMain function of the service being started.
2.Handler()
void LphandlerFunction(
DWORD dwControl
)
Handler也是一个回调函数,也是被service dispatch 调用。当service dispatch收到SCM发来的服务控制请求,如start、stop等时,就会调用Handler去控制该服务的状态。
一个完整的服务程序源码如下,(来自MSDN,进行过少量改动和注释添加)
#include
#include
#include
//#include "sample.h"
#pragma comment(lib, "advapi32.lib")
#define SVCNAME TEXT("SvcName")
#define SVC_ERROR ((DWORD)0xC0020001L)
SERVICE_STATUS gSvcStatus;
SERVICE_STATUS_HANDLE gSvcStatusHandle;
HANDLE ghSvcStopEvent = NULL;
#define LOGFILE "C:\\MyServices\\SvcName.txt"
VOID SvcInstall(void);
VOID WINAPI SvcCtrlHandler(DWORD);
VOID WINAPI SvcMain(DWORD, LPTSTR *);
VOID ReportSvcStatus(DWORD, DWORD, DWORD);
VOID SvcInit(DWORD, LPTSTR *);
VOID SvcReportEvent(LPTSTR);
int WriteToLog(char*);
//
// Purpose:
// Entry point for the process
//
// Note:
// The main function of a service program calls the StartServiceCtrlDispatcher function to connect to the
// service control manager (SCM) and start the control dispatcher thread. The dispatcher thread loops,
// waiting for incoming control requests for the services specified in the dispatch table. This thread
// returns when there is an error or when all of the services in the process have terminated. When all
// services in the process have terminated, the SCM sends a control request to the dispatcher thread
// telling it to exit. This thread then returns from the StartServiceCtrlDispatcher call and the process
// can terminate.
//
// Parameters:
// None
//
// Return value:
// None
//
void __cdecl _tmain(int argc, TCHAR *argv[])
{
// If command-line parameter is "install", install the service.
// Otherwise, the service is probably being started by the SCM.
if (lstrcmpi(argv[1], TEXT("install")) == 0)
{
SvcInstall();
return;
}
// TO_DO: Add any additional services for the process to this table.
SERVICE_TABLE_ENTRY DispatchTable[] =
{
{ SVCNAME, (LPSERVICE_MAIN_FUNCTION)SvcMain },
{ NULL, NULL }
};
DWORD dw_RetCode = -1;
// This call returns when the service has stopped.
// The process should simply terminate when the call returns.
if (!StartServiceCtrlDispatcher(DispatchTable))
{
dw_RetCode = GetLastError();
MessageBoxEx(NULL, _T("start SvrName service fail!"), NULL, 0, 0);
SvcReportEvent(TEXT("StartServiceCtrlDispatcher"));
}
}
//
// Purpose:
// Installs a service in the SCM database
//
// Parameters:
// None
//
// Return value:
// None
//
VOID SvcInstall()
{
SC_HANDLE schSCManager;
SC_HANDLE schService;
TCHAR szPath[MAX_PATH];
if (!GetModuleFileName(NULL, szPath, MAX_PATH))
{
printf("Cannot install service (%d)\n", GetLastError());
return;
}
// Get a handle to the SCM database.
schSCManager = OpenSCManager(
NULL, // local computer
NULL, // ServicesActive database
SC_MANAGER_ALL_ACCESS); // full access rights
if (NULL == schSCManager)
{
printf("OpenSCManager failed (%d)\n", GetLastError());
return;
}
// Create the service
schService = CreateService(
schSCManager, // SCM database
SVCNAME, // name of service
SVCNAME, // service name to display
SERVICE_ALL_ACCESS, // desired access
SERVICE_WIN32_OWN_PROCESS, // service type
SERVICE_DEMAND_START, // start type
SERVICE_ERROR_NORMAL, // error control type
szPath, // path to service's binary
NULL, // no load ordering group
NULL, // no tag identifier
NULL, // no dependencies
NULL, // LocalSystem account
NULL); // no password
if (schService == NULL)
{
printf("CreateService failed (%d)\n", GetLastError());
CloseServiceHandle(schSCManager);
return;
}
else printf("Service installed successfully\n");
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
}
//
// Purpose:
// Entry point for the service
//
// Parameters:
// dwArgc - Number of arguments in the lpszArgv array
// lpszArgv - Array of strings. The first string is the name of
// the service and subsequent strings are passed by the process
// that called the StartService function to start the service.
//
// Return value:
// None.
//
VOID WINAPI SvcMain(DWORD dwArgc, LPTSTR *lpszArgv)
{
// Register the handler function for the service
gSvcStatusHandle = RegisterServiceCtrlHandler(
SVCNAME,
SvcCtrlHandler);
if (!gSvcStatusHandle)
{
SvcReportEvent(TEXT("RegisterServiceCtrlHandler"));
return;
}
// These SERVICE_STATUS members remain as set here
gSvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
gSvcStatus.dwServiceSpecificExitCode = 0;
gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_PAUSE_CONTINUE;
WriteToLog("in SvrMain(), start Service success!");
// Report initial status to the SCM
ReportSvcStatus(SERVICE_START_PENDING, NO_ERROR, 3000);
// Perform service-specific initialization and work.
SvcInit(dwArgc, lpszArgv);
}
//
// Purpose:
// The service code
//
// Parameters:
// dwArgc - Number of arguments in the lpszArgv array
// lpszArgv - Array of strings. The first string is the name of
// the service and subsequent strings are passed by the process
// that called the StartService function to start the service.
//
// Return value:
// None
//
VOID SvcInit(DWORD dwArgc, LPTSTR *lpszArgv)
{
// TO_DO: Declare and set any required variables.
// Be sure to periodically call ReportSvcStatus() with
// SERVICE_START_PENDING. If initialization fails, call
// ReportSvcStatus with SERVICE_STOPPED.
// Create an event. The control handler function, SvcCtrlHandler,
// signals this event when it receives the stop control code.
ghSvcStopEvent = CreateEvent(
NULL, // default security attributes
TRUE, // manual reset event
FALSE, // not signaled
NULL); // no name
if (ghSvcStopEvent == NULL)
{
ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
return;
}
// Report running status when initialization is complete.
ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0);
// TO_DO: Perform work until service stops.
while (1)
{
// Check whether to stop the service.
WaitForSingleObject(ghSvcStopEvent, INFINITE);
ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
return;
}
}
//
// Purpose:
// Sets the current service status and reports it to the SCM.
//
// Parameters:
// dwCurrentState - The current state (see SERVICE_STATUS)
// dwWin32ExitCode - The system error code
// dwWaitHint - Estimated time for pending operation,
// in milliseconds
//
// Return value:
// None
//
// NOTE:
// SCM中对服务进行的可控操作是由SERVICE_STATUS.dwControlsAccepted决定的,所以当服务程序状态发生变化后需要下
// 需要及时向SCM汇报服务当前可接受的操作
//
//
VOID ReportSvcStatus(DWORD dwCurrentState,
DWORD dwWin32ExitCode,
DWORD dwWaitHint)
{
static DWORD dwCheckPoint = 1;
// Fill in the SERVICE_STATUS structure.
gSvcStatus.dwCurrentState = dwCurrentState;
gSvcStatus.dwWin32ExitCode = dwWin32ExitCode;
gSvcStatus.dwWaitHint = dwWaitHint;
if (dwCurrentState == SERVICE_START_PENDING)
{
gSvcStatus.dwControlsAccepted = 0;
}
else
{
gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
}
if ((dwCurrentState == SERVICE_RUNNING) ||
(dwCurrentState == SERVICE_STOPPED))
{
gSvcStatus.dwCheckPoint = 0;
}
else
{
gSvcStatus.dwCheckPoint = dwCheckPoint++;
}
// Report the status of the service to the SCM.
SetServiceStatus(gSvcStatusHandle, &gSvcStatus);
}
//
// Purpose:
// Called by SCM whenever a control code is sent to the service
// using the ControlService function.
//
// Parameters:
// dwCtrl - control code
//
// Return value:
// None
//
VOID WINAPI SvcCtrlHandler(DWORD dwCtrl)
{
// Handle the requested control code.
switch (dwCtrl)
{
case SERVICE_CONTROL_STOP:
ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 0);
// Signal the service to stop.
SetEvent(ghSvcStopEvent);
ReportSvcStatus(gSvcStatus.dwCurrentState, NO_ERROR, 0);
return;
case SERVICE_CONTROL_PAUSE:
break;
case SERVICE_CONTROL_CONTINUE:
break;
case SERVICE_CONTROL_INTERROGATE:
break;
default:
break;
}
}
//
// Purpose:
// Logs messages to the event log
//
// Parameters:
// szFunction - name of function that failed
//
// Return value:
// None
//
// Remarks:
// The service must have an entry in the Application event log.
VOID SvcReportEvent(LPTSTR szFunction)
{
HANDLE hEventSource;
LPCTSTR lpszStrings[2];
TCHAR Buffer[80];
hEventSource = RegisterEventSource(NULL, SVCNAME);
if (NULL != hEventSource)
{
StringCchPrintf(Buffer, 80, TEXT("%s failed with %d"), szFunction, GetLastError());
lpszStrings[0] = SVCNAME;
lpszStrings[1] = Buffer;
ReportEvent(hEventSource, // event log handle
EVENTLOG_ERROR_TYPE, // event type
0, // event category
SVC_ERROR, // event identifier
NULL, // no security identifier
2, // size of lpszStrings array
0, // no binary data
lpszStrings, // array of strings
NULL); // no binary data
DeregisterEventSource(hEventSource);
}
}
/**
* 功能:
* 将日志写入日志文件
*/
int WriteToLog(char* str)
{
//获取系统时间
SYSTEMTIME sysTime;
GetLocalTime(&sysTime);
char logStr[100] = { 0 };
sprintf_s(logStr, "%04d-%02d-%02d %02d:%02d:%02d : %s", sysTime.wYear, sysTime.wMonth, sysTime.wDay,
sysTime.wHour, sysTime.wMinute, sysTime.wSecond, str);
FILE* logfile;
fopen_s(&logfile, LOGFILE, "a+");
if (logfile == NULL)
{
return -1;
}
fprintf(logfile, "%s\n", logStr);
fclose(logfile);
return 0;
}
需要特别说明得一个函数是StartServiceCtrlDispatcher,该函数调用后不会立即返回,直到SERVICE_TABLE_ENTRY表中的所有服务都处于结束状态才会返回,还有能通过双击exe程序直接启动服务程序,如果这样做StartServiceCtrlDispatcher()函数会返回1063错误,表示不能与SCM进行连接,所以服务程序的启动必须通过SCM来控制。msdn对该函数的描述如下:
// The main function of a service program calls the StartServiceCtrlDispatcher function to connect to the
// service control manager (SCM) and start the control dispatcher thread. The dispatcher thread loops,
// waiting for incoming control requests for the services specified in the dispatch table. This thread
// returns when there is an error or when all of the services in the process have terminated. When all
// services in the process have terminated, the SCM sends a control request to the dispatcher thread
// telling it to exit. This thread then returns from the StartServiceCtrlDispatcher call and the process
// can terminate.