注意:windows上的服务通常是控制台应用程序,即程序一般是没有界面的。
编写服务程序主要关注四个点:
-
状态反馈
-
服务入口点(Service Entry Point)
-
服务主函数(Service ServiceMain Function)
-
服务控制函数(Service Control Handler Function)
按照我的理解,状态反馈即将程序当前的状态反馈给SCM。服务入口点即为服务从哪里开始。服务主函数即为服务启动后最先执行的函数。服务控制函数即为服务运行中接收各种命令的处理函数,比如响应停止,关闭等。
状态反馈
状态反馈是通过SetServiceStatus
函数完成的,需要将程序连接到SCM后使用。连接SCM的操作通过StartServiceCtrlDispatcher
函数完成。
当程序连接到SCM之后,需要使用RegisterServiceCtrlHandlerEx
函数注册服务控制函数,同时该函数会返回当前程序的状态信息句柄。
SetServiceStatus
函数需要输入两个参数,第一个就是当前程序的状态信息句柄(类型为SERVICE_STATUS_HANDLE
),第二个就是程序最近的状态信息指针(类型为LPSERVICE_STATUS
),注意这两个参数的区别。
服务入口点
当主线程启动后(main函数),应该在主线程中马上调用StartServiceCtrlDispatcher
函数,这个函数会将主线程连接到SCM。注意相关初始化工作最好在服务主函数中进行。代码如下:
#include <Windows.h>
#define SERVICENAME L"Your Service Name"
void ServiceMain(int argc, char** argv)
{
}
int main()
{
WCHAR ServiceName[] = SERVICENAME;
SERVICE_TABLE_ENTRY DispatchTable[2];
// 服务名字
DispatchTable[0].lpServiceName = ServiceName;
// 服务主函数
DispatchTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;
// 注意需要加上下面两行,因为StartServiceCtrlDispatcher规定最后一个元素必须为NULL
DispatchTable[1].lpServiceName = NULL;
DispatchTable[1].lpServiceProc = NULL;
// 服务入口点
StartServiceCtrlDispatcher(DispatchTable);
return 0;
}
虽然ServiceMain
作为服务主函数名不是唯一的,但是微软建议不要更改服务主函数名,即保持为ServiceMain
。目前代码可以编译成功,但是还需要继续添加相关控制。
服务主函数
进入到服务主函数后,首先初始化全局变量,然后调用RegisterServiceCtrlHandler
函数来注册服务控制函数,并获取当前程序的状态信息句柄,同时将程序的状态反馈给SCM。最后执行其他初始化,注意在状态未更改为SERVICE_RUNNING
之前,初始化过程不要消耗太长时间,最好控制在1秒之内完成。代码如下:
#include <Windows.h>
#define SERVICENAME L"Your Service Name"
// 当前程序的状态信息句柄
SERVICE_STATUS_HANDLE gSvcStatusHandle;
// 程序最近的状态信息
SERVICE_STATUS gSvcStatus;
// 服务控制函数
void WINAPI ServiceControlHandler(DWORD request)
{
}
void ServiceMain(int argc, char** argv)
{
// 下面填充当前服务的基本信息
// 服务类型
gSvcStatus.dwServiceType = SERVICE_WIN32;
// 服务状态:正在启动中
gSvcStatus.dwCurrentState = SERVICE_START_PENDING;
// 当前服务接收的控制有哪些
gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP;
// 启动或停止时的服务错误码
gSvcStatus.dwWin32ExitCode = 0;
// 其它详细信息请查看文档: https://docs.microsoft.com/en-us/windows/win32/api/winsvc/ns-winsvc-service_status
gSvcStatus.dwServiceSpecificExitCode = 0;
gSvcStatus.dwCheckPoint = 0;
gSvcStatus.dwWaitHint = 0;
// 返回状态信息句柄
gSvcStatusHandle = RegisterServiceCtrlHandler(SERVICENAME, ServiceControlHandler);
if (gSvcStatusHandle == 0)
{
return;
}
// 将状态更改为running,即代表程序可以接收SCM发送的控制信息
gSvcStatus.dwCurrentState = SERVICE_RUNNING;
// 反馈状态信息给SCM
SetServiceStatus(gSvcStatusHandle, &gSvcStatus);
return;
}
int main()
{
WCHAR ServiceName[] = SERVICENAME;
SERVICE_TABLE_ENTRY DispatchTable[2];
// 服务名字
DispatchTable[0].lpServiceName = ServiceName;
// 服务主函数
DispatchTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;
// 注意需要加上下面两行,因为StartServiceCtrlDispatcher规定最后一个元素必须为NULL
DispatchTable[1].lpServiceName = NULL;
DispatchTable[1].lpServiceProc = NULL;
// 服务入口点
StartServiceCtrlDispatcher(DispatchTable);
return 0;
}
服务控制函数
在服务控制函数中编写针对SCM发送的各种请求进行处理即可。
// 服务控制函数
void WINAPI ServiceControlHandler(DWORD request)
{
switch (request)
{
// 服务停止
case SERVICE_CONTROL_STOP:
gSvcStatus.dwWin32ExitCode = 0;
gSvcStatus.dwCurrentState = SERVICE_STOPPED;
break;
// 系统关机
case SERVICE_CONTROL_SHUTDOWN:
gSvcStatus.dwWin32ExitCode = 0;
gSvcStatus.dwCurrentState = SERVICE_STOPPED;
break;
default:
break;
}
SetServiceStatus(gSvcStatusHandle, &gSvcStatus);
}
最后完善代码
#include <Windows.h>
#include <stdio.h>
#define SERVICENAME L"ServiceDemo"
// 当前程序的状态信息句柄
SERVICE_STATUS_HANDLE gSvcStatusHandle;
// 程序最近的状态信息
SERVICE_STATUS gSvcStatus;
// 停止事件
HANDLE ghSvcStopEvent = NULL;
void WriteLog(const char* str)
{
FILE* fp;
fopen_s(&fp, "E:\\ServiceOutFile.txt", "a+");
if (fp == NULL)
return;
fprintf(fp, "%s\n", str);
fclose(fp);
}
// 服务控制函数
void WINAPI ServiceControlHandler(DWORD request)
{
switch (request)
{
// 服务停止
case SERVICE_CONTROL_STOP:
gSvcStatus.dwWin32ExitCode = 0;
gSvcStatus.dwCurrentState = SERVICE_STOPPED;
WriteLog("stop!");
SetEvent(ghSvcStopEvent);
break;
// 系统关机
case SERVICE_CONTROL_SHUTDOWN:
gSvcStatus.dwWin32ExitCode = 0;
gSvcStatus.dwCurrentState = SERVICE_STOPPED;
WriteLog("shutdown!");
SetEvent(ghSvcStopEvent);
break;
default:
break;
}
SetServiceStatus(gSvcStatusHandle, &gSvcStatus);
}
void ServiceMain(int argc, char** argv)
{
// 下面填充当前服务的基本信息
// 服务类型
gSvcStatus.dwServiceType = SERVICE_WIN32;
// 服务状态:正在启动中
gSvcStatus.dwCurrentState = SERVICE_START_PENDING;
// 当前服务接收的控制有哪些
gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP;
// 启动或停止时的服务错误码
gSvcStatus.dwWin32ExitCode = 0;
// 其它详细信息请查看文档: https://docs.microsoft.com/en-us/windows/win32/api/winsvc/ns-winsvc-service_status
gSvcStatus.dwServiceSpecificExitCode = 0;
gSvcStatus.dwCheckPoint = 0;
gSvcStatus.dwWaitHint = 0;
// 返回状态信息句柄
gSvcStatusHandle = RegisterServiceCtrlHandler(SERVICENAME, ServiceControlHandler);
if (gSvcStatusHandle == 0)
{
return;
}
// 将状态更改为running,即代表程序可以接收SCM发送的控制信息
gSvcStatus.dwCurrentState = SERVICE_RUNNING;
// 反馈状态信息给SCM
SetServiceStatus(gSvcStatusHandle, &gSvcStatus);
ghSvcStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (ghSvcStopEvent == 0)
{
return;
}
WriteLog("enter while\n");
while (WaitForSingleObject(ghSvcStopEvent, 0) == WAIT_TIMEOUT)
{
WriteLog("while....\n");
Sleep(500);
}
return;
}
int main()
{
WCHAR ServiceName[] = SERVICENAME;
SERVICE_TABLE_ENTRY DispatchTable[2];
// 服务名字
DispatchTable[0].lpServiceName = ServiceName;
// 服务主函数
DispatchTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;
// 注意需要加上下面两行,因为StartServiceCtrlDispatcher规定最后一个元素必须为NULL
DispatchTable[1].lpServiceName = NULL;
DispatchTable[1].lpServiceProc = NULL;
// 服务入口点
StartServiceCtrlDispatcher(DispatchTable);
return 0;
}
安装和删除服务
程序编译完成之后会生成exe文件,由于没有在代码中加入安装操作,所以需要借助其它工具来将这个exe文件安装到服务中,这个工具就是sc.exe
,已经内置到系统中了。
第一步:首先使用管理员权限打开命令提示符。
第二步:安装服务,运行命令sc create ServiceDemo binpath=D:\c++\ServiceProgramDemo\x64\Debug\ServiceProgramDemo.exe
。注意替换自己的路径。
第三步:查看自己的服务并配置启动项。搜索框搜索"运行",输入services.msc
,然后找到自己的服务名字ServiceDemo
,右键点击启动,服务就会启动了,并在E盘下输出相关日志信息。
删除服务很简单,先停止我们安装的服务,然后运行命令sc delete ServiceDemo
即可。
参考文献
https://docs.microsoft.com/en-us/windows/win32/services/service-programs
https://docs.microsoft.com/en-us/windows/win32/services/configuring-a-service-using-sc
网友评论