前言
想要了解服务程序的运行相关的基础知识,最好还是从最底层的API入手去动手实践一下,而不是用上层的封装,搭建一个简单的服务
由于某些原因,我需要编写一个服务程序,无奈没注释的示例代码看不懂,从往上零零散散的博客文章中找到了这么一个编写服务的练习,结合官方文档,终于,完成了简单的服务的搭建
本文用来记录一下服务程序是如何编写的
例子是:创建一个定期查询可用物理内存并将结果写入某个文本文件的服务。
服务程序介绍
服务程序主要有三部分构成:
- main程序:程序的入口函数,功能是创建分派表并启动控制分派机线程,启动控制分派机线程需要指定ServiceMain函数
- ServiceMain程序:负责初始化服务状态,注册指定控制处理器函数,更新报告服务状态,进入服务的功能部分
- 控制处理器程序:负责接收服务请求,进行处理,更新报告服务状态
编写服务程序
服务main函数
#include<Windows.h>
#include<stdio.h>
#define SLEEP_TIME 5000 //两次连续查询可用内存之间的毫秒间隔
#define LOGFILE "D:\\MyServicesLog.txt" //日志文件的路径
//全局变量
SERVICE_STATUS ServiceStatus; //服务状态
SERVICE_STATUS_HANDLE hStatus;
//服务程序main函数只用来创建分派表,和启动控制分派机
int main() {
//创建分派表
SERVICE_TABLE_ENTRY ServiceTable[2];
ServiceTable[0].lpServiceName = (LPWSTR)L"MemoryStatus";
ServiceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;
//这个结构体以空结尾
ServiceTable[1].lpServiceName = NULL;
ServiceTable[1].lpServiceProc = NULL;
//启动控制分派机
StartServiceCtrlDispatcher(ServiceTable);
}
服务主程序的功能非常简单,就做一件事。创建分派表并启动控制分派机线程
分派表是一个结构体数组SERVICE_TABLE_ENTRY,以空项作为结尾的标识
typedef struct _SERVICE_TABLE_ENTRYA {
LPSTR lpServiceName;//服务名称,
LPSERVICE_MAIN_FUNCTIONA lpServiceProc;//服务主函数
} SERVICE_TABLE_ENTRYA, *LPSERVICE_TABLE_ENTRYA;
https://docs.microsoft.com/en-us/windows/win32/api/winsvc/ns-winsvc-service_table_entrya
启动分派机函数:StartServiceCtrlDispatcherA
BOOL StartServiceCtrlDispatcherA(
const SERVICE_TABLE_ENTRYA *lpServiceStartTable//指向SERVICE_TABLE_ENTRYA的指针,结构体最后一成员必须为空
);
功能是将服务进程主线程连接到服务控制管理器SCM,使该线程成为调用过程的服务控制调度程序线程,简单来说,就是创建一个新线程用来进行服务控制调度
当分派表中的所有服务之星完毕之后(服务为停止状态),或者发送运行时错误,该函数调用返回,进程终止
https://docs.microsoft.com/en-us/windows/win32/api/winsvc/nf-winsvc-startservicectrldispatchera
服务ServiceMain主函数
VOID ServiceMain(int argc, char** argv) {
//设置服务状态
//服务类型:创建Win32服务
ServiceStatus.dwServiceType = SERVICE_WIN32;
//服务当前状态,在这里的时候初始化未完成,所以pending,pending是啥???
ServiceStatus.dwCurrentState = SERVICE_START_PENDING;
//通知SCM服务接收哪个域,处理控制请求后处理
ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP;//本例只接收系统关机和停止服务两种控制命令
//终止服务并报告退出细节,初始化时不退出,进行置零
ServiceStatus.dwWin32ExitCode = 0;
ServiceStatus.dwServiceSpecificExitCode = 0;
//表示初始化某个服务需要30s以上,因为这个服务很短,置零即可
ServiceStatus.dwCheckPoint = 0;
ServiceStatus.dwWaitHint = 0;
////////////////////////////////////////////////////////////////////////
//为服务注册控制处理器SCM
hStatus = RegisterServiceCtrlHandler(L"MemoryStatus", (LPHANDLER_FUNCTION)ControlHandler);
if (!hStatus) {
WriteToLog("RegisterServiceCtrlHandler Failed!\n");
return;
}
WriteToLog("RegisterServiceCtrlHandler Success!\n");
if (InitService() == FALSE) {
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
ServiceStatus.dwWin32ExitCode = -1;
SetServiceStatus(hStatus, &ServiceStatus); //向SCM报告服务出错的状态
return;
}
//////////////////////////////////////////////////////////////////////////
//向SCM报告运行服务状态
ServiceStatus.dwCurrentState = SERVICE_RUNNING;
BOOL bStatus = SetServiceStatus(hStatus, &ServiceStatus);
if (!bStatus) {
DWORD dwError = GetLastError();
char szStr[100] = { 0 };
sprintf_s(szStr,100,"SetServiceStatus Failed,The Error Value is %d",dwError);
WriteToLog(szStr);
return;
}
//启动任务循环,添加自己的代码
}
进入ServiceMain函数之后,首先需要设置服务的状态,也就是向SERVICE_STATUS结构体填充值
typedef struct _SERVICE_STATUS {
DWORD dwServiceType; //服务类型,SERVICE_WIN32表示创建Win32服务
DWORD dwCurrentState; //指定服务当前状态,初始化未完成时填SERVICE_START_PANDING
DWORD dwControlsAccepted; //这个通知SCM服务接收哪种控制
DWORD dwWin32ExitCode; //终止服务报告退出细节时用,不退出则为0
DWORD dwServiceSpecificExitCode;//终止服务报告退出细节时用,不退出则为0
DWORD dwCheckPoint; //初始化要30s以上时候需要填,不然为0
DWORD dwWaitHint; //初始化要30s以上时候需要填,不然为0
} SERVICE_STATUS, *LPSERVICE_STATUS;
这里第一次初始化的时候每个值都要进行设置,初始化的时候主要就是设置前三个成员的值,后面全是0
然后之后更变服务状态的时候,主要就是改变退出码和服务状态然后报告给SCM
https://docs.microsoft.com/en-us/windows/win32/api/winsvc/ns-winsvc-service_status
初始化完服务的状态之后,需要为服务注册控制处理器,注册完之后返回状态句柄
SERVICE_STATUS_HANDLE RegisterServiceCtrlHandlerA(
LPCSTR lpServiceName, //服务名称
LPHANDLER_FUNCTION lpHandlerProc //服务控制函数
);
https://docs.microsoft.com/en-us/windows/win32/api/winsvc/nf-winsvc-registerservicectrlhandlera
注册完控制处理器之后,需要向SCM进行报告
ServiceStatus.dwCurrentState = SERVICE_RUNNING;
BOOL bStatus = SetServiceStatus(hStatus, &ServiceStatus);
注册完控制处理器之后,服务状态更变为RUNNING,每次变更状态都需要向SCM进行报告
BOOL SetServiceStatus(
SERVICE_STATUS_HANDLE hServiceStatus,//状态句柄
LPSERVICE_STATUS lpServiceStatus//服务状态结构体
);
https://docs.microsoft.com/en-us/windows/win32/api/winsvc/nf-winsvc-setservicestatus
报告完状态,没有问题的话,就可以开始写服务功能函数了
这里的WriteToLog函数是:
//将文本写入文件
BOOL WriteToLog(LPCSTR szText) {
FILE* log = fopen(LOGFILE, "a+");//以向后添加的方式打开文件
if (log == NULL) {
return FALSE;
}
fprintf(log, "%s\n", szText);
fclose(log);
return TRUE;
}
服务功能是:
//这里是每隔10s查询一次可用物理内存写入日志
MEMORYSTATUS Memstatus;
BOOL bRun = TRUE;
while (bRun) {
char szStr[100] = { 0 };
GlobalMemoryStatus(&Memstatus);
int iAvailMb = Memstatus.dwAvailPhys / 1024 / 1024;
sprintf_s(szStr, 100, "Available Memory is %d MB", iAvailMb);
WriteToLog(szStr);
Sleep(SLEEP_TIME);
}
WriteToLog("Service Stopped");
服务初始化函数InitService:检查服务功能是否能够正常使用
//服务初始化
int InitService() {
int result = WriteToLog("Monitoring Start");
return result;
}
服务控制处理器
接下来就是服务控制处理器了
VOID ControlHandler(DWORD request) {
//不管响应什么请求,都要报告状态
switch (request) {
case SERVICE_CONTROL_STOP: {//SCM终止服务的时候发送
//写日志文件,停止监视,报告状态
WriteToLog("Monitoring stopped.");
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
ServiceStatus.dwWin32ExitCode = 0;
SetServiceStatus(hStatus,&ServiceStatus);
return;
}
case SERVICE_CONTROL_SHUTDOWN: {//电脑关机时发送的
WriteToLog("Monitoring stopped.");
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
ServiceStatus.dwWin32ExitCode = 0;
SetServiceStatus(hStatus, &ServiceStatus);
return;
}
default:break;
}
//不管响应什么请求,都要调用这个函数报告状态
SetServiceStatus(hStatus, &ServiceStatus);
return;
}
这个玩意有点像窗口过程(除此之外,还有调试循环也很像窗口过程)
函数获得请求信息,通过switch语句进行选择执行,执行不同的指令,设置不同的服务状态,并报告给SCM
这一块比较简单,用到的都是前面提过的结构体和API
生成服务&效果演示
到此,服务程序的代码已经写完了,接下来要让这个程序变成服务
通过cmd(要用管理员启动才行)的sc命令可以实现服务的创建与删除:
//服务的创建
sc create MemStatus binpath= D:\\MyFirstWindowsService.exe
//服务的删除
sc delete MemStatus
//服务的启动
sc start MemStatus
//服务的停止
sc stop MemStatus
可通过运行services.msc来查看系统的服务
通过Process Explorer也能查看到:
这个SESSION 0是只有系统服务才有的
这个服务的功能是每10s查询一次可用物理内存并写入文件内:
服务程序正常运行了!
参考资料
-
初始化
-
C/C++编写服务程序
-
windows下sc create命令行添加/创建/修改服务
-
C++创建Windows后台服务程序