你好,欢迎来到电脑编程技巧与维护杂志社! 杂志社简介广告服务读者反馈编程社区  
合订本订阅
 
 
您的位置:杂志经典 / 图形图象处理与游戏编程
6.4 Windows服务编写综述(上)
 

一、服务介绍

几乎所有的操作系统在启动的时候都会启动一些不需要与用户交互的进程,这些进程在Windows中就被称作服务。它通常用于实现客户/服务器模式中的服务器方,如常见的Web服务IIS,当操作系统在启动后它就自动被运行,不管是否有人登录到系统只要系统开启它就能得到运行。

服务程序、服务控制程序(SCPservice control program)和服务控制管理器(SCMservice control manager)组成了Windows服务。我们可以通过服务控制程序操纵服务控制管理器来配置、启动、暂停、停止服务程序。其中服务程序和服务控制程序可以由自己来编写扩展,而服务控制管理器(\windows\system32\servics.exe)则是操作系统内置的一个部件。首先来了解一下服务控制管理器的工作情况,然后介绍服务程序的编写和服务控制时所涉及API的使用。

二、控制管理器

SCM本身也是一个服务程序(\windows\system32\servics.exe),作为Windows的后台服务运行的。Winlogon在系统引导的早期会将SCM启动起来。SCM的服务入口函数首先创建一个初始化为无信号的同步事件对象(SvcCtrlEvent_A3752DX);接下来,它开始建立一个内部服务数据库,这个数据库要按事先规定好的一个顺序列出所有服务组,并记录与服务相关的详细信息;当这个数据库建立完成时SCM就开始按顺序启动那些启动方式为自动的服务,如果有服务要运行于指定用户账户中时还要调用LSASS,如果服务启动失败则会被放入一个名为ScFailedDrivers的列表中。当这些工作都完成后,SCM将同步事件对象SvcCtrlEvent_A3752DX置为有信号状态;并做好系统停机的准备。

当系统要关机时会向Windows子系统进程Csrss发送一个消息,以便调用Csrss的停机例程。Csrss会对所有活动的进程循环通知系统正在停机。对于除SCM以外的每一个系统进程如果没有返回退出的响应Csrss 都会等待由HKEY_USER\.DEFAULT\Control Panel\Desktop\WaitToKillAppTimeout指定的毫秒数,知通下一个进程结束。当遇到SCM时也会通知SCM进程系统正在停机,但不同的是会使用一个专用的超时间隔值(SCM在系统初始化时要向Csrss登记,于是Csrss就将SCM的进程ID保存了下来Csrss也就是通过个ID来识别SCM)。这个专用的超时间隔位于HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\WaitToKillServiceTimeout中,有趣的是默认值也是20秒。这在段时间里SCM要通知所有在初始化时(服务程序自身初始化)请求SCM通知自己系统停机的服务,SCM有一个停机处理器负责这项工作。通知下达后SCM就等待服务退出,服务在接收到停机消息后会返回给SCM一个等待时间,SCM跟踪所有服务返回等待时间找出其中的最大值,当这个最大值达到后,如果有一个服务或多个服务又告诉SCM它们正在处理停机工作,那么SCM还会循环等待下去,但Csrss也再等待SCMCsrss,等待超时后,会继续后面的停机工作,最终完成停机。

这就要求服务程序要在Csrss等待SCM的这段时内完成自己的停机处理工作,否则服务就没机会在系统停机前完成自己的关闭工作了。

三、服务程序

Windows服务程序其实并不神秘,它只是遵循特定规则编写的一个程序。只要遵循这个特定的规则与服务控制管理器正确的交互,就可实现我们的服务程序。而我们只要能实现一个简单的服务程序,设计一个能处理复杂业务的服务也并非难事,因为从结构上看两者并没有太大的区别。只要遵循与SCM交互的规则,设计服务程序与设计普通的应用程序几乎没什么区别。

1. 程序结构概要

    服务程序的与普通应用程序一样也需要一个主函数作为程序的入口,与之不同的是作为一个服务程序它需要在主函数中立即调用StartServiceCtrlDispatcher来注册一个服务的入口函数ServerMain(DWORD argc,LPTSTR *argv),当然这个名字可自由命名。StartServiceCtrlDispatcher函数的原型是:

 BOOL StartServiceCtrlDispatcher(

  LPSERVICE_TABLE_ENTRY lpServiceStartTable 

);

它的参数是一个指向SERVICE_TABLE_ENTRY的指针; SERVICE_TABLE_ENTRY结构有两个域;第一个域存储服务的内部名称,第二个域是服务入口函数的指针。这个函数完成后,SCM就在可以服务启动的时候调用服务的入口函数。

管理员在服务管理器启动一个服务,SCM就会在一个单独的线程中调用服务注册的入口函数。这时在服务的这个入口函数中必须调用RegisterServiceCtrlHandler完成Handler函数的注册,这个函数用来接收和处理SCM的控制消息,控制消息宏定义如下表所示。下表列出Hander要处理的控制消息和RegisterServiceCtrlHandler的函数原型。

控制消息宏定义

说明

SERVICE_CONTROL_STOP

要服务停止

SERVICE_CONTROL_PAUSE

要服务暂停

SERVICE_CONTROL_CONTINUE

要服务继续

SERVICE_CONTROL_INTERROGATE

要服务马上报告它的状态

SERVICE_CONTROL_SHUTDOWN

告诉服务即将关机

 

VOID WINAPI Handler(

  DWORD fdwControl   // 请求控制消息代码

);

控制消息宏定义

说明

SERVICE_CONTROL_STOP

要服务停止

SERVICE_CONTROL_PAUSE

要服务暂停

SERVICE_CONTROL_CONTINUE

要服务继续

SERVICE_CONTROL_INTERROGATE

要服务马上报告它的状态

SERVICE_CONTROL_SHUTDOWN

告诉服务即将关机

SERVICE_STATUS_HANDLE RegisterServiceCtrlHandler(

  LPCTSTR lpServiceName,             // 服务的内部名称

  LPHANDLER_FUNCTION lpHandlerProc   // Handler函数的地址

);

    RegisterServiceCtrlHandler调用完成后我们就可以开始业务处理的初始化工作。初始化完成后向SCM报告服务开始运行(SERVICE_RUNNING)的消息。如果

erverMain(DWORD argc,LPTSTR *argv)函数退出服务也就停止了。下面让我总结一下实现服务程序的步骤:在main()调用StartServiceCtrlDispatcher来注册一个服务的入口函数;在ServerMain(DWORD argc,LPTSTR *argv)中调用RegisterServiceCtrlHandler注册Handler函数;完成业务处理程序的初始化工作,如果初始化时间较长要实时向SCM报告当前正在启动;初始化完毕,报告服务正在运行;开始业务处理工作。

2.程序实例分析

1main()函数

int main(int argc, char* argv[])

{

    SERVICE_TABLE_ENTRY serviceTable[]=

    {

        {

            SERVICE_NAME,

            (LPSERVICE_MAIN_FUNCTION)ServiceMain

        }

        {

            NULL,NULL

        }

    };

    BOOL success;

    success = StartServiceCtrlDispatcher(serviceTable);

    if (!success)

    {

        ErrorHandler("In StartServiceCtrlDispatcher",GetLastError());

    }

    return 0;

}

StartServiceCtrlDispatcher的参数必须是一个以NULL结尾的数组指针,可以在一个程序文件中注册多个服务实例,只在把所要注册的服务名和服务入口函数地址写到数组中即可。在调用CreateService创建服务时要把dwServiceType 参数设为共享进程(SERVICE_WIN32_SHARE_PROCESS);不过当要创建独立进程的服务时(dwServiceType 参数为SERVICE_WIN32_OWN_PROCESS时)在这里就只能注册一个服务实例。

2)服务入口函数ServerMain()

VOID ServiceMain(DWORD argc,LPTSTR *argv)

{

    BOOL success;

    StatusHandler=

        RegisterServiceCtrlHandler(SERVICE_NAME,(LPHANDLER_FUNCTION)Handler);

    if (!serviceStatusHandler)

    {

        return;

    }

    success = ReportStatus (SERVICE_START_PENDING,

                            NO_ERROR,0,1,5000);

    if (!success)

    {

        return;

    }

    endEvent = CreateEvent(0,TRUE,FALSE,0);

    if (!endEvent)

    {

        return;

    }

    success = ReportStatus (SERVICE_START_PENDING,

                            NO_ERROR,0,2,5000);

    if (!success)

    {

        return;

    }

    //init parameter start

    RecvParam(argc,argv);

    //init parameter end

    success = ReportStatus (SERVICE_START_PENDING,

                            NO_ERROR,0,3,5000);

    if (!success)

    {

        return;

    }

    success = InitService();

    if (!success)

    {

        return;

    }

    success = ReportStatus (SERVICE_RUNNING,NO_ERROR,0,0,0);

    if (!success)

    {

        return;

    }

    WaitForSingleObject(endEvent,INFINITE);

}

RegisterServiceCtrlHandler完成Handler函数的注册,它的第一个参数是调用CreateService创建服务时lpServiceName指向的名服务名称,每二个参数是Handler函数的地址;函数名可以自由命名。ReportStatus是向SCM报告服务当前状态的一个自定义函数。它内部调用SetServiceStatus向SCM报告服务的当状态,此函数有两个参数第一个就是RegisterServiceCtrlHandler完成时返回的SERVICE_STATUS_HANDLE,第二个参数是一个SERVICE_STATUS变量的指针,它指示了服务当前的状态信息;当注册完Handler函数后向SCM报告一下自己当前的状态(正在启动)。接着创建endEvent事件对像,它是当收到SCM的退出控制代码时通知服务主函数退出的,大家可看ServiceMain的最后一句。下面又是向SCM报告自己正在启动,当初始化所花费的时间非常短时这样做并不是必须的,但如果很长就必须这样做。RecvParam(argc,argv)使用了ServiceMain函数的两个参数,大家可以看出ServiceMainmain有着一样的形参;说明ServiceMainmain一样可以接收配置参数,稍后会在服务控制程序的编写中给大家介绍如何给服务配置参数。InitService()完成业务初始化工作并开始业务处理。最后报告服务启动完成,等待endEvent事件退出服务。下面再来看一下SCM控制消息的处理。

3SCM控制消息处理(Handler函数)

VOID Handler(DWORD controlCode)

{

    DWORD currentState = 0;

    BOOL success;

    switch(controlCode)

    {

    case SERVICE_CONTROL_STOP:

        success=ReportStatus(SERVICE_STOP_PENDING,NO_ERROR,0,1,5000);

        CloseTask();

        success=ReportStatus(SERVICE_STOPPED,NO_ERROR,0,0,0);

        return;

    case SERVICE_CONTROL_PAUSE:

        if (runningService&&!pauseService)

        {

            success=ReportStatus(SERVICE_PAUSE_PENDING,NO_ERROR,0,1,1000);

            pauseService=TRUE;

            ServicePause();

            currentState=SERVICE_PAUSED;

        }

        break;

    case SERVICE_CONTROL_CONTINUE:

        if (runningService&&pauseService)

        {

            success = ReportStatus(SERVICE_CONTINUE_PENDING,

                                    NO_ERROR,0,1,1000);

            pauseService = FALSE;

            ServiceContinue();

            currentState=SERVICE_RUNNING;

        }

        break;

    case SERVICE_CONTROL_INTERROGATE://检索更新状态的时

        break;

    case SERVICE_CONTROL_SHUTDOWN://告诉服务即将关机

        success=ReportStatus(SERVICE_STOP_PENDING,NO_ERROR,0,1,5000);

        CloseTask();

        return;

    default:

        break;

    }

    ReportStatus(currentState,NO_ERROR,0,0,0);

}

Handler只有一个参数就是SCM传来的控制消息代码;这里处理的了停止,暂停,继续,更新,关机五个控制消息。但并不是这五个消息SCM都会向服务发送,要在向服务报告状时向SCM报告自己可以响应的控制消息,只要设置SERVICE_STATUS结构中的dwControlsAccepted域即可,它对应的值有:SERVICE_ACCEPT_STOPSERVICE_ACCEPT_PAUSE_CONTINUESERVICE_ACCEPT_SHUTDOWN,当要设置多个时只要把宏相或(|)传给dwControlsAccepted域即可。在响应SCM控制消息时也要注意及时报告服务当前的状态信息,否则SCM会认为服务响应超时出错了。

4)服务的安装与卸载

    服务程序编写完成并编译通过后,还要安装注册到操作系统中,这样它才会出现在管理工具→服务那个管理器里面。API提供了一个函数来实现注册服务的功能; SC_HANDLE CreateService(

  SC_HANDLE hSCManager,  // 服务控制管理器的句柄

  LPCTSTR lpServiceName, // 指向服务的内部名称

  LPCTSTR lpServiceName,, // 指向服务的显示名称

  DWORD dwDesiredAccess, // 服务的访问类型

  DWORD dwServiceType,   // 服务的类型

  DWORD dwStartType,     // 服务的启动方式(自动,手动,禁用)

  DWORD dwErrorControl,  // 错误控制方式

  LPCTSTR lpBinaryPathName,  // 服务程序的路径

  LPCTSTR lpLoadOrderGroup,  // 服务组的名称

  LPDWORD lpdwTagId,     // 服务的标签号

  LPCTSTR lpDependencies,  // 服务依赖的服务或组名

  LPCTSTR lpServiceStartName, // 服务的启动帐户

  LPCTSTR lpPassword       // 服务启动帐户的密码

);

hSCManager:这是函数的第一个参数-SCM的句柄。它要调用OpenSCManager来获得,稍后我们会讲它怎么调用方法。

lpServiceNamelpServiceName:分别的服务的名称和服务的显示名称,服务是显示名称就是服务管理器中看到的那个服务名。则是服务在SCM中注册的名称,比如调用OpenService打开服务时就会用到它。

dwDesiredAccess:标出服务同意请求的访问,可以是下面任意任值:

SERVICE_ALL_ACCESS

SERVICE_CHANGE_CONFIG

SERVICE_ENUMERATE_DEPENDENTS

SERVICE_INTERROGATE

SERVICE_PAUSE_CONTINUE

SERVICE_QUERY_CONFIG

SERVICE_QUERY_STATUS

SERVICE_START

SERVICE_STOP

SERVICE_USER_DEFINED_CONTROL

可以指定一个或多个,如果有多个的话要用或符号(|)联结起来。

dwServiceType:注册服务的类型,它必须是下面的值:SERVICE_WIN32_OWN_PROCESSSERVICE_WIN32_SHARE_PROCESSSERVICE_KERNEL_DRIVERSERVICE_FILE_SYSTEM_DRIVER。如果指定的是SERVICE_WIN32_OWN_PROCESS类型的服务还可以加上SERVICE_WIN32_OWN_PROCESS(允许用户桌面交互)。这里介绍的服务只能注册为SERVICE_WIN32_OWN_PROCESSSERVICE_WIN32_SHARE_PROCESS;另两种类型是驱动级的服务用的,有兴趣者可查看相关资料。

dwStartType:服务的启动类型SERVICE_BOOT_STARTSERVICE_SYSTEM_STARTSERVICE_AUTO_STARTSERVICE_DEMAND_STARTSERVICE_DISABLED。前两种类型仅对驱动程序用效,所在这里所说的这类服务能后三种(自动,手动,禁用)。

dwErrorControl:服务的错误控制标记

SERVICE_ERROR_IGNORE:忽略所有错误

SERVICE_ERROR_NORMAL:正常报告服务返回的错误

SERVICE_ERROR_SEVERE:当服务返回错误出现时,如果最后已知好控制集尚未使用,则重新引导进入最后已知好控制集,否则重新引导。

SERVICE_ERROR_CRITICAL:当服务返回错误出现时,如果最后已知好控制集尚未使用,则重新引导进入最后已知好控制集,否则蓝屏崩溃。

lpBinaryPathName:服务程序的文件路径

lpLoadOrderGroup:服务所属的组

lpdwTagId:在组中的唯一标识

lpDependencies:服务所依赖的其他组和服务

lpServiceStartNamelpPassword:服务由哪个用户启动,也即服务运行在哪个用户权限下,分别指定用户名和密码.

    下面给出OpenSCManagerCloseServiceHandle两个重要函数的原型:

SC_HANDLE OpenSCManager(

  LPCTSTR lpMachineName,  // 机器名,打开本机的SCM时可为NULL

  LPCTSTR lpDatabaseName,  // 指向SCM数据库的名字可为NULL

  DWORD dwDesiredAccess   // 访问权限类型如:SC_MANAGER_ALL_ACCESS

);

BOOL CloseServiceHandle(

  SC_HANDLE hSCObject   // 服务控制句柄

);

    这里列出一段注册服务的代码供参考:

SC_HANDLE newService,scm;

BOOL success = FALSE;

SERVICE_STATUS status;

scm = OpenSCManager(NULL,NULL,

SC_MANAGER_ENUMERATE_SERVICE|SC_MANAGER_CREATE_SERVICE);

    if (!scm){

        OUT_DEBUG("OpenSCManager ERROR!");

        return false;

    }

newService = CreateService(scm,pszServiceName,pszDisplayName,

        SERVICE_ALL_ACCESS|SERVICE_STOP,SERVICE_WIN32_OWN_PROCESS,

        SERVICE_AUTO_START,SERVICE_ERROR_NORMAL,pszServicePath,

        0,0,0,0,0);

    if (!newService){

        OUT_DEBUG("CreateService ERROR!");

        CloseServiceHandle(scm);

        return false;

    }

CloseServiceHandle(newService);

CloseServiceHandle(scm);

return true;

删除服务时调用DeleteService;它只有一个参数(服务句柄)。可分四步完成打开SCM句柄;打开要删除的服务;检查当前服务的状态确保服务已经停止;删除服务并关闭所有打开的句柄。下面是一段删除服务的核心代码:

SC_HANDLE Service,scm;

    SERVICE_STATUS status;

    BOOL success;

    if (pszServiceName==NULL)

    {

        return false;

    }

    scm = OpenSCManager(NULL,NULL,SC_MANAGER_ENUMERATE_SERVICE);

    if (!scm){

  推荐精品文章

·2024年9月目录 
·2024年8月目录 
·2024年7月目录 
·2024年6月目录 
·2024年5月目录 
·2024年4月目录 
·2024年3月目录 
·2024年2月目录 
·2024年1月目录
·2023年12月目录
·2023年11月目录
·2023年10月目录
·2023年9月目录 
·2023年8月目录 

  联系方式
TEL:010-82561037
Fax: 010-82561614
QQ: 100164630
Mail:gaojian@comprg.com.cn

  友情链接
 
Copyright 2001-2010, www.comprg.com.cn, All Rights Reserved
京ICP备14022230号-1,电话/传真:010-82561037 82561614 ,Mail:gaojian@comprg.com.cn
地址:北京市海淀区远大路20号宝蓝大厦E座704,邮编:100089