服务

Windows服务,是指运行在windows nt操作系统后台的计算机程序.
Windows服务必须符合服务控制管理器的接口规则和协议(SCM)

如何创建一个Windows服务

分为:
1.完成服务程序主函数(进程入口点(Main函数
2.完成服务程序内容主函数(ServiceMain
3.服务的注册器和卸载器

服务程序主函数

这个阶段,主要干两件事:
1.设定好SERVICE_TABLE_ENTRY 结构变量,传入服务名和服务主函数
2.调用StartServiceCtrlDispatcher函数
以下是SERVICE_TABLE_ENTRY结构

typedef struct _SERVICE_TABLE_ENTRYW {
LPWSTR lpServiceName;
LPSERVICE_MAIN_FUNCTIONW lpServiceProc;
}SERVICE_TABLE_ENTRYW, *LPSERVICE_TABLE_ENTRYW;

所以我们的服务程序的主函数只需要像这样写即可

int main(){
SERVICE_TABLE_ENTRY Table[] = { {L"servicename",ServiceMain},{NULL,NULL} };
StartServiceCtrlDispatcher(Table);
}

在SERVICE_TABLE_ENTRY里我们定义好了一个服务的名字以及其入口函数,然后使用StartServiceCtrlDispatcher去调用这个结构。
第一步就是这么简单,接下来让我们去实现服务的入口函数

完成服务程序内容主函数

这一步主要要干这几个事情:
1.创建服务内容主函数
2在服务内容主函数里实现 SERVICE_STATUS 结构的填充,用于与SCM交流
3.实现服务句柄,并根据有服务句柄改变状态信息,从而实现SCM发来的控制请求
4.逻辑

创建服务内容主函数

根据我们在 SERVICE_TABLE_ENTRY 结构中定义的服务入口函数,创建对应函数

void WINAPI Servicename(DWORD argc, LPTSTR* argv)

实现 SERVICE_STATUS 结构

SERVICE_STATUS 结构定义如下

typedef struct _SERVICE_STATUS {
DWORD dwServiceType;
DWORD dwCurrentState;
DWORD dwControlsAccepted;
DWORD dwWin32ExitCode;
DWORD dwServiceSpecificExitCode;
DWORD dwCheckPoint;
DWORD dwWaitHint;
} SERVICE_STATUS, *LPSERVICE_STATUS;

每个项都对应了参数…太多了,建议去官方文档对照
https://docs.microsoft.com/en-us/windows/win32/api/winsvc/ns-winsvc-service_status

dwservicetype 指定了服务的类型
dwCurrentState 指定服务当前的状态(挂起,暂停,启动,停止…)
dwControlsAccepted 指定了服务句柄可以接受的参数
dwWin32ExitCode 服务用于报告错误的错误代码
dwServiceSpecificExitCode 服务特定的错误代码
dwCheckPoint 不太懂
dwWaitHint 挂起的 启动,停止,暂停或继续操作所需的估计时间,以毫秒为单位 。若指定时间已过去而dwCheckPoint未增加或者dwCurrentState尚未更改,则中止服务。

总而言之,就是创建一个结构用于收集服务的各个信息

实现服务句柄&改变状态信息

实现服务句柄主要是依靠RegisterServiceCtrlHandlerA函数实现的。
这个函数的作用是注册一个函数来处理指定服务控制请求。

这个函数的结构是这样

SERVICE_STATUS_HANDLE RegisterServiceCtrlHandlerA(
LPCSTR lpServiceName,
LPHANDLER_FUNCTION lpHandlerProc
);

lpservicename是指定服务名,lphandlerproc是被注册的函数。所以我们要使用这个函数来注册另一个函数,首先要完成被注册函数的内部逻辑。这里做个范例

void WINAPI ctrl(DWORD Opcode)  //定义式固定
{
switch (Opcode)
{
case SERVICE_CONTROL_PAUSE:
m_ServiceStatus.dwCurrentState = SERVICE_PAUSED;
break;
case SERVICE_CONTROL_CONTINUE:
m_ServiceStatus.dwCurrentState = SERVICE_RUNNING;
break;
case SERVICE_CONTROL_STOP:
m_ServiceStatus.dwWin32ExitCode = 0;
m_ServiceStatus.dwCurrentState = SERVICE_STOPPED;
m_ServiceStatus.dwCheckPoint = 0;
m_ServiceStatus.dwWaitHint = 0;

SetServiceStatus(m_ServiceStatusHandle, &m_ServiceStatus);
bRunning = false;
break;
case SERVICE_CONTROL_INTERROGATE:
break;
}
return;
}

我们发现这个函数会根据传入的值改变 SERVICE_STATUS 结构。
若把他注册,那么从服务管理控制器发来的控制指令会被当作参数传入该函数,然后该函数会修改 SERVICE_STATUS

那么改变状态信息的函数主要就是SetServiceStatus 了

BOOL SetServiceStatus(
SERVICE_STATUS_HANDLE hServiceStatus,
LPSERVICE_STATUS lpServiceStatus
);

hservicestatus 指向服务句柄 lpservicestatus 指向 SERVICE_STATUS 结构。
完成这个函数后,服务就会向服务控制管理器发送自己最新的状态信息(即 SERVICE_STATUS 里定义的东西)。

DEMO

#include<Windows.h>

SERVICE_STATUS m_ServiceStatus;
SERVICE_STATUS_HANDLE m_ServiceStatusHandle;
BOOL bRunning;
int main() {
WCHAR Servicename[] = L"ServiceA";
SERVICE_TABLE_ENTRY Table[] = { {Servicename,ServiceMain},{NULL,NULL} };
StartServiceCtrlDispatcher(Table);
}

void WINAPI ServiceMain(DWORD argc, LPTSTR* argv) {


m_ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
m_ServiceStatus.dwCurrentState = SERVICE_START_PENDING;
m_ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
m_ServiceStatus.dwWin32ExitCode = 0;
m_ServiceStatus.dwServiceSpecificExitCode = 0;
m_ServiceStatus.dwCheckPoint = 0;
m_ServiceStatus.dwWaitHint = 0;

m_ServiceStatusHandle = RegisterServiceCtrlHandler(L"ServiceA", HandlerFunc);
m_ServiceStatus.dwCurrentState = SERVICE_RUNNING;
m_ServiceStatus.dwCheckPoint = 0;
m_ServiceStatus.dwWaitHint = 0;
SetServiceStatus(m_ServiceStatusHandle, &m_ServiceStatus);
bRunning = true;
while (bRunning) {
//your code
}
return 0;
}

void WINAPI HandlerFunc(DWORD code) {
switch (code) {
case SERVICE_CONTROL_PAUSE:
m_ServiceStatus.dwCurrentState = SERVICE_PAUSED;
break;
case SERVICE_CONTROL_CONTINUE:
m_ServiceStatus.dwCurrentState = SERVICE_RUNNING;
break;
case SERVICE_CONTROL_STOP:
m_ServiceStatus.dwWin32ExitCode = 0;
m_ServiceStatus.dwCurrentState = SERVICE_STOPPED;
m_ServiceStatus.dwCheckPoint = 0;
m_ServiceStatus.dwWaitHint = 0;

SetServiceStatus(m_ServiceStatusHandle, &m_ServiceStatus);
bRunning = false;
break;
case SERVICE_CONTROL_INTERROGATE:
break;
}
}