快捷搜索:

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

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——译者注)。可以将办事设置设置设备摆设摆设成操作系统启动时自动启动,这样你就不必每次再重启系统后还要手动启动办事。

本文将首先解释若何创建一个按期查询可用物理内存并将结果写入某个文本文件的办事。然后指示你完整天生,安装和实现办事的全部历程。

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

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

#include

#include

接着,定义两个常量:

#define SLEEP_TIME 5000

#define LOGFILE "C:\\MyServices\\memstatus.txt"

SLEEP_TIME 指定两次继续查询可用内存之间的毫秒距离。在第二步中编写办事事情轮回的时刻要应用该常量。

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 = "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 函数

Listing 1 展示了 ServiceMain 的代码。该函数是办事的进口点。它运行在一个零丁的线程傍边,这个线程是由节制分派器创建的。ServiceMain 应该尽可能早早为办事注册节制处置惩罚器。这要经由过程调用 RegisterServiceCtrlHadler 函数来实现。你要将两个参数通报给此函数:办事名和指向 ControlHandlerfunction 的指针。

它唆使节制分派器调用 ControlHandler 函数处置惩罚 SCM 节制哀求。注册完节制处置惩罚器之后,得到状态句柄(hStatus)。经由过程调用 SetServiceStatus 函数,用 hStatus 向 SCM 申报办事的状态。

Listing 1 展示了若何指定办事特性和其当前状态来初始化 ServiceStatus 布局,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 函数中,你给布局的几个域赋值,它们在办事运行的全部历程中都维持不变,比如:dwServiceType。

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

// 办事初始化

int InitService()

{

int result;

result = WriteToLog("Monitoring started.");

return(result);

}

在 ServiceMain 中,反省 InitService 函数的返回值。假如初始化有错(由于有可能写日志文件掉败),则将办事状态置为终止并退出 ServiceMain:

error = InitService();

if (error)

{

// 初始化掉败,终止办事

ServiceStatus.dwCurrentState = SERVICE_STOPPED;

ServiceStatus.dwWin32ExitCode = -1;

SetServiceStatus(hStatus, &ServiceStatus);

// 退出 ServiceMain

return;

}

假如初始化成功,则向 SCM 申报状态:

// 向 SCM 申报运行状态

ServiceStatus.dwCurrentState = SERVICE_RUNNING;

SetServiceStatus (hStatus, &ServiceStatus);

接着,启动事情轮回。每五秒钟查询一个可用物理内存并将结果写入日志文件。

如 Listing 1 所示,轮回不停到办事的状态为 SERVICE_RUNNING 或日志文件写入掉足为止。状态可能在 ControlHandler 函数相应 SCM 节制哀求时改动。

第三步:处置惩罚节制哀求

在第二步中,你用 ServiceMain 函数注册了节制处置惩罚器函数。节制处置惩罚器与处置惩罚各类 Windows 消息的窗口回调函数异常类似。它反省 SCM 发送了什么哀求并采取响应行动。

每次你调用 SetServiceStatus 函数的时刻,必须指定办事接管 STOP 和 SHUTDOWN 哀求。Listing 2 示范了若何在 ControlHandler 函数中处置惩罚它们。

STOP 哀求是 SCM 终止办事的时刻发送的。例如,假如用户在“办事”节制面板中手动终止办事。SHUTDOWN 哀求是关闭机械时,由 SCM 发送给所有运行中办事的哀求。两种环境的处置惩罚要领相同:

写日志文件,监视竣事;

向 SCM 申报 SERVICE_STOPPED 状态;

因为 ServiceStatus 布局对付全部法度榜样而言为全局量,ServiceStatus 中的事情轮回在当前状态改变或办事终止后竣事。其它的节制哀求如:PAUSE 和 CONTINUE 在本文的例子没有处置惩罚。

节制处置惩罚器函数必须申报办事状态,即便 SCM 每次发送节制哀求的时刻状态维持相同。是以,不管相应什么哀求,都要调用 SetServiceStatus。

第四步:安装和设置设置设备摆设摆设办事

法度榜样编好了,将之编译成 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 办事的措施:

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

发出此创建敕令。指定办事名和二进制文件的路径(留意 binpath= 和路径之间的那个空格)。安装成功后,便可以用办事节制面板来节制这个办事。用节制面板的对象栏启动和终止这个办事。

MemoryStatus 的启动类型是手动,也便是说根据必要来启动这个办事。右键单击该办事,然后选择高低文菜单中的“属性”菜单项,此时显示该办事的属性窗口。在这里可以改动启动类型以及其它设置。你还可以从“老例”标签中启动/竣事办事。以下是从系统中移除办事的措施:

sc delete MemoryStatus

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

第五步:测试办事

从办事节制面板启动 MemoryStatus 办事。假如初始化不掉足,表示启动成功。过一下子将办事竣事。反省一下 C:\MyServices 文件夹中 memstatus.txt 文件的办事输出。在我的机械上输出是这样的:

Monitoring started.

273469440

273379328

273133568

273084416

Monitoring stopped.

为了测试 MemoryStatus 办事在掉足环境下的行径,可以将 memstatus.txt 文件设置成只读。这样一来,办事应该无法启动。

去掉落只读属性,启动办事,在将文件设成只读。办事将竣事履行,由于此时日志文件写入掉败。假如你更新办事节制面板的内容,会发明办事状态是已经竣事。

原文:Yevgeny Menaker

您可能还会对下面的文章感兴趣: