如何用C语言编写Windows服务程序的五个步骤,C语言编写Windows服务程序
分类:面向对象

Windows 服务被设计用于需要在后台运行的应用程序以及实现没有用户交互的任务。为了学习这种控制台应用程序的基础知识,C(不是C++)是最佳选择。 <!--

如何用C语言编写Windows服务程序的五个步骤

   Windows 服务被设计用于需要在后台运行的应用程序以及实现没有用户交互的任务。为了学习这种控制台应用程序的基础知识,C(不是C++)是最佳选择。本文将建立并实现一个简单的服务程序,其功能是查询系统中可用物理内存数量,然后将结果写入一个文本文件。最后,你可以用所学知识编写自己的Windows 服务。

 

关键字:C语言 Windows 服务程序 服务 </div> -->

前一段时间我写了一篇通过写服务的形式来达到一些监视程序运行的目的的文章,至于如何在windows下写服务我没有详细介绍,今天就让我们一起看看如何来写服务程序。

当初他写第一个NT 服务时,他到 MSDN 上找例子。在那里他找到了一篇 Nigel Thompson 写的文章:“Creating a Simple Win32 Service in C++”,这篇文章附带一个 C++ 例子。虽然这篇文章很好地解释了服务的开发过程,但是,他仍然感觉缺少他需要的重要信息。他想理解通过什么框架,调用什么函数,以及何时调用,但 C++ 在这方面没有让他轻松多少。面向对象的方法固然方便,但由于用类对底层 Win32 函数调用进行了封装,它不利于学习服务程序的基本知识。这就是为什么他觉得 C 更加适合于编写初级服务程序或者实现简单后台任务的服务。在你对服务程序有了充分透彻的理解之后,用 C++ 编写才能游刃有余。当他离开原来的工作岗位,不得不向另一个人转移他的知识的时候,利用他用 C 所写的例子就非常容易解释 NT 服务之所以然。

这两天想学习关于如何编写windows服务程序的知识,就上网查了些资料。看了一篇文章《用C 语言编写Windows 服务程序的五个步骤》。但可能由于译者的疏忽,忘记了将关键代码放入该文档,导致初学者可能会看不懂。所以又查阅了相关资料。自己完成了《用C 语言编写Windows 服务程序的五个步骤》中的样例程序。

     Windows 服务被设计用于需要在后台运行的应用程序以及实现没有用户交互的任务。为了学习这种控制台应用程序的基础知识,C(不是C++)是最佳选择。

        Windows 服务被设计用于需要在后台运行的应用程序以及实现没有用户交互的任务。为了学习这种控制台应用程序的基础知识,C(不是C++)是最佳选择。本文将建立并实现一个简单的服务程序,其功能是查询系统中可用物理内存数量,然后将结果写入一个文本文件。最后,你可以用所学知识编写自己的 Windows 服务。
  当初我写第一个 NT 服务时,我到 MSDN 上找例子。在那里我找到了一篇 Nigel Thompson 写的文章:“Creating a Simple Win32 Service in C++”,这篇文章附带一个 C++ 例子。虽然这篇文章很好地解释了服务的开发过程,但是,我仍然感觉缺少我需要的重要信息。我想理解通过什么框架,调用什么函数,以及何时调用,但 C++ 在这方面没有让我轻松多少。面向对象的方法固然方便,但由于用类对底层 Win32 函数调用进行了封装,它不利于学习服务程序的基本知识。这就是为什么我觉得 C 更加适合于编写初级服务程序或者实现简单后台任务的服务。在你对服务程序有了充分透彻的理解之后,用 C++ 编写才能游刃有余。当我离开原来的工作岗位,不得不向另一个人转移我的知识的时候,利用我用 C 所写的例子就非常容易解释 NT 服务之所以然。
  服务是一个运行在后台并实现勿需用户交互的任务的控制台程序。Windows NT/2000/XP 操作系统提供为服务程序提供专门的支持。人们可以用服务控制面板来配置安装好的服务程序,也就是 Windows 2000/XP 控制面板|管理工具中的“服务”(或在“开始”|“运行”对话框中输入 services.msc /s——译者注)。可以将服务配置成操作系统启动时自动启动,这样你就不必每次再重启系统后还要手动启动服务。
  本文将首先解释如何创建一个定期查询可用物理内存并将结果写入某个文本文件的服务。然后指导你完成生成,安装和实现服务的整个过程。

服务是一个运行在后台并实现勿需用户交互的任务的控制台程序。Windows NT/2000/XP 操作系统提供为服务程序提供专门的支持。人们可以用服务控制面板来配置安装好的服务程序,也就是 Windows 2000/XP 控制面板|管理工具中的“服务”(或在“开始”|“运行”对话框中输入 services.msc /s——译者注)。可以将服务配置成操作系统启动时自动启动,这样你就不必每次再重启系统后还要手动启动服务。

 

    本文将建立并实现一个简单的服务程序,其功能是查询系统中可用物理内存数量,然后将结果写入一个文本文件。最后,你可以用所学知识编写自己的 Windows 服务。

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

本文将首先解释如何创建一个定期查询可用物理内存并将结果写入某个文本文件的服务。然后指导你完成生成,安装和实现服务的整个过程。

在本文中我希望能给初学者一些帮助,大致讲一下编写windows服务程序需要的知识。

    当初我写第一个 NT 服务时,我到 MSDN 上找例子。在那里我找到了一篇 Nigel Thompson 写的文章:“Creating a Simple Win32 Service in C++”,这篇文章附带一个 C++ 例子。虽然这篇文章很好地解释了服务的开发过程,但是,我仍然感觉缺少我需要的重要信息。我想理解通过什么框架,调用什么函数,以及何时调用,但 C++ 在这方面没有让我轻松多少。面向对象的方法固然方便,但由于用类对底层 Win32 函数调用进行了封装,它不利于学习服务程序的基本知识。这就是为什么我觉得 C 更加适合于编写初级服务程序或者实现简单后台任务的服务。在你对服务程序有了充分透彻的理解之后,用 C++ 编写才能游刃有余。当我离开原来的工作岗位,不得不向另一个人转移我的知识的时候,利用我用 C 所写的例子就非常容易解释 NT 服务之所以然。

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

第一步:主函数和全局定义
  
首先,包含所需的头文件。例子要调用 Win32 函数(windows.h)和磁盘文件写入(stdio.h):

 

    服务是一个运行在后台并实现勿需用户交互的任务的控制台程序。Windows NT/2000/XP 操作系统提供为服务程序提供专门的支持。人们可以用服务控制面板来配置安装好的服务程序,也就是 Windows 2000/XP 控制面板|管理工具中的“服务”(或在“开始”|“运行”对话框中输入 services.msc /s——译者注)。可以将服务配置成操作系统启动时自动启动,这样你就不必每次再重启系统后还要手动启动服务。

#include
#include
接着,定义两个常量:

  #include <windows.h>
#include <stdio.h>
  
接着,定义两个常量:

首先Microsoft Windows 服务(即,以前的NT 服务)使您能够创建在它们自己的Windows 会话中可长时间运行的可执行应用程序。这些服务可以在计算机启动时自动启动,可以暂停和重新启动而且不显示任何用户界面。这使服务非常适合在服务器上使用,或任何时候,为了不影响在同一台计算机上工作的其他用户,需要长时间运行功能时使用。还可以在不同于登录用户的特定用户帐户或默认计算机帐户的安全上下文中运行服务。

    本文将首先解释如何创建一个定期查询可用物理内存并将结果写入某个文本文件的服务。然后指导你完成生成,安装和实现服务的整个过程。

#define SLEEP_TIME 5000
#define LOGFILE "C:\MyServices\memstatus.txt"
SLEEP_TIME 指定两次连续查询可用内存之间的毫秒间隔。在第二步中编写服务工作循环的时候要使用该常量。
LOGFILE 定义日志文件的路径,你将会用 WriteToLog 函数将内存查询的结果输出到该文件,WriteToLog 函数定义如下:

  #define SLEEP_TIME 5000
#define LOGFILE "C:\MyServices\memstatus.txt"
  
SLEEP_TIME 指定两次连续查询可用内存之间的毫秒间隔。在第二步中编写服务工作循环的时候要使用该常量。

 

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

int WriteToLog(char* str)
{
    FILE* log;
    log = fopen(LOGFILE, "a+");
    if (log == NULL)
    return -1;
    fprintf(log, "%sn", str);
    fclose(log);
    return 0;
}
声明几个全局变量,以便在程序的多个函数之间共享它们值。此外,做一个函数的前向定义:

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

服务是有状态的,当我们使用windows自带的服务管理程序sc.exe查看服务状态时可以显示服务的当前状态,这个状态是由我们在程序代码中进行控制的。你最好在服务初始化的时候将服务设置为SERVICE_START_PENDING,当初始化完毕时设为SERVICE_RUNNING,这些状态是系统自定义的状态,可通过msdn查看其他状态。这个状态信息你会在sc.exe中看到。

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

SERVICE_STATUS ServiceStatus;
SERVICE_STATUS_HANDLE hStatus;

  int WriteToLog(char* str)
{
FILE* log;
log = fopen(LOGFILE, "a+");
if (log == NULL)
return -1;
fprintf(log, "%sn", str);
fclose(log);
return 0;
}
  
声明几个全局变量,以便在程序的多个函数之间共享它们值。此外,做一个函数的前向定义:

 

    #include

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

  SERVICE_STATUS ServiceStatus;
SERVICE_STATUS_HANDLE hStatus;

在编写windows服务程序过程中你需要关注的函数有:

    #include

void main()
{
    SERVICE_TABLE_ENTRY ServiceTable[2];
    ServiceTable[0].lpServiceName = "MemoryStatus";
    ServiceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;
   
    ServiceTable[1].lpServiceName = NULL;
    ServiceTable[1].lpServiceProc = NULL;

void ServiceMain(int argc, char** argv);
void ControlHandler(DWORD request);
int InitService();
  
现在,准备工作已经就绪,你可以开始编码了。服务程序控制台程序的一个子集。因此,开始你可以定义一个 main 函数,它是程序的入口点。对于服务程序来说,main 的代码令人惊讶地简短,因为它只创建分派表并启动控制分派机。
  
 void main()
{
SERVICE_TABLE_ENTRY ServiceTable[2];
ServiceTable[0].lpServiceName = "MemoryStatus";
ServiceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;

 

    接着,定义两个常量:

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

ServiceTable[1].lpServiceName = NULL;
ServiceTable[1].lpServiceProc = NULL;

1.首先是main函数,由于windows服务不需要界面,所以大部分程序为win32控制台应用程序,所以程序主函数为main 而不是WinMain()。在主函数要做的主要工作就是初始化一个SERVICE_TABLE_ENTRY 分派表结构体,然后调用StartServiceCtrlDispatcher();这将把调用进程的主线程转换为控制分派器。该分派器启动一个新线程,该线程运行分派表中对应于你的服务的ServiceMain()函数。ServiceMain()函数将在下面提到。

    #define SLEEP_TIME 5000

lpServiceName: 指向表示服务名称字符串的指针;当定义了多个服务时,那么这个域必须指定;
lpServiceProc: 指向服务主函数的指针(服务入口点);
  分派表的最后一项必须是服务名和服务主函数域的 NULL 指针,文本例子程序中只宿主一个服务,所以服务名的定义是可选的。
  服务控制管理器(SCM:Services Control Manager)是一个管理系统所有服务的进程。当 SCM 启动某个服务时,它等待某个进程的主线程来调用 StartServiceCtrlDispatcher 函数。将分派表传递给 StartServiceCtrlDispatcher。这将把调用进程的主线程转换为控制分派器。该分派器启动一个新线程,该线程运行分派表中每个服务的 ServiceMain 函数(本文例子中只有一个服务)分派器还监视程序中所有服务的执行情况。然后分派器将控制请求从 SCM 传给服务。

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

 

    #define LOGFILE "C:file://MyServices//memstatus.txt"

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

lpServiceName: 指向表示服务名称字符串的指针;当定义了多个服务时,那么这个域必须指定;
lpServiceProc: 指向服务主函数的指针(服务入口点);

此过程示例代码如下:

    SLEEP_TIME 指定两次连续查询可用内存之间的毫秒间隔。在第二步中编写服务工作循环的时候要使用该常量。

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

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

 

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

第二步:ServiceMain 函数

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

SERVICE_TABLE_ENTRY entrytable[2];

  int WriteToLog(char* str)
{
    FILE* log;
    log = fopen(LOGFILE, "a+");
    if (log == NULL)
    return -1;
    fprintf(log, "%sn", str);
    fclose(log);
    return 0;
}

  Listing 1 展示了 ServiceMain 的代码。该函数是服务的入口点。它运行在一个单独的线程当中,这个线程是由控制分派器创建的。ServiceMain 应该尽可能早早为服务注册控制处理器。这要通过调用 RegisterServiceCtrlHadler 函数来实现。你要将两个参数传递给此函数:服务名和指向 ControlHandlerfunction 的指针。
  它指示控制分派器调用 ControlHandler 函数处理 SCM 控制请求。注册完控制处理器之后,获得状态句柄(hStatus)。通过调用 SetServiceStatus 函数,用 hStatus 向 SCM 报告服务的状态。
Listing 1 展示了如何指定服务特征和其当前状态来初始化 ServiceStatus 结构,ServiceStatus 结构的每个域都有其用途:

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

 

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

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。
  在报告了服务状态之后,你可以调用 InitService 函数来完成初始化。这个函数只是添加一个说明性字符串到日志文件。如下面代码所示:

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

    entrytable[0].lpServiceName="testservice";

    SERVICE_STATUS ServiceStatus;

    SERVICE_STATUS_HANDLE hStatus;

    void ServiceMain(int argc, char** argv);

    void ControlHandler(DWORD request);

    int InitService();

// 服务初始化
int InitService()
{
    int result;
    result = WriteToLog("Monitoring started.");
    return(result);
}
  在 ServiceMain 中,检查 InitService 函数的返回值。如果初始化有错(因为有可能写日志文件失败),则将服务状态置为终止并退出 ServiceMain:

  第二步:ServiceMain 函数

 

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

error = InitService();
if (error)
{
    // 初始化失败,终止服务
    ServiceStatus.dwCurrentState = SERVICE_STOPPED;
    ServiceStatus.dwWin32ExitCode = -1;
    SetServiceStatus(hStatus, &ServiceStatus);
    // 退出 ServiceMain
    return;
}
如果初始化成功,则向 SCM 报告状态:

void ServiceMain(int argc, char** argv) 
{
BOOL bRet;

    entrytable[0].lpServiceProc=(LPSERVICE_MAIN_FUNCTION)ServiceMain;

 void main()
{
    SERVICE_TABLE_ENTRY ServiceTable[2];
    ServiceTable[0].lpServiceName = "MemoryStatus";
    ServiceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;

    ServiceTable[1].lpServiceName = NULL;
    ServiceTable[1].lpServiceProc = NULL;

    // 启动服务的控制分派机线程
    StartServiceCtrlDispatcher(ServiceTable);
}

// 向 SCM 报告运行状态
ServiceStatus.dwCurrentState = SERVICE_RUNNING;
SetServiceStatus (hStatus, &ServiceStatus);
接着,启动工作循环。每五秒钟查询一个可用物理内存并将结果写入日志文件。

bRet = TRUE;
ServiceStatus.dwServiceType     =SERVICE_WIN32; 
ServiceStatus.dwCurrentState     =SERVICE_START_PENDING; 
ServiceStatus.dwControlsAccepted   =SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
ServiceStatus.dwWin32ExitCode    = 0; 
ServiceStatus.dwServiceSpecificExitCode = 0; 
ServiceStatus.dwCheckPoint     = 0;
ServiceStatus.dwWaitHint     = 0; 
hStatus = RegisterServiceCtrlHandler("SERVICENAME", (LPHANDLER_FUNCTION)ControlHandler); 
if (hStatus == (SERVICE_STATUS_HANDLE)0) 
{
   //登陆失败
   return;
}

 

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

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

// service状态情报更新
ServiceStatus.dwCurrentState = SERVICE_RUNNING; 
SetServiceStatus (hStatus, &ServiceStatus);

    entrytable[1].lpServiceName=NULL;

    lpServiceName: 指向表示服务名称字符串的指针;当定义了多个服务时,那么这个域必须指定;lpServiceProc: 指向服务主函数的指针(服务入口点);分派表的最后一项必须是服务名和服务主函数域的 NULL 指针,文本例子程序中只宿主一个服务,所以服务名的定义是可选的。

第三步:处理控制请求

while (ServiceStatus.dwCurrentState == SERVICE_RUNNING)
   {
      int result = startFunc();
      if (result)
      {
         ServiceStatus.dwCurrentState = SERVICE_STOPPED; 
         ServiceStatus.dwWin32ExitCode = -1; 
         SetServiceStatus(hStatus, &ServiceStatus);
         return;
      }
   }

 

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

  在第二步中,你用 ServiceMain 函数注册了控制处理器函数。控制处理器与处理各种 Windows 消息的窗口回调函数非常类似。它检查 SCM 发送了什么请求并采取相应行动。
  每次你调用 SetServiceStatus 函数的时候,必须指定服务接收 STOP 和 SHUTDOWN 请求。Listing 2 示范了如何在 ControlHandler 函数中处理它们。
  STOP 请求是 SCM 终止服务的时候发送的。例如,如果用户在“服务”控制面板中手动终止服务。SHUTDOWN 请求是关闭机器时,由 SCM 发送给所有运行中服务的请求。两种情况的处理方式相同:

return;
}

    entrytable[1].lpServiceProc=NULL;

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

写日志文件,监视停止;
向 SCM 报告 SERVICE_STOPPED 状态;
  由于 ServiceStatus 结构对于整个程序而言为全局量,ServiceStatus 中的工作循环在当前状态改变或服务终止后停止。其它的控制请求如:PAUSE 和 CONTINUE 在本文的例子没有处理。
  控制处理器函数必须报告服务状态,即便 SCM 每次发送控制请求的时候状态保持相同。因此,不管响应什么请求,都要调用 SetServiceStatus。

第三步:建立自己的startFunc()函数----愿意写点什么就写点什么吧。:)

 

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

第四步:安装和配置服务

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

StartServiceCtrlDispatcher(entrytable);

    第二步:ServiceMain 函数

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

sc create MemoryStatus binpath= c:MyServicesMemoryStatus.exe

 

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

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

发出此创建命令。指定服务名和二进制文件的路径(注意 binpath= 和路径之间的那个空格)。安装成功后,便可以用服务控制面板来控制这个服务(参见图一)。用控制面板的工具栏启动和终止这个服务。
MemoryStatus 的启动类型是手动,也就是说根据需要来启动这个服务。右键单击该服务,然后选择上下文菜单中的“属性”菜单项,此时显示该服务的属性窗口。在这里可以修改启动类型以及其它设置。你还可以从“常规”标签中启动/停止服务。以下是从系统中移除服务的方法:

在这之后系统将自动创建一个线程去执行ServiceMain函数的内容,你应该将你要执行的任务在ServiceMain中循环,这样服务就开始运行了。

    它指示控制分派器调用 ControlHandler 函数处理 SCM 控制请求。注册完控制处理器之后,获得状态句柄(hStatus)。通过调用 SetServiceStatus 函数,用 hStatus 向 SCM 报告服务的状态。

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

sc delete MemoryStatus

 

    Listing 1 展示了如何指定服务特征和其当前状态来初始化 ServiceStatus 结构,ServiceStatus 结构的每个域都有其用途:

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

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

2.ServiceMain函数为void WINAPI ServiceMain(int argc, char** argv)格式的函数,函数名字可以任意定义。它的作用就是:将你需要执行的任务放到该函数中循环执行即可。这就是服务程序的工作函数。在ServiceMain执行你的任务前,需要给SERVICE_TABLE_ENTRY 分派表结构体进行赋值,注意由于此时服务还没有开始执行你的任务所以我们将服务的状态设置为SERVICE_START_PENDING,即正在初始化。我们进行如下赋值:

    dwServiceType:指示服务类型,创建 Win32 服务。赋值 SERVICE_WIN32;

  从服务控制面板启动 MemoryStatus 服务。如果初始化不出错,表示启动成功。过一会儿将服务停止。检查一下 C:MyServices 文件夹中 memstatus.txt 文件的服务输出。在我的机器上输出是这样的:

注意:service 服务是XP系统运行在C:WindowsSystem32下的。

 

    dwCurrentState:指定服务的当前状态。因为服务的初始化在这里没有完成,所以这里的状态为 SERVICE_START_PENDING;

Monitoring started.
273469440
273379328
273133568
273084416
Monitoring stopped.
  为了测试 MemoryStatus 服务在出错情况下的行为,可以将 memstatus.txt 文件设置成只读。这样一来,服务应该无法启动。
  去掉只读属性,启动服务,在将文件设成只读。服务将停止执行,因为此时日志文件写入失败。如果你更新服务控制面板的内容,会发现服务状态是已经停止。

      2000系统是运行在CWinntsystem下的。

servicestatus.dwServiceType = SERVICE_WIN32;

    dwControlsAccepted:这个域通知 SCM 服务接受哪个域。本文例子是允许 STOP 和 SHUTDOWN 请求。处理控制请求将在第三步讨论;

 

所以程序中有关路径的地方一定要注意了。

 

    dwWin32ExitCode 和 dwServiceSpecificExitCode:这两个域在你终止服务并报告退出细节时很有用。初始化服务时并不退出,因此,它们的值为 0;

 

除了这两个系统,其他系统还没有测试过,不过可以自己测试一下,生成的目录在C:memstatus.txt中,代码如下:

    servicestatus.dwCurrentState = SERVICE_START_PENDING;

    dwCheckPoint 和 dwWaitHint:这两个域表示初始化某个服务进程时要30秒以上。本文例子服务的初始化过程很短,所以这两个域的值都为 0.

 

 

 

    调用 SetServiceStatus 函数向 SCM 报告服务的状态时。要提供 hStatus 句柄和 ServiceStatus 结构。注意 ServiceStatus 一个全局变量,所以你可以跨多个函数使用它。ServiceMain 函数中,你给结构的几个域赋值,它们在服务运行的整个过程中都保持不变,比如:dwServiceType.

#include <windows.h>
#include <stdio.h>

servicestatus.dwControlsAccepted=SERVICE_ACCEPT_SHUTDOWN|SERVICE_ACCEPT_STOP;

    在报告了服务状态之后,你可以调用 InitService 函数来完成初始化。这个函数只是添加一个说明性字符串到日志文件。如下面代码所示: 

#define SLEEP_TIME 3000
#define LOGFILE "C:\memstatus.txt"

 

 // 服务初始化
int InitService()
{
    int result;
    result = WriteToLog("Monitoring started.");
    return(result);
}

int WriteToLog(char* );

//在本例中只接受系统关机和停止服务两种控制命令

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

SERVICE_STATUS ServiceStatus; 
SERVICE_STATUS_HANDLE hStatus;

 

  error = InitService();
if (error)
{
    // 初始化失败,终止服务
    ServiceStatus.dwCurrentState = SERVICE_STOPPED;
    ServiceStatus.dwWin32ExitCode = -1;
    SetServiceStatus(hStatus, &ServiceStatus);
    // 退出 ServiceMain
    return;
}

void ServiceMain(int argc, char** argv); 
void ControlHandler(DWORD request); 
//int InitService();

    servicestatus.dwWin32ExitCode = 0;

    如果初始化成功,则向 SCM 报告状态:

int main() 

    SERVICE_TABLE_ENTRY ServiceTable[2];
    ServiceTable[0].lpServiceName = "MemoryStatus";
    ServiceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;
    
    ServiceTable[1].lpServiceName = NULL;
    ServiceTable[1].lpServiceProc = NULL;

 

    // 向 SCM 报告运行状态

    //
    StartServiceCtrlDispatcher(ServiceTable); 
return 0;
}

    servicestatus.dwServiceSpecificExitCode = 0;

    ServiceStatus.dwCurrentState = SERVICE_RUNNING;

void ServiceMain(int argc, char** argv) 

//   int error;

 

    SetServiceStatus (hStatus, &ServiceStatus);

   ServiceStatus.dwServiceType =    SERVICE_WIN32; 
   ServiceStatus.dwCurrentState =    SERVICE_START_PENDING; 
   ServiceStatus.dwControlsAccepted   =     SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
   ServiceStatus.dwWin32ExitCode = 0; 
   ServiceStatus.dwServiceSpecificExitCode = 0; 
   ServiceStatus.dwCheckPoint = 0; 
   ServiceStatus.dwWaitHint = 0;

    servicestatus.dwCheckPoint = 0;

    接着,启动工作循环。每五秒钟查询一个可用物理内存并将结果写入日志文件。

   hStatus = RegisterServiceCtrlHandler(
      "MemoryStatus", 
      (LPHANDLER_FUNCTION)ControlHandler); 
   if (hStatus == (SERVICE_STATUS_HANDLE)0) 
   { 
      return; 
   }

 

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

   ServiceStatus.dwCurrentState = SERVICE_RUNNING; 
   SetServiceStatus (hStatus, &ServiceStatus);

    servicestatus.dwWaitHint = 0;

    第三步:处理控制请求

   char memory[256];

 

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

   while (ServiceStatus.dwCurrentState == SERVICE_RUNNING)
   {

hstatus = ::RegisterServiceCtrlHandler("testservice", CtrlHandler);

    每次你调用 SetServiceStatus 函数的时候,必须指定服务接收 STOP 和 SHUTDOWN 请求。Listing 2 示范了如何在 ControlHandler 函数中处理它们。

   GetCurrentDirectory(256,memory);

 

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

      int result = WriteToLog(memory);
      if (result)
      {
         ServiceStatus.dwCurrentState = SERVICE_STOPPED; 
         ServiceStatus.dwWin32ExitCode = -1; 
         SetServiceStatus(hStatus, &ServiceStatus);
         return;
      }
      Sleep(SLEEP_TIME);
   }
   return; 
}

CtrlHandler为void WINAPI CtrlHandler(DWORD request)型的函数,函数名字可以任意设定。将在下一点讲到。

    写日志文件,监视停止;

void ControlHandler(DWORD request) 

   switch(request) 
   { 
      case SERVICE_CONTROL_STOP: 
         WriteToLog("Monitoring stopped.");

 

    向 SCM 报告 SERVICE_STOPPED 状态;

         ServiceStatus.dwWin32ExitCode = 0; 
         ServiceStatus.dwCurrentState = SERVICE_STOPPED; 
         SetServiceStatus (hStatus, &ServiceStatus);
         return;

Hstatus为SERVICE_STATUS_HANDLE类型的全局变量。当需要改变服务状态时SetServiceStatus()函数需要它做为参数来标识一个服务。

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

      case SERVICE_CONTROL_SHUTDOWN: 
         WriteToLog("Monitoring stopped.");

 

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

         ServiceStatus.dwWin32ExitCode = 0; 
         ServiceStatus.dwCurrentState = SERVICE_STOPPED; 
         SetServiceStatus (hStatus, &ServiceStatus);
         return; 
        
      default:
         break;
    }

  1. void WINAPI CtrlHandler(DWORD request),函数的主要功能是,接收系统传递的控制命令,比如当你通过sc.exe关闭服务时,该函数会收到SERVICE_CONTROL_STOP消息,你就可以对服务进行必要的管理。在本例子程序中就只接收SERVICE_ACCEPT_SHUTDOWN和SERVICE_ACCEPT_STOP消息,这是通过前面给servicestatus赋值设定的。

    第四步:安装和配置服务

    SetServiceStatus (hStatus, &ServiceStatus);

 

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

    return; 
}

这样一个基本的服务程序就完成了。

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

int WriteToLog(char* str)
{
    FILE* log;
    log = fopen(LOGFILE, "a+");
    if (log == NULL)
    return -1;
    fprintf(log, "%sn", str);
    fclose(log);
    return 0;
}

 

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

////////////////下面看他的代码吧////////////////

下面贴出我的示例代码仅供参考。该代码在vs2008中调试通过。本文结束的时候会附上如何安装服务。

    sc delete MemoryStatus

//main.cpp

 

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

 

#include <stdio.h>

    第五步:测试服务

#include <windows.h>
void WINAPI ServiceMain(DWORD , char** ); 
void ControlHandler(DWORD ); 
int IntelligentStart();
void txtinput();
void read();
SERVICE_STATUS ServiceStatus; 
SERVICE_STATUS_HANDLE hStatus;
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPreInst, LPSTR pchCmdLine, int iCmdShow )
{
    SERVICE_TABLE_ENTRY ServiceTable[2];
    ServiceTable[0].lpServiceName = "IntelligentStart";
    ServiceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;
    
    ServiceTable[1].lpServiceName = NULL;
    ServiceTable[1].lpServiceProc = NULL;

 

    从服务控制面板启动 MemoryStatus 服务。如果初始化不出错,表示启动成功。过一会儿将服务停止。检查一下 C:MyServices 文件夹中 memstatus.txt 文件的服务输出。在我的机器上输出是这样的:

    StartServiceCtrlDispatcher(ServiceTable); 
return 0;
   
}

#include <Windows.h>

    Monitoring started.

void WINAPI ServiceMain(DWORD ac, char **av) 
{

 

    273469440

   ServiceStatus.dwServiceType      = SERVICE_WIN32; 
   ServiceStatus.dwCurrentState     = SERVICE_START_PENDING; 
   ServiceStatus.dwControlsAccepted    =   SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
   ServiceStatus.dwWin32ExitCode     = 0; 
   ServiceStatus.dwServiceSpecificExitCode = 0; 
   ServiceStatus.dwCheckPoint      = 0; 
   ServiceStatus.dwWaitHint      = 0;

#define SLEEP_TIME 5000 //间隔时间

    273379328

   hStatus = RegisterServiceCtrlHandler("IntelligentStart", (LPHANDLER_FUNCTION)ControlHandler); 
   if (hStatus == (SERVICE_STATUS_HANDLE)0) 
   { 
      // Registering Control Handler failed
      return; 
   }

 

    273133568

   //Report the running status to SCM. 
   ServiceStatus.dwCurrentState = SERVICE_RUNNING; 
   SetServiceStatus (hStatus, &ServiceStatus);

#define FILE_PATH "C:\log.txt" //信息输出文件

    273084416

   // The worker loop of a service
   while (ServiceStatus.dwCurrentState == SERVICE_RUNNING)
   {
      int result = IntelligentStart();
      if (result)
      {
         ServiceStatus.dwCurrentState = SERVICE_STOPPED; 
         ServiceStatus.dwWin32ExitCode = -1; 
         SetServiceStatus(hStatus, &ServiceStatus);
         return;
      }
   }
   return; 
}

 

    Monitoring stopped.

void ControlHandler(DWORD request) 

   switch(request) 
   { 
      case SERVICE_CONTROL_STOP: 
         ServiceStatus.dwWin32ExitCode = 0; 
         ServiceStatus.dwCurrentState = SERVICE_STOPPED; 
         SetServiceStatus (hStatus, &ServiceStatus);
         return;

bool brun=false;

    为了测试 MemoryStatus 服务在出错情况下的行为,可以将 memstatus.txt 文件设置成只读。这样一来,服务应该无法启动。

      case SERVICE_CONTROL_SHUTDOWN: 
         ServiceStatus.dwWin32ExitCode = 0; 
         ServiceStatus.dwCurrentState = SERVICE_STOPPED; 
         SetServiceStatus (hStatus, &ServiceStatus);
         return; 
        
      default:
         break;
    }

 

    去掉只读属性,启动服务,在将文件设成只读。服务将停止执行,因为此时日志文件写入失败。如果你更新服务控制面板的内容,会发现服务状态是已经停止。

    // Report current status
    SetServiceStatus (hStatus, &ServiceStatus);

SERVICE_STATUS servicestatus;

    return; 
}

 

int IntelligentStart()
{

SERVICE_STATUS_HANDLE hstatus;

txtinput();
return 0;
}

 

write.cpp

 

#include <stdio.h>
#include <conio.h>
void txtinput()
{
FILE *fp;
char st[20] = "test new.n";
fp=fopen("C:\string.txt","at+");

 

fputs(st,fp);

int WriteToLog(char* str);

fclose(fp);

 

return ;
}

void WINAPI ServiceMain(int argc, char** argv);

 

 

void WINAPI CtrlHandler(DWORD request);

 

int InitService();

 

 

 

int WriteToLog(char* str)

 

{

 

    FILE* pfile;

 

    fopen_s(&pfile,FILE_PATH,"a+");

 

    if (pfile==NULL)

 

    {

 

        return -1;

 

    }

 

    fprintf_s(pfile,"%sn",str);

 

    fclose(pfile);

 

    return 0;

 

}

 

 

 

void WINAPI ServiceMain(int argc, char** argv)

 

{

 

    servicestatus.dwServiceType = SERVICE_WIN32;

 

    servicestatus.dwCurrentState = SERVICE_START_PENDING;

 

    servicestatus.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN|SERVICE_ACCEPT_STOP;//在本例中只接受系统关机和停止服务两种控制命令

 

    servicestatus.dwWin32ExitCode = 0;

 

    servicestatus.dwServiceSpecificExitCode = 0;

 

    servicestatus.dwCheckPoint = 0;

 

    servicestatus.dwWaitHint = 0;

 

    hstatus = ::RegisterServiceCtrlHandler("testservice", CtrlHandler);

 

    if (hstatus==0)

 

    {

 

        WriteToLog("RegisterServiceCtrlHandler failed");

 

        return;

 

    }

 

    WriteToLog("RegisterServiceCtrlHandler success");

 

    //向SCM 报告运行状态

 

    servicestatus.dwCurrentState = SERVICE_RUNNING;

 

    SetServiceStatus (hstatus, &servicestatus);

 

    //下面就开始任务循环了,你可以添加你自己希望服务做的工作

 

    brun=true;

 

    MEMORYSTATUS memstatus;

 

    char str[100];

 

    memset(str,'',100);

 

    while (brun)

 

    {

 

        GlobalMemoryStatus(&memstatus);

 

        int availmb=memstatus.dwAvailPhys/1024/1024;

 

        sprintf_s(str,100,"available memory is %dMB",availmb);

 

        WriteToLog(str);

 

        Sleep(SLEEP_TIME);

 

    }

 

    WriteToLog("service stopped");

 

}

 

 

 

void WINAPI CtrlHandler(DWORD request)

 

{

 

    switch (request)

 

    {

 

        case SERVICE_CONTROL_STOP:

 

            brun=false;

 

            servicestatus.dwCurrentState = SERVICE_STOPPED;

 

            break;

 

        case SERVICE_CONTROL_SHUTDOWN:

 

            brun=false;

 

            servicestatus.dwCurrentState = SERVICE_STOPPED;

 

            break;

 

        default:

 

            break;

 

    }

 

    SetServiceStatus (hstatus, &servicestatus);

 

 

 

}

 

void main()

 

{

 

    SERVICE_TABLE_ENTRY entrytable[2];

 

    entrytable[0].lpServiceName="testservice";

 

    entrytable[0].lpServiceProc=(LPSERVICE_MAIN_FUNCTION)ServiceMain;

 

    entrytable[1].lpServiceName=NULL;

 

    entrytable[1].lpServiceProc=NULL;

 

    StartServiceCtrlDispatcher(entrytable);

 

}

 

 

 

如何安装服务:

 

运行命令提示符cmd.exe

 

输入sc create testservicename binpath= D:test.exe

 

输入sc start testservicename 启动服务

 

输入sc query 会在最底部显示你的服务当前的状态

 

输入sc stop testservicename 停止服务

 

输入sc delete testservicename删除服务,该服务将在下次重启后删除,在重启之前将不能注册同一个名字的服务。

 

张鹏

 

HikVision

 

[email protected]

 

2011/11/22

语言编写Windows 服务程序的五个步骤》。但可能...

本文由10bet手机官网发布于面向对象,转载请注明出处:如何用C语言编写Windows服务程序的五个步骤,C语言编写Windows服务程序

上一篇:头文件一览【10bet手机官网】 下一篇:没有了
猜你喜欢
热门排行
精彩图文