Windows Service服务程序的原理及实现(0)服务主函数 & 控制处理函数

来源:互联网 发布:数据挖掘工具主要有 编辑:程序博客网 时间:2024/06/05 19:57

摘要: 何为服务?Windows 会话中可长时间运行的可执行应用程序。这些服务可以在计算机启动时自动启动,可以暂停和重新启动而且不显示任何用户界面。这种服务非常适合在服务器上使用,或任何时候,为了不影响在同一台计算机上工作的其他用户,需要长时间运行功能时使用。一个服务首先是一个Win32可执行程序,如果要写一个功能完备且强大的服务,需要熟悉动态连接库(DLL)、结构异常处理、内存映射文件、虚拟内存、设备I/O、线程及其同步、Unicode以及其他的由WinAPI函数提供的应用接口。


Windows Service概要

实现一个服务程序,主要需要完成两个部分:

  • 服务运行函数(相当于Thread中的run):服务运行函数,一般被呼为ServiceMain。但是它的名称与ThreadProc一样,其命名没有特殊要求,只是参数接口和调用类型必须与要求一致:
void WINAPI serviceImpl(DWORD argc, LPCWSTR* argv);
  • 服务控制函数:Windows中提供了强大的命令行工具sc.exe,用于对服务进行管理。可以实现对服务的所有控制,包括安装、删除、配置、启动、停止等。为此,我们需要在自己的服务程序中对这些命令做出响应。也就是需要实现一个服务控制函数,当接收到不同命令的时候,执行不同的操作,具体操作自行定义。

一个服务包括若干属性,包括服务名称、显示名称、服务类型、描述、可执行文件路径、启动类型、服务状态、启动参数、依存关系等。

  1. 服务名称:唯一标识一个服务;
  2. 显示名称:在系统服务管理界面和sc.exe中显示名称;
  3. 服务类型:包括SERVICE_FILE_SYSTEM_DRIVER(文件系统驱动服务)、SERVICE_KERNEL_DRIVER(驱动服务)——(2种)运行于内核态。
    SERVICE_WIN32_OWN_PROCESS(独立进程服务)、SERVICE_WIN32_SHARE_PROCESS(共享进程服务)——(2种)运行于用户态。

本文主要讲解Windows服务运行函数开发。Windows服务与多线程极为类似,服务运行函数一般称为ServiceMain,与线程中的ThreadProc(线程执行体)一样,其函数名并没有特殊的要求,只要求其参数接口和返回值调用类型与要求一致即可,这里所说的ServiceMain(服务运行函数)也可以理解为Thread中的run,虽然不是很恰当。服务程序通过调用StartServiceCtrlDispatcher API函数设置服务主函数。

其中,StartServiceCtrlDispatcher 函数原型如下:

StartServiceCtrlDispatcherW(    _In_ CONST  SERVICE_TABLE_ENTRYW    *lpServiceStartTable    );

以上函数中只有一个结构体,SERVICE_TABLE_ENTRYW的原型也给出如下:

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

lpServiceName为服务名称;lpServiceProc为指向ServiceMain的函数指针。只要将函数的指针复制给lpServiceProc,再调用StartServiceCtrlDispatcher ,这个函数就称为了服务主函数。

没有服务控制函数,光有服务执行体——服务是跑不起来的。
服务控制函数可以用Windows提供的sc.exe 程序,在命令提示符的环境下,让服务运行体(ServiceMain)跑起来。这里使用另外一种方式,使用Windows API来控制服务执行体。所谓服务——其实也就是一个EXE可执行程序。

Service一般开发流程

一般服务开发流程:

  1. 先将服务运行体(ServiceMain)生成,得到一个XXX.exe。(本文讲解内容)
  2. 接着,将该exe放在电脑的某一个位置,再运行服务控制函数,服务就跑起来了。
  3. 这里需要注意一点,开发服务程序,需要以管理员方式运行Visual Studio 2013等版本。(或者在DOS下操作时,就是不编写服务控制程序,而采用Windows提供的sc.exe管理服务,也同样需要管理员权限。)

在服务程序开发过程中,有一个很重要的结构体SERVICE_STATUS 其具体定义如下:

typedef struct _SERVICE_STATUS {    DWORD   dwServiceType;                   //可执行文件类型    DWORD   dwCurrentState;                  //服务当前状态    DWORD   dwControlsAccepted;              //指明服务接受何种控制    DWORD   dwWin32ExitCode;                 //错误报告码(Win32已义错误)    DWORD   dwServiceSpecificExitCode;       //自定义错误码    DWORD   dwCheckPoint;                    //处于进程的哪一步    DWORD   dwWaitHint;                      //超时时间} SERVICE_STATUS, *LPSERVICE_STATUS;
  1. dwServiceType指明服务可执行文件的类型。如果你的可执行文件中只有一个单独的服务,就把这个成员设置成SERVICE_WIN32_OWN_PROCESS;如果拥有多个服务的话,就设置成SERVICE_WIN32_SHARE_PROCESS。除了这两个标志之外,如果你的服务需要和桌面发生交互(当然不推荐这样做),就要用“OR”运算符附加上SERVICE_INTERACTIVE_PROCESS。这个成员的值在你的服务的生存期内绝对不应该改变。
  2. dwCurrentState是这个结构中最重要的成员,它将告诉SCM你的服务的现行状态。为了报告服务仍在初始化,应该把这个成员设置成SERVICE_START_PENDING。在以后具体讲述CtrlHandler函数的时候具体解释其它可能的值。
  3. dwControlsAccepted指明服务愿意接受什么样的控制通知。如果你允许一个SCP去暂停/继续服务,就把它设成SERVICE_ACCEPT_PAUSE_CONTINUE。很多服务不支持暂停或继续,就必须自己决定在服务中它是否可用。如果你允许一个SCP去停止服务,就要设置它为SERVICE_ACCEPT_STOP。如果服务要在操作系统关闭的时候得到通知,设置它为SERVICE_ACCEPT_SHUTDOWN可以收到预期的结果。这些标志可以用“OR”运算符组合。
  4. dwWin32ExitCodedwServiceSpecificExitCode是允许服务报告错误的关键,如果希望服务去报告一个Win32错误代码(预定义在WinError.h中),它就设置dwWin32ExitCode为需要的代码。一个服务也可以报告它本身特有的、没有映射到一个预定义的Win32错误代码中的错误。为了这一点,要把dwWin32ExitCode设置为ERROR_SERVICE_SPECIFIC_ERROR,然后还要设置成员dwServiceSpecificExitCode为服务特有的错误代码。当服务运行正常,没有错误可以报告的时候,就设置成员dwWin32ExitCode为NO_ERROR。
  5. 最后的两个成员dwCheckPointdwWaitHint是一个服务用来报告它当前的事件进展情况的。当成员dwCurrentState被设置成SERVICE_START_PENDING的时候,应该把dwCheckPoint设成0,dwWaitHint设成一个经过多次尝试后确定比较合适的数,这样服务才能高效运行。一旦服务被完全初始化,就应该重新初始化SERVICE_STATUS结构的成员,更改dwCurrentState为SERVICE_RUNNING,然后把dwCheckPoint和dwWaitHint都改为0。
    • dwCheckPoint成员的存在对用户是有益的,它允许一个服务报告它处于进程的哪一步。每一次调用SetServiceStatus时,可以增加它到一个能指明服务已经执行到哪一步的数字,它可以帮助用户决定多长时间报告一次服务的进展情况。
    • 如果决定要报告服务的初始化进程的每一步,就应该设置dwWaitHint为你认为到达下一步所需的毫秒数,而不是服务完成它的进程所需的毫秒数。

Service 实战

对于Windows服务开发想了解更多可以阅读《Windows服务编写原理及探讨》一文,想实战开发可以参阅《Windows API—函数、接口、编程实例》一书。以下是一个完整的服务运行体(ServiceMain)实战。如果想直接讨论《Q群号:233688836》。在阅读本文的基础上,推荐阅读《Windows Service服务程序(1)实现对服务的控制和管理》,将对Windows服务编程有个更加深入的了解。

win32service.h

#ifndef _WIN32_SERVICE_H#define _WIN32_SERVICE_H#include <Windows.h>extern SERVICE_STATUS g_ServiceStatus;extern SERVICE_STATUS_HANDLE g_ServiceStatusHandle;void WINAPI serviceImpl(DWORD argc, LPCWSTR* argv);void WINAPI serviceCtrlHandlerProc(DWORD opcode);#endif

win32service.cpp

#include "win32service.h"SERVICE_STATUS g_ServiceStatus;SERVICE_STATUS_HANDLE g_ServiceStatusHandle;/*功能:服务控制函数*/void WINAPI serviceCtrlHandlerProc(DWORD opcode){    switch (opcode){    case SERVICE_CONTROL_PAUSE:        g_ServiceStatus.dwCurrentState = SERVICE_PAUSED; break;    case SERVICE_CONTROL_CONTINUE:        g_ServiceStatus.dwCurrentState = SERVICE_RUNNING; break;    case SERVICE_CONTROL_STOP:        g_ServiceStatus.dwCurrentState = SERVICE_STOPPED; break;    case SERVICE_CONTROL_INTERROGATE:/*[ɪn'terə.ɡeɪt] v.审问;询问*/        MessageBeep(MB_OK);    default:        break;    }    if (!SetServiceStatus(g_ServiceStatusHandle, &g_ServiceStatus)){        //set service status error.    }}/*功能:服务运行函数*/void WINAPI serviceImpl(DWORD argc, LPCWSTR* argv){    g_ServiceStatus.dwServiceType = SERVICE_WIN32;    g_ServiceStatus.dwCurrentState = SERVICE_START_PENDING;    g_ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;    g_ServiceStatus.dwWin32ExitCode = 0;    g_ServiceStatus.dwCheckPoint = 0;    g_ServiceStatus.dwWaitHint = 0;    g_ServiceStatusHandle = RegisterServiceCtrlHandlerA("demo_srv", serviceCtrlHandlerProc);    if (g_ServiceStatusHandle == (SERVICE_STATUS_HANDLE)0){        //register service ctrl handle error.        return;    }    g_ServiceStatus.dwCurrentState = SERVICE_RUNNING;    if (!SetServiceStatus(g_ServiceStatusHandle, &g_ServiceStatus)){        //set service status error.        return;    }#if 0    while (flag){        //run service    }#endif}

main.cpp

#include "win32service.h"int main(void){    SERVICE_TABLE_ENTRYA DispatchTable[] = {        { "demo_srv", (LPSERVICE_MAIN_FUNCTIONA)serviceImpl },        { NULL, NULL }    };    if (!StartServiceCtrlDispatcherA(DispatchTable)){        //start service ctrl dispather error.    }    return 0;}
0 0