




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
用C++创建简单的Win32服务程序作者:NigelThomson(MSDN技术组)翻译:NorthTibet原文出处:CreatingaSimpleWin32ServiceinC++源文在后面下载NTService例子源代码已经下载下载NTServCpl例子源代码已经下载下载NTServCtrl例子源代码已经下载°摘要本文描述如何用VisualC++创建WindowsNT服务程序。创建该服务仅用到一个C++类,这个类提供服务与操作系统之间一个简单的接口。使用这个类实现自己的服务非常简单,只要改写少数几个基类中的虚拟函数即可。在本文有三个源代码参考例子:NTService是一个简单的Win32服务,它就是用本文所描述的方法建立的;NTServCpl是一个控制面版程序,用来控制NTService服务;NTServCtrl是一个独立的程序例子,用它可以监控某个Win32服务;。简介WindowsNT中的服务实际上是一个程序,只要计算机操作系统一启动,服务就可以运行其中。它不需要用户登陆。服务程序是一种与用户无关的任务,比如目录复制,进程监控或网络上供其它机器使用的服务,比如HTTP协议支持。创建WindowsNT服务程序并不是很难。但调试某个服务程序不是一件容易的事。就我自己而言,我喜欢用VisualC++编写自己的C++程序。大多数Win32服务都是用C写的,所以我觉得如果用某个C++类来实现Win32服务的基本功能一定很有意思。有了这个C++类,谁要想用C++创建Win32服务就是一件很简单的事情了。我为此开发了一个C++基类,用它作为编写Win32服务的起点应该没有什么大问题。创建服务程序除了编写服务代码外,还必须做一些其它额外的编码工作:在系统日志或应用程序日志中报告警告信息和出错信息,不能用输出到屏幕的方式,因为用户根本就没有登陆。服务程序的控制即可以通过单独的应用程序,也可以通过控制面版程序。这取决于你的服务实现什么样的通讯机制。从系统中安装和卸载服务大多数服务程序都是使用一个安装程序来安装,而用另外一个程序来卸载。本文我将这些功能内建在服务程序自身当中,使之一体化,这样只分发一个.EXE文件即可。你可以从命令行直接运行服务程序,并且可以随心所欲地安装和卸载或报告其版本信息。NTService支持下列的命令行参数:-v,报告服务的名字和版本号;-i,安装服务;-u,卸载服务;默认情况下,当系统启动该服务时没有命令行参数传递。°创建应用程序框架我一直都是创建基于MFC的应用程序。当我刚接触Win32服务程序时,我先是用VisualC++AppWizard创建一个SDI/MFC程序。然后去掉其中的文档和视图类、图标以及其它一些无用的东西,只剩下框架。结果到最后什么都去掉了,包括主窗口(服务程序不能有这个东东),什么也没有留下,非常愚蠢。我不得不又回过头到AppWizard,并用单个的源文件创建控制台程序,此源文件包含main入口函数,我将这个文件命名为NTServApp.cpp。我用此cpp扩展而不是用C,因为我只想用C++来写程序,而不是直接用C。稍后我们会讨论该文件代码实现。因为我想用C++类来构建服务,所以我创建了NTService.h和NTService.cpp文件,用它们来实现CNTService基类。我还创建了MyService.h和MyService.cpp文件用于实现自己的服务类(CMyService),它派生于CNTService。稍后我们会看到代码。建立新工程时,我喜欢尽快看到运行结果,所以我决定服务程序要做的第一件事情是建立一个系统应用程序日志记录。借助这个日志记录机制,我能跟踪服务何时启动,何时停止等等。我还可以记录服务中发生的任何出错信息。创建这个日志记录比我想象的要复杂得多。。建立日志记录我想,既然日志文件是操作系统的一部分,那么肯定有应用程序编程接口(API)来支持建立日志记录。所以我开始搜索MSDNCD,直到发现ReportEvent函数为止。如果你不熟悉这个函数,你可能会想,这个函数应该知道在哪个日志文件建立记录,以及你想要插入的文本信息。没错,这都是它要做的事情,但是为了简化出错信息的国际化,该函数有一个消息ID作为参数,并在你提供的消息表中查找消息。所以问题无非是你想将什么消息放入日志,以及如何将这些消息添加到你的应用程序中,下面我们一步一步来做:以.MC为扩展名创建一个包含消息描述的文本文件。我将它命名为NTServMsg.mc。该文件的格式非常特别,具体细节参见PlatformSDK文档;针对你的源文件运行消息编译器(mc.exe),默认情况下它创建名为MSG00001.BIN的输出文件。编译器还创建一个头文件(在我的例子程序中,该头文件是NTServMsg.h)和一个.RC文件(NTServMsg.rc)。只要你修改工程的.MC文件就必须重复这一步,所以把工具加到VisualC++的工具菜单里做起来会很方便;为工程创建一个.RC文件,将WINDOWS.H头文件以及消息编译器产生的.RC文件包含到其中;在主工程头文件中包含消息编译器产生的头文件,以便模块可以存取符号消息名;下面让我们仔细一下这些文件,以便弄明白你自己需要创建什么,以及消息编译器要为你创建些什么。我们不用研究整个消息集,只要看看其中一二个如何工作的即可。下面是例子程序消息源文件NTServMsg.mc的第一部分:MessageId=100SymbolicName=EVMSG_INSTALLEDLanguage二EnglishThe%1servicewasinstalled..MessageId=SymbolicName=EVMSG_REMOVEDLanguage=EnglishThe%1servicewasremoved..MessageId=SymbolicName=EVMSG_NOTREMOVEDLanguage=EnglishThe%1servicecouldnotberemoved..每一条都有一个消息ID,如果不特别设置,那么ID的取值就是指其前面所赋的值。每一条还有一个代码中使用的符号名,语言标示符以及消息文本。消息可以跨多个行,并用含有一个句号的单独一行终止。消息编译器输出一个库文件,该库文件被用作应用程序的资源,此外还输出两个要在代码中包含的文件。下面是我的.RC文件://NTServApp.rc#include<windows.h>//包含由消息编译器(MC)产生的消息表资源脚本#include"NTServMsg.rc”Heresthe.RCfilethemessagecompilergenerated:LANGUAGE0x9,0x1111MSG00001.bin正像你所看到的,这些文件中内容不多!消息编译器产生的最后一个文件是你要包含到代码中的头文件,下面就是这个头文件的部分内容:[]////MessageId:EVMSG_INSTALLED////MessageText:////The%1servicewasinstalled.//#defineEVMSG_INSTALLED0X00000064L////MessageId:EVMSG_REMOVED////MessageText:////The%1servicewasremoved.//#defineEVMSG_REMOVED0X00000065L[]你可能已经注意到了有几个消息包含参数替代项(如%1)。让我们看看将消息写入某个系统日志文件时如何在代码中使用消息ID和参数替代项。以事件日志中记录成功安装信息的部分安装代码为例。也就是CNTService::IsInstalled函数部分:[....]LogEvent(EVENTLOG_INFORMATION_TYPE,EVMSG_INSTALLED,m_szServiceName);[....]LogEvent是另一个CNTService函数,它使用事件类型(信息,警告或错误),事件消息的ID,以及形成日志消息的最多三个参数的替代串://Thisfunctionmakesanentryintotheapplicationeventlog.voidCNTService::LogEvent(WORDwType,DWORDdwID,constchar*pszS1,constchar*pszS2,constchar*pszS3)(constchar*ps[3];ps[0]=pszS1;ps[1]=pszS2;ps[2]=pszS3;intiStr=0;for(inti=0;i<3;i++)(if(ps[i]!=NULL)iStr++;}//Checktoseeiftheeventsourcehasbeenregistered,//andifnotthenregisteritnow.if(!m_hEventSource)(m_hEventSource=::RegisterEventSource(NULL,//localmachinem_szServiceName);//sourcename}if(m_hEventSource)(::ReportEvent(m_hEventSource,wType,0,dwID,NULL,//sidiStr,0,ps,NULL);}如你所见,其主要工作是由ReportEvent系统函数处理。至此,我们已经可以通过调用CNTService::LogEvent在系统日志中记录事件了。接下来我们将考虑创建服务本身的一些代码。°编写服务代码为了建构一个简单的Win32服务,你需要知道的大多数信息都可以在PlatformSDK中找到。其中的范例代码都是用C语言写的,并且很好理解。我的CNTService类就是基于这些代码。一个服务主要包括三个函数:.main函数,这是代码的入口。我们正是在这里解析任何命令行参数并进行服务的安装,移除,启动等等。.在例子中,提供真正服务代码的入口函数叫ServiceMain。你可以随便叫它什么。在服务第一次启动的恶时候,将该函数的地址传递给服务管理器。.处理来自服务管理器命令消息的函数。在例子中,这个函数叫Handler,这个名字可以随意取。服务回调函数因为ServiceMain和Handler函数都是由系统来调用,所以它们必须遵循操作系统的参数传递规范和调用规范。也就是说,它们不能简单地作为某个C++类的成员函数。这样就给封装带来一些不便,因为我们想把Win32服务的功能封装在一个C++类中。为了解决这个问题,我将ServiceMain和Handler函数创建成CNTService类的静态成员。这样就使我得以创建可以由操作系统调用的函数。但是,这样做还没有完全解决问题,因为系统不允许给被调用的函数传递任何形式的用户数据,所以我们无法确定对C++对象特定实例的ServiceMain或Handler的调用。用了一个非常简单但有局限的方法来解决这个问题。我创建一个包含C++对象指针的静态变量。这个变量是在该对象首次创建是进行初始化的。这样便限制你每个服务应用只有一个C++对象。我觉得这个限制并不过分。下面是NTService.h文件中的声明:classCNTService([...]//静态数据staticCNTService*m_pThis;//nastyhacktogetobjectptr[...]};下面是初始化m_pThis指针的方法:CNTService::CNTService(constchar*szServiceName)(//Copytheaddressofthecurrentobjectsowecanaccessitfrom//thestaticmembercallbackfunctions.//WARNING:ThislimitstheapplicationtoonlyoneCNTServiceobject.m_pThis=this;[...]}°CNTService类当我创建C++对象封装Windows函数时,我尝试为我封装的每个WindowsAPI除了创建成员函数外,还做一些别的工作,我尝试让对象更容易使用,降低实现特定项目所需的代码行数。因此我的对象是基于''我想让这个对象做什么?”而不是“Windows用这些APIs做什么?"CNTService类包含一些用来解析命令行的成员函数,为了处理服务的安装和拆卸以及事件日志的记录,你得在派生类中重写一些虚拟函数来处理服务控制管理器的请求。下面我们将通过本文的例子服务实现来研究这些函数的使用。如果你想创建尽可能简单的服务,只需要重写CNTService::Run即可,它是你编写代码实现具体服务任务的地方。你还需要实现main函数。如果服务需要实现一些初始化。如从注册表读取数据,还需重写CNTService::OnInit。如果你要向服务发送命令消息,那么可以在服务中使用系统函数ControlService,重写CNTService::OnUserControl来处理请求。在例子应用程序中使用CNTServiceNTService在CMyService类中实现了它的大多数功能,CMyService由CNTService派生。MyService.h头文件如下://myservice.h#include"ntservice.h"classCMyService:publicCNTService(public:CMyService();virtualBOOLOnInit();virtualvoidRun();virtualBOOLOnUserControl(DWORDdwOpcode);voidSaveStatus();//Controlparametersintm_iStartParam;intm_iIncParam;//Currentstateintm_iState;};正像你所看到的,CMyService改写了CNTService的OnInit、Run和OnUserControl。它还有一个函数叫SaveStatus,这个函数被用于将数据写入注册表,那些成员变量用来保存当前状态。例子服务每隔一定的时间对一个整型变量进行增量处理。开始值和增量值都存在注册表的参数中。这样做并没有别的意图。只是为了简单示范。下面我们看看这个服务是如何实现的。°实现main函数有了从CNTService派生的CMyService,实现main函数很简单,请看NTServApp.cpp文件:intmain(intargc,char*argv[])(//创建服务对象CMyServiceMyService;//解析标准参数(安装,卸载,版本等.)if(!MyService.ParseStandardArgs(argc,argv))(//未发现任何标准参数,所以启动服务,//取消下面DebugBreak代码行的注释,//当服务启动后进入调试器,//DebugBreak();MyService.StartService();}//到这里,服务已经停止returnMyService.m_Status.dwWin32ExitCode;这里代码不多,但执行后却发生了很多事情,让我们一步一步来看。首先,我们创建一个MyService类的实例。构造函数设置初始化状态和服务名字(MyService.cpp):CMyService::CMyService():CNTService(〃NTServiceDemonstration")(m_iStartParam=0;m_iIncParam=1;m_iState=m_iStartParam;}接着调用ParseStandardArgs检查命令行是否包含服务安装(-i)、卸载(-u)以及报告其版本号(-v)的请求。CNTService::ParseStandardArgs分别调用CNTService::IsInstalled,CNTService::Install和CNTService::Uninstall来处理这些请求。如果没有可识别的命令行参数,则假设该服务控制管理器试图启动该服务并调用StartService。该函数直到服务停止运行才返回。当你调试完代码,即可把用于调试的代码行注释掉或删除。安装和卸载服务服务的安装由CNTService::Install处理,它用Win32服务管理器注册服务并在注册表中建立一个条目以支持服务运行时日志消息。服务的卸载由CNTService::Uninstall处理,它仅仅通知服务管理器该服务已经不再需要。CNTService::Uninstall不会删除服务实际的可执行文件。。编写服务代码现在我们来编写实现服务的具体代码。对于NTService例子,有三个函数要写。他们涉及初始化,运行服务的细节和响应控制请求。初始化注册表有一个给服务用来存储参数的地方:HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services我就是选择这里来存储我的服务配置信息。我创建了一个Parameters键,并在此存储我要保存的值。所以当服务启动时,OnInit函数被调用;这个函数从注册表中读取初始设置。BOOLCMyService::OnInit()(//Readtheregistryparameters.//Tryopeningtheregistrykey://HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\\ParametersHKEYhkey;charszKey[1024];strcpy(szKey,"SYSTEM\\CurrentControlSet\\Services\\");strcat(szKey,m_szServiceName);strcat(szKey,"''Parameters");if(RegOpenKeyEx(HKEY_LOCAL_MACHINE,szKey,0,KEY_QUERY_VALUE,&hkey)==ERROR_SUCCESS)(//Yesweareinstalled.DWORDdwType=0;DWORDdwSize=sizeof(m_iStartParam);RegQueryValueEx(hkey,"Start",NULL,&dwType,(BYTE*)&m_iStartParam,&dwSize);dwSize=sizeof(m_iIncParam);RegQueryValueEx(hkey,"Inc",NULL,&dwType,(BYTE*)&m_iIncParam,&dwSize);RegCloseKey(hkey);}//Settheinitialstate.m_iState=m_iStartParam;returnTRUE;}现在我们有了服务参数,我们便可以运行服务了。运行服务当Run函数被调用时将执行服务的主体代码。本文例子的这部分很简单:voidCMyService::Run()(while(m_bIsRunning)(//Sleepforawhile.DebugMsg("Myserviceissleeping(%lu)...〃,m_iState);Sleep(1000);//Updatethecurrentstate.m_iState+=m_iIncParam;}}注意,只要服务不终止,这个函数就不会退出。当有终止服务的请求时,CNTService::m_bIsRunning标志被置成FALSE。如果在服务终止时,你要实现清除操作,那么你还可以改写OnStop和/或OnShutdown。响应控制请求你可以用任何适合的方式与服务通讯一一命名管道,思想交流,便条等等一一对于一些简单的请求,用系统函数ControlService很容易实现。CNTService提供了一个处理器专门用于通过ControlService函数发送的非标准消息(也就是用户发送的消息)。本文例子用单一消息在注册表中保存当前服务的状态,以便其它应用程序能看到它。我不建议用这种方法来监控服务,因为它不是最佳方法,这只是比较容易编码实现而已。ControlService所能处理的用户消息必须在128到255这个范围。我定义了一个常量SERVICE_CONTROL_USER,128作为基值。范围内的用户消息被发送到CNTService::OnUserControl,在例子服务中,处理此消息的细节如下:BOOLCMyService::OnUserControl(DWORDdwOpcode)(switch(dwOpcode)(caseSERVICE_CONTROL_USER+0://Savethecurrentstatusintheregistry.SaveStatus();returnTRUE;default:break;returnFALSE;//saynothandled}SaveStatus是一个局部函数,用来在注册表中存储服务状态。°调试Win32服务main函数中包含一个对DebugBreak的调用,当服务第一次被启动时,它会激活系统调试器。你可以监控来自调试器命令窗口中的服务调试信息。你可以在服务中用CNTService::DebugMsg来报告调试期间感兴趣的事件。为了调试服务代码,你需要按照PlatformSDK文档中的要求安装系统调试器(WinDbg)。你也可以用VisualStudio自带的调试器调试Win32服务。有一点很重要,那就是当它被服务管理器控制时,你不能终止服务和单步执行,因为服务管理器会让服务请求超时并终止服务线程。所以你只能让服务吐出消息,跟踪其过程并在调试器窗口查看它们。当服务启动后(例如,从控制面板的''服务”中),调试器将在服务线程的挂起后启动。你需要通过单击“Go”按钮或按F5让继续运行。然后在调试器中观察服务的运行过程。下面是启动和终止服务的调试输出例子:ModuleLoad:WinDebug/NTService.exe(symbolloadingdeferred)ThreadCreate:Process=0,Thread=0ModuleLoad:C:\NT351\system32\NTDLL.DLL(symbolloadingdeferred)ModuleLoad:C:\NT351\system32\KERNEL32.DLL(symbolloadingdeferred)ModuleLoad:C:\NT351\system32\ADVAPI32.DLL(symbolloadingdeferred)ModuleLoad:C:\NT351\system32\RPCRT4.DLL(symbolloadingdeferred)ThreadCreate:Process=0,Thread=1***WARNING:symbolschecksumiswrong0x0005830f0x0005224fforC:\NT351\symbols\dll\NTDLL.DBGModuleLoad:C:\NT351\symbols\dll\NTDLL.DBG(symbolsloaded)ThreadTerminate:Process=0,Thread=1,ExitCode=0HardcodedbreakpointhitHardcodedbreakpointhit[](130):CNTService::CNTService()ModuleLoad:C:\NT351\SYSTEM32\RPCLTC1.DLL(symbolloadingdeferred)[NTServiceDemonstration](130):CallingStartServiceCtrlDispatcher()ThreadCreate:Process=0,Thread=2[NTService[NTServiceDemonstration](174):CNTService::ServiceMain()[NTServiceDemonstration](174)[NTServiceDemonstration](174)[NTServiceDemonstration](174)[NTServiceDemonstration](174)[NTServiceDemonstration](174)[NTServiceDemonstration](174)[NTServiceDemonstration](174)[NTServiceDemonstration](174)[NTServiceDemonstration](130)[NTServiceDemonstration](130)[NTServiceDemonstration](130)[NTServiceDemonstration](130)[NTServiceDemonstration](130)[NTServiceDemonstration](174)[NTServiceDemonstration](174)[NTServiceDemonstration](174)[NTServiceDemonstration](174)EnteringEnteringCNTService::Initialize()CNTService::SetStatus(3026680,2)Sleeping...CNTService::SetStatus(3026680,4)EnteringCNTService::Run()Sleeping...Sleeping...Sleeping...CNTService::Handler(1)EnteringCNTService::Stop()CNTService::SetStatus(3026680,3)LeavingCNTService::Stop()Updatingstatus(3026680,3)LeavingCNTService::Run()LeavingCNTService::Initialize()LeavingCNTService::ServiceMain()CNTService::SetStatus(3026680,1)ThreadTerminate:Process=0,Thread=2,ExitCode=0[NTServiceDemonstration](130):ReturnedfromStartServiceCtrlDispatcher()ModuleUnload:WinDebug/NTService.exeModuleUnload:C:\NT351\system32\NTDLL.DLLModuleUnload:C:\NT351\system32\KERNEL32.DLLModuleUnload:C:\NT351\system32\ADVAPI32.DLLModuleUnload:C:\NT351\system32\RPCRT4.DLLModuleUnload:C:\NT351\SYSTEM32\RPCLTC1.DLLThreadTerminate:Process=0,Thread=0,ExitCode=0ProcessTerminate:Process=0,ExitCode=0>。总结也许用C++创建Win32服务并不是最理想的,但使用单一的类来派生你自己的服务的确方便了你的服务开发工作。」最新评论[发表评论][文章投稿]■查看所有评论■推荐给好友■打印口为什么StartServiceCtrlDispatcher返回1063错误呢?(qmroom发表于2008-12-2416:41:00)□众里寻她千百度。太好了。(zwz_good发表于2007-11-3016:05:00)□如果删除正在运行的服务会发现SCM数据库里的记录不能完全清除,只是标记该记录为已禁用.(vcbull发表于2005-11-911:01:00)口上面说“所以我们无法确定对C++对象特定实例的ServiceMain或Handler的调用。用了一个非常简单但有局限的方法来解决这个问题。我创建一个包含C++对象指针的静态变量。这个变量是在该对象首次创建是进行初始化的。这样便限制你每个服务应用只有一个C++对象。我觉得这个限制并不过分”但是这样如果有多个对象会不会发生混乱?(ainilw发表于2005-9-1916:13:00)曰谢谢〜写得很好!(KenerToday发表于2005-8-1214:42:00)凹用vc6打开时(open.mak)会提示你转换。按“OK”键即可。(hangwire发表于2005-4-1915:45:00)D.mak文件可以打开工程(cool_ice发表于2005-4-1823:20:00)口你的程序被你改到怎么打开..我也不会...为什么连*.dsw文件也不见了...有人能告诉我怎么打吗?(caicoi发表于2005-4-1822:55:00)More...DLLs,Processes,andThreadsTechnicalArticlesCreatingaSimpleWin32ServiceinC++NigelThompsonMicrosoftDeveloperNetworkTechnologyGroupNovember1995AbstractThisarticledescribeshowtocreatesimpleMicrosoft®WindowsNT®servicesusingMicrosoftVisualC++®.TheservicesarecreatedusingasingleC++classthatprovidesasimpleinterfacebetweenyourserviceandtheoperatingsystem.Usingthisclassapproach,yourownimplementationissimplyamatterofoverridingafewvirtualfunctionsinthebaseclass.Threesampleapplicationsaccompanythisarticle:TheNTServicesampleisasimpleWin32servicebuiltusingthemethoddescribedinthisarticle.TheNTServCplsampleisaControlPanelappletthatcontrolstheNTServiceservice.TheNTServCtrlsampleapplicationisanexampleofastand-aloneapplicationthatcanbeusedtocontrolandmonitoraWin32service.IntroductionAserviceinMicrosoft®WindowsNT®isaprogramthatrunswheneverthecomputerisrunningtheoperatingsystem.Itdoesnotrequireausertobeloggedon.Servicesareneededtoperformuser-independenttaskssuchasdirectoryreplication,processmonitoring,orservicestoothermachinesonanetwork,suchassupportfortheInternetHTTPprotocol.CreatingaserviceforWindowsNTisnotparticularlyhard.Debuggingaserviceis,however,alittlemoredifficult.FormyownworkIprefertocreatemyapplicationsinC++usingMicrosoftVisualC++®.MostWin32servicesamplesareinC,soIthoughtitwouldbeinterestingtoseeifIcouldcreateaC++classtoperformtherudimentaryfunctionsofaWin32service.Asitturnsout,onecancreateWin32servicesinC++quitesimply.ThebaseclassIdevelopedforthisshouldbeanadequatestartingpointforyourownwork.Creatingaserviceinvolvesabitmorethatjusttheservicecode.Additionally,youmustwritecodeto:Reportwarningsanderrorsinthesystemorapplicationlogs.Youtypicallycan'tuseoutputtothescreensincetheremaybenouserloggedon.ControltheservicethrougheitheraseparateapplicationoraControlPanelapplet.Thisinvolvesimplementingacommunicationmechanismforyourservice.Installandremovetheservicefromthesystem.Mostotherserviceexamplesuseoneprogramtoinstalltheserviceandanothertoremoveit.Ibuiltthesefunctionsintotheserviceitselfsoyouhaveonlyone.EXEtodistribute.Youcanruntheserviceapplicationdirectlyfromthecommandlineandaskittoinstall,uninstall,orreportitsversioninformation.TheNTServicesamplesupportsthefollowingcommand-linearguments:-v,whichreportsthenameandversionnumberoftheservice-i,whichinstallstheservice-u,whichremovestheserviceBydefault,whenthesystemstartstheservicetherewillbenocommand-lineargumentspassedtoit.CreatingtheApplicationFrameworkIhavebeencreatingapplicationsbasedontheMicrosoftFoundationClassLibrary(MFC)fortoolong.WhenIinitiallysetouttobuildmyWin32service,IstartedwiththeVisualC++AppWizardandcreatedanSDI/MFCapplication.Iintendedtoremovethedocumentandviewclasses,icons,andsoonandjustleavetheframework.Asitturnsout,bythetimeyou'veremovedallthatstuffandthemainwindow(sincewecan'thaveone),thereisn'tanythingleft.Verysilly.SoIwentbacktoAppWizardandcreatedaConsoleApplicationprojectwithasinglesourcefilethatwouldcontainthemainentrypointfunction.IcalledthisfileNTServApp.cpp.IusedthcppextensionratherthanjustcbecauseIwantedtowritetheentireprojectusingC++ratherthanstraightC.We'lllookattheimplementationofthecodeinthisfilelater.SinceIwantedtobuildmyservicefromaC++class,IcreatedtheNTService.handNTService.cppfilesinwhichIwouldimplementtheCNTServicebaseclass.IalsocreatedtheMyService.handMyService.cppfilesinwhichIwouldimplementmyownserviceclass(CMyService)derivedfromCNTService.Again,welllookatthecodeabitlater.WhenIstartanewproject,Iliketogetsomethingworkingassoonaspossible,soIdecidedthatthefirstthingmyserviceshoulddoismakesomeentriesinthesystem'sapplicationlogfile.Havingimplementedamechanismformakingtheseentries,I'dbeabletotrackwhentheservicewasstarted,stopped,andsoon;I'dalsohaveawaytorecordanyerrorsthatmightoccurintheservice.MakinglogentriesturnedouttobemuchmorecomplicatedthanIthought.MakingLogEntriesIfiguredthatsincethelogfileswereapartoftheoperatingsystem,therewouldbesomeapplicationprogramminginterface(API)supportformakingentriesintothem.SoIbrokeoutmytrustyMSDNCDanddugarounduntilIfoundtheReportEventfunction.Nowifyoudon'tknowaboutthisstuff,you'dprobablythinkthatthisfunctionwouldneedtoknowinwhichlogfileyouwanttomaketheentry,andthetextofthemessageyouwanttoinsert.Well,that'ssortofwhatitdoes,buttosimplifyinternationalizationoferrormessages,thisfunctiontakesamessageIDandlooksupthemessageinamessagetableyouprovide.Sotheproblemisnotsomuchwhatmessageyouwanttoputinthelog,ashowtoaddthesemessagestoyourapplication.Here'sastep-by-stepguide:Createatextfilewiththeextension.MCcontainingthedescriptionsofthemessages.IcalledmineNTServMsg.mc.Theformatisveryspecificandiscoveredinthe"MessageCompiler"documentationinthePlatformSDK.Runthemessagecompiler(MC.EXE)againstyoursourcefile,whichbydefaultcreatesanoutputfilecalledMSG00001.BIN.Thecompileralsocreatesaheaderfile(inmycaseNTServMsg.h)andan.RCfile(NTServMsg.rc).Youneedtorepeatthisstepanytimeyouchangethe.MCfileinyourproject,soit'shandytoaddatoolentryinyourVisualC++menutodothis.Createan.RCfileforyourprojectand#includeinittheWINDOWS.Hfileandthe.RCfileproducedbythemessagecompiler.Includetheheaderfileproducedbythemessagecompilerinyourmainprojectheaderfilesoallmoduleshaveaccesstothesymbolicmessagenames.Let'slookatsomeofthesefilesinmoredetailsoyoucanseewhatyouneedtocreateandwhatthemessagecompilercreatesforyou.Wewon'tlookattheentiresetofmessages,justoneortwotoshowyouhowitworks.Here'sthefirstpartofmymessagesourcefile,NTServMsg.mc:CopyCodeMessageId=100SymbolicName=EVMSG_INSTALLEDLanguage=EnglishThe%1servicewasinstalled.MessageId=SymbolicName=EVMSG_REMOVEDLanguage=EnglishThe%1servicewasremoved.MessageId=SymbolicName=EVMSG_NOTREMOVEDLanguage=EnglishThe%1servicecouldnotberemoved..EachentryhasanIDvaluethat,ifnotspecificallyset,issimplyonemorethanthevalueassignedtothemessagebeforeit.Eachentryalsohasasymbolicnameforuseinyourcode,alanguageidentifier,andthetextoftheactualmessage.Messagescanspanmorethanonelineandareterminatedbyalinecontainingasingleperiodonitsown.Themessagecompileroutputsabinaryfiletobeusedasaresourceintheapplicationandtwofilesforinclusioninyoursourcecode.Here'smy.RCfile:CopyCode//NTServApp.rc#include<windows.h>//Includethemessagetableresourcescript//generatedbythemessagecompiler(MC).#include"NTServMsg.rc"Here'sthe.RCfilethemessagecompilergenerated:CopyCodeLANGUAGE0x9,0x1111MSG00001.binAsyoucansee,there'snotalotoftextinthesefiles!Thelastfilegeneratedbythemessagecompilerisaheaderfileforyoutoincludeinyourcode.Here'sjustapartoftheonegeneratedforthesample:CopyCode[]////MessageId:EVMSG_INSTALLED////MessageText:////The%1servicewasinstalled.//#defineEVMSG_INSTALLED0x00000064L////MessageId:EVMSG_REMOVED////MessageText:////The%1servicewasremoved.//#defineEVMSG_REMOVED0x00000065L[]Youmighthavenoticedthatseveralofmymessagesincludeargumentsubstitutionitems(%1andsoon).Let'sseehowtheseareusedinthecodewhenweactuallywanttowriteamessagetooneofthesystem'slogfiles.Foranexample,let'slookatthepartoftheinstallationcodethatrecordssuccessfulinstallationintheeventlog.ThisispartofmyCNTService::IsInstalledfunction:CopyCode[■■■■ILogEvent(EVENTLOG_INFORMATION_TYPE,EVMSG_INSTALLED,m_szServiceName);[....]LogEventisanotherCNTServicefunctionthatusesthetypeofevent(information,warning,orerror),theIDoftheeventmessage,anduptothreeargumentsubstitutionstringstoformthelogmessage:CopyCode//Thisfunctionmakesanentryintotheapplicationeventlog.voidCNTService::LogEvent(WORDwType,DWORDdwID,constchar*pszSl,constchar*pszS2,constchar*pszS3){constchar*ps[3];ps[0]=pszSl;ps[1]=pszS2;ps[2]=pszS3;intiStr=0;for(inti=0;i<3;i++){if(ps[i]!=NULL)iStr++;}//Checktoseeiftheeventsourcehasbeenregistered,//andifnotthenregisteritnow.if(!m_hEventSource){m_hEventSource=::RegisterEventSource(NULL,//localmachinem_szServiceName);//sourcename}if(m_hEventSource){::ReportEvent(m_hEventSource,wType,0,dwID,NULL,//sidiStr,0,ps,NULL);}}Asyoucansee,themajorityoftheworkishandledbytheReportEventsystemfunction.SonowwehaveawaytorecordeventsinthesystemeventlogbycallingNTService::LogEvent.Nowwecanmoveontocreatingsomeofthecodefortheserviceitself.WritingtheServiceCodeThePlatformSDKdocumentationcontainsmostofwhatyouneedtoknowinordertoconstructasimpleWin32service.ThesamplecodeinthesectionisinCandisquiteeasytofollow.IbasediCNTServiceclassonthematerialinthiscode.Aservicecontainsthreemajorfunctions:Amainfunctionthatistheentrypointofthecode.Thisiswhereweparseanycommand-lineargumentsandgettheserviceinstalled,removed,started,andsoon.Afunctionthatprovidestheentrypointfortheactualservicecode.IntheexamplesthisfunctioniscalledServiceMain,butyoucancallitanythingyoulike.Youpasstheaddressofthisfunctiontotheservicemanagerwhentheserviceisfirststarted.Afunctionthatprocessescommandmessagesfromtheservicemanager.IntheexamplesthisfunctioniscalledHandler,butyoucannameitanythingyoulike.ServiceCallbackFunctionsSincetheServiceMainandHandlerfunctionsarecalledfromthesystem,theymustconformtotheparameter-passingschemeandcallingconventionoftheoperatingsystem.Thismeanstheycan'tsimplybememberfunctionsofaC++class.Thisisslightlyinconvenient,sincewewanttoencapsulatethefunctionalityofaWin32serviceinasingleC++class.Togetaroundthisproblem,IcreatedmyServiceMainandHandlerfunctionsasstaticmembersofmyCNTServiceclass.Thisenabledmetocreatefunctionsthatarecallablebytheoperatingsystem.Thisdoesn'tprovideacompletesolution,however,becausethesystemdoesnotallowpassinganyformofuserdatatothecalledfunctions,sowehavenowaytoidentifyacalltoeitheServiceMainorHandlerwithaspecificinstanceofaC++object.Iuseaverysimplebutlimitingsolutiontothisproblem.IcreateastaticvariablethatcontainsapointertotheC++object.Thevariableisinitializedwhentheobjectisfirstcreated.ThislimitsyoutooneC++objectperserviceapplication.Ididnotconsiderthistobetoorestrictive.Here'sthedeclarationintheNTService.hfile:CopyCodeclassCNTService{[...]//staticdatastaticCNTService*m_pThis;//nastyhacktogetobjectptr[...]};Here'showthem_pThispointergetsinitialized:CopyCodeCNTService::CNTService(constchar*szServiceName){//Copytheaddressofthecurrentobjectsowecanaccessitfrom//thestaticmembercallbackfunctions.//WARNING:ThislimitstheapplicationtoonlyoneCNTServiceobject.m_pThis=this;[...]}TheCNTServiceClassWhenIcreateC++objectstoencapsulategroupsofMicrosoftWindows®functions,ItrytodomorethanjustmakeamemberfunctionforeachWindowsAPII'mencapsulating.Itrytomaketheobjecteasytouse,andItrytohelpreducethenumberoflinesofcodeneededtoimplementaparticularsectionofaproject.Somyobjectdesignsarebasedon"whatdoIwanttodowiththisobject?"ratherthan"whatdoesWindowsdowiththissetofAPIs?".TheCNTServiceclasscontainsmemberfunctionstoparseacommandline,tohandleinstallingandremovingtheservice,andtologevents,andasetofvirtualfunctionsthatyoucanoverrideinyourderivedclasstohandlerequestsfromtheservicecontrolmanager.We'lllookattheuseofmostofthesefunctionsaswegothroughtheimplementationofthesampleservice.Ifyouwanttocreatethesimplestpossibleservice,youneedonlyoverrideCNTService::Run,whichiswhereyouwritethecodetoperformwhatevertaskyourserviceprovides.Youalsoneedtoimplementthemainfunction.Ifyourserviceneedstoperformsomeinitialization,suchasreadingdatafromtheregistry,italsoneedstooverrideCNTService::OnInit.Ifyouneedtobeabletosendcommandmessagestoyourservice,youcandothisbyusingthControlServicesystemfunctionandhandlingtherequestsintheservicebyoverridingCNTService::OnUserControl.UsingCNTServiceintheSampleApplicationTheNTServicesampleimplementsmostofitsfunctionalityintheCMyServiceclass,whichisderivedfromCNTService.Here'stheMyService.hheaderfile:CopyCode//myservice.h#include"ntservice.h"classCMyService:publicCNTService{public:CMyService();virtualBOOLOnInit();virtualvoidRun();virtualBOOLOnUserControl(DWORDdwOpcode);voidSaveStatus();//Controlparametersintm_iStartParam;intm_iIncParam;//Currentstateintm_iState;};Asyoucansee,CMyServiceoverridesOnInit,Run,andOnUserControlfromCNTService.ItalsohasafunctioncalledSaveStatusthatisusedtowritedatatotheregistry,andsomemembervariablestoholdthecurrentstate.Thesampleserviceincrementsanintegervariableatregularintervals.Thestartvalueandincrementvaluearebothheldasparametersintheregistry.Notveryexciting,buteasyforyoutofollow.Let'smoveonnowtoseehowtheserviceisimplemented.ImplementingthemainFunctionHavingderivedCMyServicefromCNTService,it'snowasimplemattertoimplementthmainfunction(inNTServApp.cpp):CopyCodeintmain(intargc,char*argv[]){//Createtheserviceobject.CMyServiceMyService;//Parseforstandardarguments(install,uninstall,version,etc).if(!MyService.ParseStandardArgs(argc,argv)){//Didn'tfindanystandardargssostarttheservice.//UncommenttheDebugBreaklinebelowtoenterthedebugger//whentheserviceisstarted.//DebugBreak();MyService.StartService();}//Whenwegethere,theservicehasbeenstopped.returnMyService.m_Status.dwWin32ExitCode;}Notmuchcodetolookathere,butanawfullothappenswhenit'sexecuted,solet'sgothroughitstepbystep.Firstofall,wecreateaninstanceoftheMyServiceclass.Theconstructorsetstheinitialstateandnameoftheservice(MyService.cpp):CopyCodeCMyService::CMyService():CNTService("NTServiceDemonstration"){m_iStartParam=0;m_iIncParam=1;m_iState=m_iStartParam;}AcallisthenmadetoParseStandardArgstoseeifthecommandlinecontainsarequesttoinstalltheservice(-i),removeit(-u),orreportitsversionnumber(-v).CNTService::ParseStandardArgscallsCNTService::IsInstalled,CNTService::Install,andCNTService::Uninstalltoprocesstheserequests.Ifnorecognizablecommand-lineargumentsarefound,itisassumedthattheservicecontrolmanageristryingtostarttheserviceandacallismadetoStartService.Thisfunctiondoesnotreturnuntiltheservicestopsrunning.ThecalltoDebugBreakcausesabreakintothedebuggerwhentheserviceisfirststarted.Whenyouaredonedebuggingthecode,youcancommentoutordeletethisline.InstallingandRemovingtheServiceInstallingtheserviceishandledbyCNTService::Install,whichregisterstheservicewiththeWin32servicemanagerandmakesentriesintheregistrytosupportloggingmessageswhentheserviceisrunning.RemovingtheserviceishandledbyCNTService::Uninstall,whichsimplyinformstheservicemanagerthattheserviceisnolongerrequired.CNTService::Uninstalldoesnotremovetheactualserviceexecutablefile.WritingYourServiceCodeNowweneedtowritethecodethatactuallyimplementsyourservice.FortheNTServicesampletherearethreemajorfunctionstowrite.Thesecoverinitialization,actuallyrunningtheservice,andrespondingtocontrolrequests.InitializationTheregistryhasalocationforservicestostoretheirparameters:HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services.ThisiswhereIchosetostoretheconfigurationdataformyservice.IcreatedaParameterskeyandunderthatstoredthevaluesIwantedtosave.Sowhentheservicestarts,theOnInitfunctioniscalled;itreadstheinitialsettingsfromthisplaceintheregistry.CopyCodeBOOLCMyService::OnInit(){//Readtheregistryparameters.//Tryopeningtheregistrykey://HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\<AppName>\ParametersHKEYhkey;charszKey[1024];strcpy(szKey,"SYSTEM\\CurrentControlSet\\Services\\");strcat(szKey,m_szServiceName);strcat(szKey,"\\Parameters");if(RegOpenKeyEx(HKEY_LOCAL_MACHINE,szKey,0,KEY_QUERY_VALUE,&hkey)==ERROR_SUCCESS){//Yesweareinstalled.DWORDdwType=0;DWORDdwSize=sizeof(m_iStartParam);RegQueryValueEx(hkey,"Start",NULL,&dwType,(BYTE*)
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 工厂用工合同简单范本
- 设备电气安装合同
- 二零二五年度货车挂靠运营合作协议
- 二零二五年度可再生能源利用合作意向书合同范本
- 二零二五年度国际物流运输合同授权委托书
- 二零二五年度个人健身教练雇佣合同
- 二零二五年度风力发电工程款结算与环境保护合同
- 二零二五年度报废汽车拆解与环保资源化合同
- 二零二五年度特殊岗位人才聘用合同
- 二零二五年度个体工商户教育培训场地租赁合同
- 2025年春花城版(2024)小学音乐一年级下册教学计划
- 溶质的质量分数课件-九年级化学人教版(2024)下册
- 二零二五年度房屋租赁合同附带租户隐私保护协议
- 2025年上海市安全员《C证》考试题库及答案
- 2025年湖南汽车工程职业学院单招职业技能测试题库完整版
- 全国河大版(三起)小学信息技术第三册第1单元第1课《珍藏童年的回忆-文字输入和格式设置》教学设计
- 2025年新苏教版数学一年级下册课件 期末复习 第4课时 数据分类
- 《新能源汽车技术》课件-第二章 动力电池
- 拘留所被拘留人员管理教育
- 儿童饮食健康指南
- 民用无人机操控员执照(CAAC)考试复习重点题库500题(含答案)
评论
0/150
提交评论