用C创建简单的Win32服务程序_第1页
用C创建简单的Win32服务程序_第2页
用C创建简单的Win32服务程序_第3页
用C创建简单的Win32服务程序_第4页
用C创建简单的Win32服务程序_第5页
已阅读5页,还剩6页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、:首页»文档中心 >> 在线杂志>> nt服务在线杂志第41期翻译文档本文适合中级读者己阅读52756次文档用c+创建简单的win32服务程序 作者:nigel thomson (msdn 技术组) 翻译:northtibot原文出处:creating a simple win32 service in c+下载ntservice例子源代码下载ntservcpl例子源代码下载ntservctrl例了源代码 摘要本文描述如何用visual c+创建windows nt服务程序。创建该服务仅用到一个c卄 类,这个类提供服务与操作系统之间一个简单的接口。使用这个类实

2、现自己的服务非常简 单,只要改写少数几个基类中的虚拟函数即可。在本文有三个源代码参考例子:ntservice是一个简单的win32服务,它就是用本文所描述的方法建立的;*. ntservcpl是一个控制面版程序,用来控制ntservice服务;ntservctrl是一个独立的程序例子,用它可以监控某个win32服务; 简介windows nt中的服务实际上是一个程序,只要计算机操作系统一启动,服务就可以运 行其中。它不需要用户登陆。服务程序是一种与用户无关的任务,比如目录复制,进程监 控或网络上供其它机器使用的服务,比如http协议支持。创建windows nt服务程序并不是很难。但调试某个服

3、务程序不是一件容易的事。就我 自己而言,我喜欢用visual c+编写自己的c+程序。大多数win32服务都是用c写 的,所以我觉得如果用某个c+类来实现win32服务的基本功能一定很有意思。有了这 个c+类,谁要想用c+创建win32服务就是一件很简单的事情了。我为此开发了一个 c+基类,用它作为编写win32服务的起点应该没有什么大问题。创建服务程序除了编写服务代码外,还必须做一些其它额外的编码工作:*在系统日志或应用程序口志中报告警告信息和出错信息,不能用输出到屏幕的方式,因 为用户根木就没有登陆。*服务稈序的控制即可以通过单独的应用程序,也可以通过控制面版程序。这取决于你的 服务实现什

4、么样的通讯机制。*.从系统中安 装和卸载服务大多数服务程序都是使用一个安装程序来安装,而用另外一个程序来卸载。本文我将 这些功能内建在服务程序自少当中,使z体化,这样只分发一个.exe文件即可。你可以 从命令行直接运行服务程序,并且可以随心所欲地安装和卸载或报告其版本信息。 ntservice支持下列的命令行参数:*.-仃报告服务的名字和版本号;*-i,安装服务;*. -u,卸载服务;默认情况下,当系统启动该服务时没有命令行参数传递。 创建应用程序框架我一直都是创建基于mfc的应用程序。当我刚接触win32服务程序时,我先是用 visual c+ appwizard创建一个sdt/mfc程序。

5、然后去掉其中的文档和视图类、图标以 及其它一些无用的东西,只剩下框架。结果到最后什么都去掉了,包括主窗口(服务程序 不能有这个东东),什么也没有留下,非常愚蠢。我不得不又回过头到appwizard,并用 单个的源文件创建控制台程序,此源文件包含main入口函数,我将这个文件命名为 ntservapp. cppo我用此cpp扩展而不是用c,因为我只想用c+來写程序,而不是直接 用co稍后我们会讨论该文件代码实现。因为我想用c+类来构建服务,所以我创建了 ntservice. h和ntservice. cpp文件, 用它们来实现cntservice基类。我还创建了 myservice. h和mys

6、ervice. cpp文件用于 实现自己的服务类(cmyservice),它派生于cntservice.稍后我们会看到代码。建立新工程时,我喜欢尽快看到运行结果,所以我决定服务程序要做的第一件事情是 建立一个系统应用程序h志记录。借助这个h志记录机制,我能跟踪服务何时启动,何时 停止等等。我还可以记录服务中发生的任何岀错信息。创建这个日志记录比我想象的要复 杂得多。建立日志记录我想,既然日志文件是操作系统的一部分,那么肯定有应用程序编程接口 (api)来支 持建立日志记录。所以我开始搜索msdn cd,直到发现reportevent函数为止。如果你不 熟悉这个函数,你可能会想,这个函数应该知道

7、在哪个日志文件建立记录,以及你想要插 入的文本信息。没错,这都是它要做的事情,但是为了简化出错信息的国际化,该函数有 一个消息id作为参数,并在你提供的消息表中查找消息。所以 问题无非是你想将什么消息放入日志,以及如何将这些消息添加到你的应用程序中,下面 我们一步一步来做:1以mc为扩展名创建一个包含消息描述的文本文件。我将它命名为ntservmsg. mce该 文件的格式非常特别,具体细节参见platform sdk文档;2针对你的源文件运行消息编译器(mc.exe),默认情况下它创建名为msg00001.bin的 输岀文件。编译器还创建一个头文件(在我的例子程序中,该头文件是ntservm

8、sg. h)和 一个.rc文件(ntservmsg. tc)。只要你修改工程的.mc文件就必须重复这一步,所以把 工具加到visual c+的工具菜单里做起來会很方便;3. 为工程创建一个.rc文件,将windows. h头文件以及消息编译器产生的.rc文件包 含到其中;4. 在主工程头文件中包含消息编译器产生的头文件,以便模块可以存取符号消息名;下而让我们仔细一下这些文件,以便弄明白你自己需要创建什么,以及消息编译器要 为你创建些什么。我们不用研究整个消息集,只要看看其中一二个如何工作的即可。下面 是例子程序消息源文件ntservmsg. me的笫一部分:messageld二100symbo

9、licname=evmsg_installedlanguage=englishthe %1 service was installed messageld=symbo1icname=evmsg_removedlanguage=englishthe %1 service was removed. messageld=symbo1icname=evmsg_notremovedlanguage二englishthe %1 service could not be removed. 每一条都有一个消息id,如果不特别设置,那么id的取值就是指其前面所赋的值。 每一条还有一个代码中使用的符号名,语言标示

10、符以及消息文本。消息可以跨多个行,并 用含有一个句号的单独一行终止。消息编译器输出一个库文件,该库文件被用作应用程序的资源,此外还输出两个要在 代码中包含的文件。下面是我的.rc文件:/ ntservapp. rcttinclude <windows. h>/包含由消息编译器(mc)产生的消息表资源脚 本ttinclude,zntsorvmsg.rc"iiere''s the . rc file the message compiler generated:language 0x9,0x11 11 msg00001. bin正像你所看到的,这些文件中内容不

11、多!消息编译器产生的最后一个文件是你耍包含到代码中的头文件,下面就是这个头文件的部 分内容:/message id: evmsg_ installed/ messagetext: / the %1 servicewas installed, /ttdefine evmsg_installed 0x00000064l/ messageld:evmsg removed/ messagetext:/ the %1 service was removed, /define evmsg_removed 0x00000065l你可能己经注意到了有几个消息包含参数替代项(如%1) o让我们看看将消息写入某

12、个系统日志文件吋如何在代码中使用消息id和参数替代项。以事件日志中记录成功安装信 息的部分安装代码为例。也就是cntservice: tstnstalled函数部分:.jlogevent(eventlog_information_type, evmsg_installed,m szservicename)logevent是另一个cntservice函数,它使用事件类型(信息,警告或错误),事件消息 的id,以及形成日志消息的最多三个参数的替代串:/ this function makes an entry into the application event log. void cntserv

13、ice:logevent(word wtype, dword dwid, const char* pszsl, const char* pszs2, const char* pszs3) const char* ps3;ps0 = pszsl;psl = pszs2;ps2= pszs3;int istr = 0;for (int i = 0; i < 3; i+) if (psi != null) istr+;/ check to see if the eve nt source has been registered, / and if not then register it no

14、w. if (!m heventsource) m_heventsource = :registereventsource(null, / local machinem_szservicename);/ source nameif (m heventsource):reportevent(m_heventsource, wtype, 0, dwid, null, / sidistr, 0, ps,null); 如你所见,其主要工作是由reportevent系统函数处理。至此,我们己经可以通过调用cntservice:logevent在系统日志中记录事件了。接 下来我们将考虑创建服务本身的一些代

15、码。编写服务代码为了建构一个简单的win32服务,你需要知道的大多数信息都可以在platform sdk 中找到。其中的范例代码都是用c语言写的,并且很好理解。我的cntservice类就是基于 这些代码。一个服务主要包括三个函数:main函数,这是代码的入口。我们正是在这里解析任何命令行参数并进行服务的安装, 移除,启动等等。*在例子中,提供真正服务代码的入口函数叫servicemaino你可以随便叫它什么。在服 务第一次启动的恶时候,将该函数的地址传递给服务管理器。*.处理来自服务管理器命令消息的函数。在例子中,这个函数叫handler,这个名字可以 随意取。服务回调函数因为service

16、main和handler函数都是由系统來调用,所以它们必须遵循操作系统 的参数传递规范和调用规范。也就是说,它们不能简单地作为某个c+类的成员函数。这 样就给封装带来一些不便,因为我们想把win32服务的功能封装在一个c+类中。为了 解决这个问题,我将servicemain和handler函数创建成cntservice类的静态成员。 这样就使我得以创建可以由操作系统调用的函数。但是,这样做还没有完全解决问题,因 为系统不允许给被调用的函数传递任何形式的用户数据,所以我们无法确定对c+对象特 定实例的servicemain或handler的调用。用了一个非常简单但有局限的方法來解决这 个问题o我

17、创建一个包含c+对象指针的静态变量。这个变量是在该对象首次创建是进行初始化 的。这样便限制你每个服务应用只有一个c+对彖。我觉得这个限制并不过分。下面是 ntservice. h文件中的声明: class cntservice/静态数据static cntservice* m_pthis; / nasty hack to get object ptr;下而是初始化m_pthis指针的方法:cntservice:cntservice(const char* szservicename) / copy the address of the current object so we can acce

18、ss it from/ the static member cal 1 back functions./ warning: this limits the application to only one cntservice object.m pthis = this;.cntservice 类当我创建c+对象封装windows函数时,我尝试为我封装的每个windows api除了 创建成员函数外,还做一些別的工作,我尝试让对象更容易使用,降低实现特定项目所需 的代码行数。因此我的对象是基于“我想让这个对象做什么? ”而不是“windows用这些 apis做什么? ”cntservice类包含

19、一些用来解析命令行的成员函数,为了处理服务的安装和拆卸以及 事件日志的记录,你得在派生类中重写一些虚拟函数來处理服务控制管理器的请求。下而 我们将通过本文的例子服务实现来研究这些函数的使用。如果你想创建尽可能简单的服务,只需要重写cntservice: :run即可,它是你编写代 码实现具体服务任务的地方。你还需要实现main函数。如果服务需要实现一些初始化。 如从注册表读取数据,还需重写cntservice: :0nlnito如果你要向服务发送命令消息, 那么可以在服务中使用系统函数control service,重写cntservice: :onusercontrol来 处理请求。在例子应

20、用程序中使用cntservicentservice 在cmyservice类中实现了它的大多数功能,cmyservice由cntservice派生。myservice. h头文件如下:/ myservice.httinclude "ntservice. h"class cmyservice : public cntservicepublic:cmyservice();virtual bool onlnit();virtual void run();virtual bool onusercontrol(dword dwopcode);void savestatus();/ c

21、ontrol parametersint m_istartparam;int m_iincparam;/ current stateint m_istate;;正像你所看到的,cmyservice 改写了 cntservice 的 onlnit> run 和 onusercontrolo 它还有一个函数叫savestatus,这个函数被用于将数据写入注册表,那些成员变量用來保 存当前状态。例子服务每隔一定的时间对一个整型变量进行增量处理。开始值和增量值都 存在注册表的参数中。这样做并没有别的意图。只是为了简单示范。下面我们看看这个服 务是如何实现的。实现main函数有了从cntservi

22、ce派生的cmyservice,实现main函数很简单,请看ntservapp. cpp文 件:int main(int argc, char* argv)/创建服务对象cmyservice myservice;/解析标准参数(安装,卸载,版本等.)if (!myservice. parsestandardargs(argc, argv)未发现任何标准参数,所以启动服务,/取消下而debugbreak代码行的注释,/当服务启动后进入调试器,/debugbreak0 ;myservice. startservice();/到这里,服务已经停止return myservice. m_status.

23、 dwwin32exitcode;这里代码不多,但执行后却发生了很多事情,让我们一步一步来看。首先,我们创 建一个myservice类的实例。构造函数设置初始化状态和服务名字(myservice. cpp): cmyservice:cmyservice():cntservice("nt servicedemonstration") m_i startparam = 0; milneparam = 1; m_istate m_istartparam;接着调用parsestandardargs检查命令行是否包含服务安装(-i)、卸载(-u)以及 报告其版本号(-v)的请求。cn

24、tservice:parsestandardargs分别调用cntservice: islnstalled, cntservice: install 和 cntservice:uninstall 来处理这 些请求。如果没有可识别的命令行参数,则假设该服务控制管理器试图启动该服务并调用 startserviceo该函数直到服务停止运行才返回。当你调试完代码,即可把用于调试的代 码行注释掉或删除。安装和卸载服务服务的安装由cntservice: install处理,它用win32服务管理器注册服务并在注 册表中建立一个条目以支持服务运行时fi志消息。服务的卸载由cntservice:uninsta

25、ll处理,它仅仅通知服务管理器该服务已经不再 需要。cntservice:uninstall不会删除服务实际的可执行文件。编写服务代码现在我们来编写实现服务的具体代码。对于ntservice例子,有三个函数要写。他们 涉及初始化,运行服务的细节和响应控制请求。初始化注册表有一个给服务用来存储参数的地方:hkey_local_machinesystemcurrentcontrolsetservices我就是选择这里来存储我的服务配置信息。我创建了一个parameters键,并在此存 储我要保存的值。所以当服务启动时,onlnit函数被调丿山这个函数从注册表中读取初始 设置。bool cmyser

26、vice:0ninit()/ read the registry parameters/ try opening the registry key:/ hkey_local_machinesystemcurrentcontrolsetservices parametersiikey hkey;char szkey1024;strcpy (szkey, systemwcurrentcontrolsetwservicesw');strcat(szkey, m_szservicename);strcat (szkey, "paranieters");if (regopen

27、keyex(hkeyj.ocal_machine,szkey,0,key_query_value,&hkey) = error_success)/ yes we are instailed.dword dwtype = 0;dword dwsize = sizeof (ni istartparam);regqueryvalueex(hkey,"start",null,&dwtype,(byte*)&m istartparam,&dwsize);dwsize = sizeof(m_订neparam);regqueryvalueex(hkey,i

28、nc ,null,&dwtype,(byte*)&m_订ncparam,&dwsize);regclosekey(hkey);/ set the initial state.m istate = m istartparam; return true;现在我们有了服务参数,我们便可以运行服务了。运行服务 当run函数被调用时将执行服务的主体代码。本文例子的这部分很简单: void cmyservice:run()wh i1e (m blsrunni ng) -/ sleep for a while.debugmsg(z?my service is sleeping (%l

29、u).", m istate);sleep (1000);/ update the current state. mistate += miincparam; " "注意,只要服务不终止,这个函数就不会退出。当有终止服务的请求时, cntservice: :m_bisrunning标志被置成false。如果在服务终止时,你要实现清除操作, 那么 你还可以改写onstop和/或onshutdown。响应控制请求你可以用任何适合的方式与服务通讯一一命名管道,思想交流,便条等等一一对于一 些简单的请求,用系统函数controlservice很容易实现。cntservic

30、e提供了一个处理 器专门用于通过controlservice函数发送的非标准消息(也就是用户发送的消息)。本 文例子用单一消息在注册表中保存当前服务的状态,以便其它应用程序能看到它。我不建 议用这种方法来监控服务,因为它不是最佳方法,这只是比较容易编码实现而已。 control service所能处理的用户消息必须在128到255这个范围。我定义了一个常量 service_control_user, 128作为基值。范围内的用户消息被发送到cntservice: onllsercontrol,在例了服务中,处理此消息的细节如下: bool cmyservice:onusercontrol(dw

31、ord dwopcode)switch (dwopcode)case service_control_user + 0:/ save the current status in the registry.savestatus(); return true;default:break;return false;/ say not handledsavestatus是一个局部函数,用来在注册表中存储服务状态。调试win32服务main函数中包含一个对debugbreak的调川,当服务第一次被启动时,它会激活系统 调试器。你可以监控来自调试器命令窗口中的服务调试信息。你可以在服务中用 cntserv

32、ice:debugmsg来报告调试期间感兴趣的事件。为了调试服务代码,你需要按照platform sdk文档中的要求 安装 系统调试器(windbg) o你也可以用visual studio自带的调试器调试win32服务。有一点很重要,那就是当它被服务管理器控制吋,你不能终止服务和单步执行,因 为服务管理器会让服务请求超时并终止服务线程。所以你只能让服务吐出消息,跟踪其 过程并在调试器窗口查看它们。当服务启动后(例如,从控制面板的“服务”中),调试器将在服务线程的挂起后启 动。你需要通过单击“go”按钮或按f5让继续运行。然后在调试器中观察服务的运行过 程。下面是启动和终止服务的调试输出例子:

33、module load: windebug/ntservice. exe (symbol loading deferred)thread create: procoss=0, thread=omodule load: c:nt351systom32ntdll. dll (symbol loading deferred)module load: c:nt351system32kernel32. dll (symbol loading deferred)module load: c:nt351system32advapi32. dll (symbol loading deferred)module

34、 load: c:nt351system32rpcrt4. dll (symbol loading deferred)thread create: process=0, thread二 1* warning: symbols checksum is wrong 0x0005830f 0x0005224f for c:nt351symbo1sd11ntdll. dbgmodu1eload: c:nt351symbolsdllntdll. dbg (symbols loaded)thread terminate: process=0, thread=l, exit code二ollard code

35、d breakpoint hitllard coded breakpoint hit (130): cntservice:cntservice ()modu1e load: c:nt351system32rpcltc1. dll (symbol loading deferred)nt service demonstrati on(130): callingstartservicectrldispatcherothread create: process=0, thread二2nt servicedemonstration(174): entering cntservice:servicemai

36、n()nt servicedemonstration(174): entering cntservice:itialize()nt service demonstration(174): cntservice:setstatus(3026680, 2)nt servicedemonstration(174): sleeping. nt service demonstration(174):cntservice:setstatus(3026680, 4)nt service demonstration(174): entering cntservice:run()nt service demon

37、stration(174): sleeping. nt servicedemonstration(174): sleeping. nt service demonstration(174): sleeping. nt service demonstration(130): cntservice:handler(1)nt servicedemonstration (130) : entering cntservice: : stopo nt service dem on stration (130): cntservice:setstatus(3026680, 3)nt service demonstration(130): leaving cntservice:stop()nt service demonstration(130): updating status (3026680, 3)nt service demonstration(174): leaving cntservice:run()nt service demonstration(174):leaving cntservice:initialize()nt service demonstration (174): leavingcntservice:servicem

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论