用C写一个简单的win32服务程序

         windows服务被设计用于需要在后台运行的应用程序以及实现没有用户交互的任务。

        服务时一个运行在后台并实现无需用户交互的任务的控制台程序。

本文只是简单的实现定期向文本文件写入内容的简单服务。然后指导你完成生成,安装和实现服务的整个过程。

 第一步:主函数和全局定义

首先包含所需的头文件。要调用win32函数(windows.h)和磁盘文件写入(stdio.h)

#include <Windows.h>
#include <stdio.h>

接着,定义两个常量:

#define SLEEP_TIME 5000      //写入文件的时间间隔
#define LOGFILE "C:\\MyServices\\memstatus.txt" //创建日志文件路径

LOGFILE 定义日志文件的路径,你将会用 WriteToLog 函数将内存查询的结果输出到该文件,WriteToLog 函数定义如下:

int WriteToLog(char* str) //写日志
{
 FILE* log;
 log = fopen(LOGFILE, "a+");   //以追加的方式打开文件
 if(log == NULL)
  return -1;
 fprintf(log, "%s\n", str);
 fclose(log);
 return 0;
}

声明几个全局变量,以便在程序的多个函数之间共享它们值。此外,做一个函数的前向定义:

SERVICE_STATUS ServiceStatus;    //服务状态
SERVICE_STATUS_HANDLE hStatus;  //服务状态句柄
void ServiceMain(int argc, char** argv);
void ControlHandler(DWORD request);
int InitService();

现在,准备工作已经就绪,你可以开始编码了。服务程序控制台程序的一个子集。因此,开始你可以定义一个 main 函数,它是程序的入口点。对于服务程序来说,main 的代码令人惊讶地简短,因为它只创建分派表并启动控制分派机。

void main()
{
 SERVICE_TABLE_ENTRY ServiceTable[2]; //分派表
 ServiceTable[0].lpServiceName = (LPWSTR)"MemoryStatus"; //服务名
 ServiceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain; //服务主函数,服务入口点
 ServiceTable[1].lpServiceName = NULL;  //标志服务结束
 ServiceTable[1].lpServiceProc = NULL;
 //启动服务的控制分派线程, 为每个服务分派一个线程
 StartServiceCtrlDispatcher(ServiceTable);

}

         一个程序可能包含若干个服务。每一个服务都必须列于专门的分派表中(为此该程序定义了一个 ServiceTable 结构数组)。这个表中的每一项都要在 SERVICE_TABLE_ENTRY 结构之中。它有两个域:

lpServiceName: 指向表示服务名称字符串的指针;当定义了多个服务时,那么这个域必须指定;

lpServiceProc: 指向服务主函数的指针(服务入口点);

分派表的最后一项必须是服务名和服务主函数域的 NULL 指针,文本例子程序中只宿主一个服务,所以服务名的定义是可选的。

服务控制管理器(SCM:Services Control Manager)是一个管理系统所有服务的进程。当 SCM 启动某个服务时,它等待某个进程的主线程来调用 StartServiceCtrlDispatcher 函数。将分派表传递给 StartServiceCtrlDispatcher。这将把调用进程的主线程转换为控制分派器。该分派器启动一个新线程,该线程运行分派表中每个服务的 ServiceMain 函数(本文例子中只有一个服务)分派器还监视程序中所有服务的执行情况。然后分派器将控制请求从 SCM 传给服务。

注意:如果 StartServiceCtrlDispatcher 函数30秒没有被调用,便会报错,为了避免这种情况,我们必须在 ServiceMain 函数中(参见本文例子)或在非主函数的单独线程中初始化服务分派表。本文所描述的服务不需要防范这样的情况。

分派表中所有的服务执行完之后(例如,用户通过“服务”控制面板程序停止它们),或者发生错误时。StartServiceCtrlDispatcher 调用返回。然后主进程终止。

第二步:ServiceMain 函数

该函数是服务的入口点。它运行在一个单独的线程当中,这个线程是由控制分派器创建的。ServiceMain 应该尽可能早早为服务注册控制处理器。这要通过调用 RegisterServiceCtrlHadler 函数来实现。你要将两个参数传递给此函数:服务名和指向 ControlHandlerfunction 的指针。

它指示控制分派器调用 ControlHandler 函数处理 SCM 控制请求。注册完控制处理器之后,获得状态句柄(hStatus)。通过调用 SetServiceStatus 函数,用 hStatus 向 SCM 报告服务的状态。
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 函数中,你给结构的几个域赋值,它们在服务运行的整个过程中都保持不变,比如:dwServiceType。

void ServiceMain(int argc, char** argv)
{
 ServiceStatus.dwServiceType = SERVICE_WIN32; //指示服务类型,创建win32服务
 ServiceStatus.dwCurrentState = SERVICE_START_PENDING;  //指定服务当前状态,服务的初始化还没完成
 ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP|SERVICE_ACCEPT_SHUTDOWN;    //通知SCM服务接受哪个域
 ServiceStatus.dwWin32ExitCode = 0;    //它是在你终止服务时报告退出细节,初始化服务并不退出
 ServiceStatus.dwServiceSpecificExitCode = 0;
 ServiceStatus.dwCheckPoint = 0; //表示初始化某个服务进程时要30s以上,本例服务的初始化过程很短,所以都为0
 ServiceStatus.dwWaitHint = 0;
 hStatus = RegisterServiceCtrlHandler((LPWSTR)"MemoryStatus",(LPHANDLER_FUNCTION)ControlHandler);  //注册服务得到句柄
 if(0 == hStatus)
 {
  return;
 }
 SetServiceStatus(hStatus, &ServiceStatus); //设置服务状态,现在还是未定

 if(InitService()) //初始化失败,终止服务     
 {
  ServiceStatus.dwCurrentState = SERVICE_STOPPED;
  ServiceStatus.dwWin32ExitCode = -1;
  SetServiceStatus(hStatus, &ServiceStatus);
  return;
 }
 ServiceStatus.dwCurrentState = SERVICE_RUNNING;     //初始化成功后,设置服务状态为运行态
 SetServiceStatus(hStatus, &ServiceStatus);           //服务状态重新设置后一定要调用此函数,以便通知controlhandler响应,就像发消息
 while(ServiceStatus.dwCurrentState == SERVICE_RUNNING)     //服务在运行时进行的操作,这里可以根据个人所需添加代码
 {
  int result = WriteToLog("monitoring running!");
  if(result == -1)
   return;
  Sleep(SLEEP_TIME);
 }
}

在 ServiceMain 中,检查 InitService 函数的返回值。如果初始化有错(因为有可能写日志文件失败),则将服务状态置为终止并退出 ServiceMain:

int InitService()
{
 int result;
 result = WriteToLog("monitoring started!");
 return result;
}

接着,启动工作循环。每五秒钟将结果写入日志文件。

循环一直到服务的状态为 SERVICE_RUNNING 或日志文件写入出错为止。状态可能在 ControlHandler 函数响应 SCM 控制请求时修改。

第三步:处理控制请求

在第二步中,你用 ServiceMain 函数注册了控制处理器函数。控制处理器与处理各种 Windows 消息的窗口回调函数非常类似。它检查 SCM 发送了什么请求并采取相应行动。

每次你调用 SetServiceStatus 函数的时候,必须指定服务接收 STOP 和 SHUTDOWN 请求。

STOP 请求是 SCM 终止服务的时候发送的。例如,如果用户在“服务”控制面板中手动终止服务。SHUTDOWN 请求是关闭机器时,由 SCM 发送给所有运行中服务的请求。两种情况的处理方式相同:

写日志文件,监视停止;

向 SCM 报告 SERVICE_STOPPED 状态;

由于 ServiceStatus 结构对于整个程序而言为全局量,ServiceStatus 中的工作循环在当前状态改变或服务终止后停止。其它的控制请求如:PAUSE 和 CONTINUE 在本文的例子没有处理。

控制处理器函数必须报告服务状态,即便 SCM 每次发送控制请求的时候状态保持相同。因此,不管响应什么请求,都要调用 SetServiceStatus。

void ControlHandler(DWORD request)
{
 switch(request)
 {
 case SERVICE_CONTROL_STOP:
 case SERVICE_CONTROL_SHUTDOWN:
            ServiceStatus.dwWin32ExitCode = 0;
            ServiceStatus.dwCurrentState = SERVICE_STOPPED;
            WriteToLog("monitoring stopped!\n");

            break;
 }
}

程序编好了,将之编译成 exe 文件。本文例子创建的文件叫 MemoryStatus.exe,将它拷贝到 C:\MyServices 文件夹。为了在机器上安装这个服务,需要用 SC.EXE 可执行文件,它是 Win32 Platform SDK 中附带的一个工具。(译者注:Visaul Studio .NET 2003 IDE 环境中也有这个工具,具体存放位置在:C:\Program Files\Microsoft Visual Studio .NET 2003\Common7\Tools\Bin\winnt)。使用这个实用工具可以安装和移除服务。其它控制操作将通过服务控制面板来完成。以下是用命令行安装 MemoryStatus 服务的方法:

1. sc create MemoryStatus binpath= c:\MyServices\MemoryStatus.exe

发出此创建命令。指定服务名和二进制文件的路径(注意 binpath= 和路径之间的那个空格)。安装成功后,便可以用服务控制面板来控制这个服务(参见图一)。用控制面板的工具栏启动和终止这个服务。

MemoryStatus 的启动类型是手动,也就是说根据需要来启动这个服务。右键单击该服务,然后选择上下文菜单中的“属性”菜单项,此时显示该服务的属性窗口。在这里可以修改启动类型以及其它设置。你还可以从“常规”标签中启动/停止服务。以下是从系统中移除服务的方法:

1. sc delete MemoryStatus

指定 “delete” 选项和服务名。此服务将被标记为删除,下次西通重启后,该服务将被完全移除。

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值