C编写服务程序详细步骤
1、简介
Windows 服务被设计用于需要在后台运行的应用程序以及实现没有用户交互的任务。为了学习这种控制台应用程序的基础知识,C(不是C++)是最佳选择。C++面向对象的方法固然方便,但由于用类对底层 Win32 函数调用进行了封装,它不利于学习服务程序的基本知识。
服务是一个运行在后台并实现勿需用户交互的任务的控制台程序。Windows NT/2000/XP 操作系统提供为服务程序提供专门的支持。
2、步骤
2.1 主函数和全局定义
包含所需的头文件。要调用 Win32 函数(windows.h)和其他所需要的
2.2 定义2个常量
1 #define SLEEP_TIME 5000 2 #define LOGFILE "E:\\test.txt"
View Code
SLEEP_TIME 指定两次连续查询可用内存之间的毫秒间隔。在第二步中编写服务工作循环的时候要使用该常量。
LOGFILE 定义日志文件的路径,你将会用 WriteToLog 函数将内存查询的结果输出到该文件
Log 函数定义如下:
1 int Log(char* str) 2 { 3 FILE* log; 4 log = fopen(LOGFILE, "a+"); 5 if (log == NULL) 6 return -1; 7 fprintf(log, "%s\n", str); 8 fclose(log); 9 return 0; 10 }
View Code
2.3 声明几个全局变量
作用:便在程序的多个函数之间共享它们值,做一个函数的前向定义:
1 SERVICE_STATUS ServiceStatus; 2 SERVICE_STATUS_HANDLE hStatus; 3 void ServiceMain(int argc, char** argv); 4 void ControlHandler(DWORD request); 5 int InitService();
View Code
2.4 编码
服务程序是控制台程序的一个子集。因此需要定义1个main函数,是程序的入口点。对于服务程序来说,main 的代码很简短,因为它只创建分派表并启动控制分派机。
1 int main(int argc, const char *argv[]) 2 { 3 SERVICE_TABLE_ENTRY ServiceTable[2]; 4 5 ServiceTable[0].lpServiceName = _T(SERVICE_NAME); 6 ServiceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)service_main; 7 8 ServiceTable[1].lpServiceName = NULL; 9 ServiceTable[1].lpServiceProc = NULL; 10 // 启动服务的控制分派机线程 11 StartServiceCtrlDispatcher(ServiceTable); 12 return 0; 13 }
View Code
2.5 分派表的域
一个程序可能包含若干个服务。每一个服务都必须列于专门的分派表中(为此该程序定义了一个 ServiceTable 结构数组)。这个表中的每一项都要在 SERVICE_TABLE_ENTRY 结构之中。它有两个域:
lpServiceName: 指向表示服务名称字符串的指针;当定义了多个服务时,那么这个域必须指定;
lpServiceProc: 指向服务主函数的指针(服务入口点);
分派表的最后一项必须是服务名和服务主函数域的 NULL 指针,文本例子程序中只宿主一个服务,所以服务名的定义是可选的。
2.6 服务控制管理器(SCM:Services Control Manager)
是一个管理系统所有服务的进程。当 SCM 启动某个服务时,它等待某个进程的主线程来调用 StartServiceCtrlDispatcher 函数。将分派表传递给 StartServiceCtrlDispatcher。这将把调用进程的主线程转换为控制分派器。该分派器启动一个新线程,该线程运行分派表中每个服务的 ServiceMain 函数(本文例子中只有一个服务)分派器还监视程序中所有服务的执行情况。然后分派器将控制请求从 SCM 传给服务。
注意:如果 StartServiceCtrlDispatcher 函数30秒没有被调用,便会报错,为了避免这种情况,我们必须在 ServiceMain 函数中(参见本文例子)或在非主函数的单独线程中初始化服务分派表。本文所描述的服务不需要防范这样的情况。分派表中所有的服务执行完之后(例如,用户通过“服务”控制面板程序停止它们),或者发生错误时。StartServiceCtrlDispatcher 调用返回。然后主进程终止。
2.7 ServiceMain 函数
1)ServiceStatus 结构的域用途
dwServiceType:指示服务类型,创建 Win32 服务。赋值 SERVICE_WIN32;
dwCurrentState:指定服务的当前状态。因为服务的初始化在这里没有完成,所以这里的状态为 SERVICE_START_PENDING;
dwControlsAccepted:这个域通知 SCM 服务接受哪个域。本文例子是允许 STOP 和 SHUTDOWN 请求。处理控制请求将在第三步讨论;
dwWin32ExitCode 和 dwServiceSpecificExitCode:这两个域在你终止服务并报告退出细节时很有用。初始化服务时并不退出,因此,它们的值为 0;
dwCheckPoint 和 dwWaitHint:这两个域表示初始化某个服务进程时要30秒以上。本文例子服务的初始化过程很短,所以这两个域的值都为 0。
调用 SetServiceStatus 函数向 SCM 报告服务的状态时。要提供 hStatus 句柄和 ServiceStatus 结构。注意 ServiceStatus 一个全局变量,所以你可以跨多个函数使用它。ServiceMain 函数中,你给结构的几个域赋值,它们在服务运行的整个过程中都保持不变。
报告了服务状态之后,可调用 InitService 函数来完成初始化。检查 InitService 函数的返回值。如果初始化有错(因为有可能写日志文件失败),则将服务状态置为终止并退出。
初始化成功,则向 SCM 报告状态:
ServiceStatus.dwCurrentState = SERVICE_RUNNING;
BOOL bstatus = SetServiceStatus(hStatus,&ServiceStatus);
启动工作循环,执行所希望的工作。知道服务的状态为SERVICE_STOP或者SERVICE_SHUTDOWN或日志文件写入出错为止。状态可能在 ControlHandler 函数响应 SCM 控制请求时修改。
2.8 处理控制请求
在 ServiceMain 函数注册了控制处理器函数。控制处理器与处理各种 Windows 消息的窗口回调函数非常类似。它检查 SCM 发送了什么请求并采取相应行动。
STOP 请求是 SCM 终止服务的时候发送的。针对的是单个服务,如同在“服务”控制面板中手动终止服务。SHUTDOWN 请求是关闭机器时,由 SCM 发送给所有运行中服务的请求。
这两种情况的处理方式相同。此时:
1)写日志文件,监视停止
2)向SCM报告SERVICE_STOPPED 状态
由于 ServiceStatus 结构对于整个程序而言为全局量,ServiceStatus 中的工作循环在当前状态改变或服务终止后停止.控制处理器函数必须报告服务状态,即便 SCM 每次发送控制请求的时候状态保持相同。因此,不管响应什么请求,都要调用 SetServiceStatus。
2.9 安装和配置服务
程序编写好之后编译为exe文件。为在机器上安装该服务,需要SC.EXE可执行文件,是 Win32 Platform SDK 中附带的一个工具。可以专门创建一个文件夹保存自己创建服务的exe文件。
安装服务方法:sc create 服务名 binPath= exe可执行文件路径(指定服务名和二进制文件路径)
一般自己创建的服务的启动类型是手动,即根据需要启动服务,可右键打开属性页设置。删除服务方法:sc delete 服务名
2.10 测试服务
若将txt日志文件设置为只读,执行net start test,会提示:test 服务正在启动 .test 服务已经启动成功。但是执行net stop test时提示:没有启动 test 服务。说明服务并没有启动。去掉只读属性,启动服务,在将文件设成只读。服务将停止执行,因为此时日志文件写入失败。