你是否想过要实现一个Windows程序,可以让它在系统启动的时候自动运行?或者后台运行,不显示界面?或者希望运行的时候能够方便的指定权限?那么Windows服务可以满足你的需求。
本文主要介绍如何用C++编写Windows服务。根据以下三点进行讲解:
在Widnows Service本地管理可以通过命令行services.msc
打开,可以看到Service的名称,运行状态等,也可以对Service 进行停止
,启动
等操作。
Windows控制服务管理器(Service Control Manager)主要负责统一的管理Windows Service,比如:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services
。Group Order
和服务的依赖关系Start
,Stop
等请求;Service程序也会告诉SCM当前Service的状态;对于我们的程序来说不一定关系这个底层实现细节,只需要知道我们调用的Service函数底层是和SCM进行了通信。Service常见的状态如下图所示。(图片来自于微软MSDN Service State Transitions)
接下来我们使用C++来实现一个Windows服务。我们先来看一下实现的结构图:
在介绍代码编写之前,我们先定义一个数据结构,实例化一个全局变量,后续会用到,代码里均以加上注释。
struct ServiceContext
{
ServiceContext() = delete;
ServiceContext(std::string strSvcName) : m_strSvcName(strSvcName)
{
;
}
~ServiceContext()
{
if (m_hStartedEvent)
CloseHandle(m_hStartedEvent);
if (m_hStopEvent)
CloseHandle(m_hStopEvent);
}
std::string m_strSvcName;
SERVICE_STATUS_HANDLE m_svcStatusHandle = nullptr; //Service Status Handle
SERVICE_STATUS m_svcStatus; //Service Name
//This event used to check whether service worker is started
HANDLE m_hStartedEvent = CreateEvent(
NULL, // default security attributes
TRUE, // manual reset event
FALSE, // not signaled
NULL); // no name;
//This event used to notify service worker to stop
HANDLE m_hStopEvent = CreateEvent(
NULL, // default security attributes
TRUE, // manual reset event
FALSE, // not signaled
NULL); // no name;
DWORD m_dwHintTimeout = 5 * 1000; // 5 seconds, check interval for starting and stopping
DWORD m_dwCheckPoint = 1; // It's used to co-work check for starting and stopping
};
ServiceContext gCoderSvcCtx("CoderService");
第一步
当用户在服务管理器中启动
Service的时候,Service Control Manager(后面简称SCM
), 将会创建我们编写的服务进程。此时进程会调用main
。
StartServiceCtrlDispatcher
在main
函数启动后,尽量快速调用,否则一段时间后,SCM会认为程序hang住了,而关闭掉Service进程。StartServiceCtrlDispatcher
, 注册我们的Service: Service名称为CoderService
, Service对应的启动函数是CoderServiceMain
。StartServiceCtrlDispatcher
一般当Service设置为停止
状态的时候,才会退出。当然这里的停止包括: Service进程终止; Service接收到停止
指令,后设置Stop状态等等。int main()
{
SERVICE_TABLE_ENTRY svcTableEntry[] = {
{const_cast<char *>(gCoderSvcCtx.m_strSvcName.c_str()), CoderServiceMain},
{NULL, NULL}
};
//It should be invoked in main as soon as possible
//Start failed, you can use GetLastError() to get the error code
if (!StartServiceCtrlDispatcher(svcTableEntry))
return -1;
return 0;
}
第二步
CoderServiceMain
是对应Service CoderWorker
的回调程序入口。
CoderServiceController
作为Event处理回调函数,比如用户停止
服务。SERVICE_WIN32_OWN_PROCESS
设置服务为独占程序的服务,即非
多个Sevice共享服务程序。SERVICE_START_PENDING
, 通知SCMStartCoderServiceWorker
进入下一步启动过程ReportSvcStatus
是SetServiceStatus
的封装,去除重复代码调用//Refer to: https://docs.microsoft.com/en-us/windows/win32/services/writing-a-servicemain-function
bool ReportSvcStatus(ServiceContext& ctx,
DWORD dwCurrentState,
DWORD dwWin32ExitCode)
{
// Fill in the SERVICE_STATUS structure.
ctx.m_svcStatus.dwCurrentState = dwCurrentState;
ctx.m_svcStatus.dwWin32ExitCode = dwWin32ExitCode;
ctx.m_svcStatus.dwWaitHint = ctx.m_dwHintTimeout;
if (dwCurrentState == SERVICE_START_PENDING)
ctx.m_svcStatus.dwControlsAccepted = 0;
else
ctx.m_svcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
if ((dwCurrentState == SERVICE_RUNNING) ||
(dwCurrentState == SERVICE_STOPPED))
ctx.m_svcStatus.dwCheckPoint = 0;
else
ctx.m_svcStatus.dwCheckPoint = ctx.m_dwCheckPoint++;
// Report the status of the service to the SCM.
return SetServiceStatus(ctx.m_svcStatusHandle, &ctx.m_svcStatus);
}
//Each Service binding a ServiceMain
void CoderServiceMain(DWORD dwNumServicesArgs, LPSTR *lpServiceArgVectors)
{
// 1. Register Control Handler
gCoderSvcCtx.m_svcStatusHandle = RegisterServiceCtrlHandler(const_cast<char *>(gCoderSvcCtx.m_strSvcName.c_str()),
CoderServiceController);
if (!gCoderSvcCtx.m_svcStatusHandle)
return;
// 2. Set SERVICE_START_PENDING
gCoderSvcCtx.m_svcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
gCoderSvcCtx.m_svcStatus.dwServiceSpecificExitCode = 0;
if (!ReportSvcStatus(gCoderSvcCtx, SERVICE_START_PENDING, NO_ERROR))
{
ReportSvcStatus(gCoderSvcCtx, SERVICE_STOPPED, NO_ERROR);
return;
}
// 3. your worker start
// Service woker will work for specified task, if it return means it's stopped
StartCoderServiceWorker(dwNumServicesArgs, lpServiceArgVectors);
return;
}
第三步
StartCoderServiceWorker
这个是Service启动的主要过程
CoderServiceWorker
你可以这样理解:StartCoderServiceWorker
以及之前的部分,主要是为了和SCM进行交互,而CoderServiceWorker
则为你的Service需要执行的内容/工作。CoderWorker
也是对实际执行的内容/工作做了封装。提供了两个接口Start
, Stop
。划重点了,如果你要实现一个windows服务的具体工作,那么只需要在Run函数中做实现即可。CoderServiceWorker
的初始化完成后,会触发m_hStartedEvent
, StartCoderServiceWorker
收到这个事件后,会通知SCM状态改为SERVICE_RUNNING
。因为有些程序初始化需要时间比较长,不过一般是不建议初始化时间太长。有的程序员会让程序的Service尽快进入正在运行
状态,异步的去做一些初始化,但这样会导致一个问题,就是服务已经显示正在运行
状态,但其初始化未完成无法提供服务;所以有的程序员会等待初始化完毕后,才通知SCM为正在运行
状态,以为这样保证,服务正在运行
状态就保证了其已经可以正常工作了。这两种方式根据自己的场景进行抉择。而本程序是用sleep
模拟了10秒钟的初始化时间,并且在StartCoderServiceWorker
等待m_hStartedEvent
。如果初始化一直没有完成,那么StartCoderServiceWorker
会间隔m_dwHintTimeout
像SCM发送SERVICE_START_PENDING
。为什么要这么做?因为MSDN上说如果在m_dwHintTimeout
间隔时间后需要更新服务的状态,否则有可能SCM会认为程序已经出错,并且停止服务(但本人实际测试,并不会停止服务,但为了信任微软的文档,此程序还是会及时的发送消息给SCM)。关于m_hStopEvent
这个和m_hStartedEvent
实现方式类似,主要用于感知我们的服务的工作已经停止了,可以对SCM发送SERVICE_STOPPED
的状态了。这个后续再讲,因为目前说的是Service启动过程。
启动完成后程序将会进入如下状态:
StartCoderServiceWorker
等待m_hStopEvent
事件CoderServiceWorker
在等待m_hStopEvent
事件CoderWorker::Run
循环执行任务,并且查看是否需要停止class CoderWorker
{
public:
bool Start()
{
// You should fill your Worker thread init task here
// Here just sleep 10 seconds to fake the init time
std::this_thread::sleep_for(std::chrono::seconds(10));
//Start the worker process tasks
int iTmpPara = 1;
m_thWorker = std::thread(&CoderWorker::Run, this, iTmpPara);
return true;
}
void Stop()
{
m_bStop = true;
if (m_thWorker.joinable())
m_thWorker.join();
// Here we just Fake sleep 10 seconds to stop
std::this_thread::sleep_for(std::chrono::seconds(10));
}
void Run(int iPara)
{
while (!m_bStop)
{
//do task
//Here just fill your code to do your work
//just here fake sleep 1 second to do task
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
private:
std::thread m_thWorker;
volatile bool m_bStop = false;
};
// Here you can also specify other parameters
void CoderServiceWorker(ServiceContext& ctx, DWORD dwNumServicesArgs, LPSTR *lpServiceArgVectors)
{
// 1. Init and Start worker and notify the it's started
CoderWorker woker{};
if (!woker.Start())
ExitThread(-1);
SetEvent(ctx.m_hStartedEvent);
// 2. Wait for the stop event
DWORD dwCode = WaitForSingleObject(ctx.m_hStopEvent, INFINITE);
woker.Stop();
ExitThread(0);
return;
}
//Service Worker means what your service really do
void StartCoderServiceWorker(DWORD dwNumServicesArgs, LPSTR *lpServiceArgVectors)
{
// 1. Start your service worker to handle tasks
std::thread thWorker(CoderServiceWorker, std::ref(gCoderSvcCtx), dwNumServicesArgs, lpServiceArgVectors);
// 2. Check the starting status
while (true)
{
DWORD dwCode = WaitForSingleObject(gCoderSvcCtx.m_hStartedEvent, gCoderSvcCtx.m_dwHintTimeout);
if (WAIT_TIMEOUT == dwCode)
{
//Still starting
ReportSvcStatus(gCoderSvcCtx, SERVICE_START_PENDING, NO_ERROR);
}
else if (WAIT_OBJECT_0 == dwCode)
{
//Started Finish
ResetEvent(gCoderSvcCtx.m_hStartedEvent);
ReportSvcStatus(gCoderSvcCtx, SERVICE_RUNNING, NO_ERROR);
break;
}
else
{
//Other errors
ReportSvcStatus(gCoderSvcCtx, SERVICE_STOPPED, NO_ERROR);
return;
}
}
// 3. Check whether receive stop event
while (true)
{
DWORD dwCheckTime = 1000; //1 second
DWORD dwCode = WaitForSingleObject(gCoderSvcCtx.m_hStopEvent, dwCheckTime);
if (WAIT_TIMEOUT == dwCode)
{
//Still runining
continue;
}
else if (WAIT_OBJECT_0 == dwCode)
{
//It begin to stop
// run into Stop pending status
ReportSvcStatus(gCoderSvcCtx, SERVICE_STOP_PENDING, NO_ERROR);
break;
}
else
{
//Other errors
ReportSvcStatus(gCoderSvcCtx, SERVICE_STOPPED, NO_ERROR);
return;
}
}
// 4. Check the stopping status
// If it's stopped then set the status to STOP
HANDLE hThreadHandle = thWorker.native_handle();
while (true)
{
DWORD dwCode = WaitForSingleObject(hThreadHandle, gCoderSvcCtx.m_dwHintTimeout);
if (WAIT_TIMEOUT == dwCode)
{
//Still stopping
ReportSvcStatus(gCoderSvcCtx, SERVICE_STOP_PENDING, NO_ERROR);
}
else if (WAIT_OBJECT_0 == dwCode)
{
// stop Finish
// Reset the stop event
ResetEvent(gCoderSvcCtx.m_hStopEvent);
ReportSvcStatus(gCoderSvcCtx, SERVICE_STOPPED, NO_ERROR);
break;
}
else
{
//Other errors
ReportSvcStatus(gCoderSvcCtx, SERVICE_STOPPED, NO_ERROR);
return;
}
}
return;
}
第四步
上面已经描述完成了程序的启动过程,并且服务已经处于正在运行
状态了。那么假设这个时候用户在服务管理器中点击了停止
服务,程序会如何运行呢?
第二步
中提到的CoderServiceController
不?这是个回调函数,收到了SERVICE_CONTROL_STOP
,会先设置服务状态为SERVICE_STOP_PENDING
, 并且设置m_hStopEvent
通知其他线程开始停止工作。CoderServiceWorker
在接受到m_hStopEvent
事件,调用woker.Stop()
设置m_bStop
为true
,此时CoderWorker::Run
,停止工作。这里模拟了10秒钟的stop后释放资源的时间。StartCoderServiceWorker
接收到m_hStopEvent
事件,开始等待CoderServiceWorker
线程停止,并且每隔m_dwHintTimeout
时长给SCM报告SERVICE_STOP_PENDING
。直到CoderServiceWorker
停止,给SCM报告SERVICE_STOPPED
状态。//Each Service have a Service Controller to control the
void CoderServiceController(DWORD dwControl)
{
// Handle the requested control code.
switch (dwControl)
{
case SERVICE_CONTROL_SHUTDOWN:
case SERVICE_CONTROL_STOP:
ReportSvcStatus(gCoderSvcCtx, SERVICE_STOP_PENDING, NO_ERROR);
// Signal the service to stop.
SetEvent(gCoderSvcCtx.m_hStopEvent);
return;
default:
break;
}
}
上面已经编写了一个Service程序,下面来讲讲如何对我们的服务进行部署和配置。本人知道的大概有两种:
CreateService
这类API。然后通过命令行参数来控制程序启动是创建服务
,删除服务
,还是启动服务
。sc
对服务进行操作,本文主要讲sc
命令创建一个服务的命令: sc create CoderWorker binpath=C:\Personal\WindowsServiceNormal.exe type=own start=demand displayname="Coder Woker"
安装一个服务我们最需要注意的是以下几点:
Local System
CoderWorker
, 这里要注意和我们编写的服务里面的m_strSvcName
服务名字要一致C:\Personal\WindowsServiceNormal.exe
demand
表示手动。而如果想做成开机启动可以设置为auto
sc start CoderWorker
这个命令是不等待服务启动完毕的。如果想等待启动完毕后再返回可以调用net start CoderWorkder
.
sc query CoderWorker
sc stop CoderWorker
这个命令是不等待服务启动完毕的。如果想等待启动完毕后再返回可以调用net stop CoderWorkder
.
sc delete CoderWorker
,在调用这个命令之前,最好是先停止服务。
这个功能是非常有用的一个功能。大多数的程序员都不敢保证自己写的程序永远都不会崩溃,尤其是C++
程序员。那么当你编写的服务在客户的机器上运行时,如果崩溃后,程序就不再工作了,在有些情况下是不太能够接受的。Windows的服务框架提供了这个功能,只需要在Windows服务中进行配置即可,如下图: