4.1 服务控制程序.htm

来自「Windows2000后台服务程序开发手册」· HTM 代码 · 共 628 行 · 第 1/5 页

HTM
628
字号
            <HR style="LINE-HEIGHT: 25px">
            </A><A style="LINE-HEIGHT: 25px" name=204002>
            <P><FONT style="LINE-HEIGHT: 25px" face=arial color=#3e70d7 
            size=5><B style="LINE-HEIGHT: 25px">从SCM资料库中删除一个服务<BR 
            style="LINE-HEIGHT: 25px"> </B></FONT></P>
            <P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000 
            size=2>一个好的软件套件需要支援解除安装的功能,就像它支援安装功能一样。所以您也要了解如何移除一个服务。为了要将一个服务移除,您必须先开启它:</FONT></P>
            <DIV style="LINE-HEIGHT: 25px; BACKGROUND-COLOR: #d7d7d7"><FONT 
            style="LINE-HEIGHT: 25px" face=Arial size=3><PRE style="LINE-HEIGHT: 25px">SC_HANDLE OpenService( <BR> SC_HANDLE&nbsp;&nbsp; hSCManager, <BR> PCTSTR&nbsp;&nbsp;&nbsp;&nbsp;pszInternalName, <BR> DWORD&nbsp;&nbsp;&nbsp;&nbsp; dwDesiredAccess);</PRE></FONT></DIV>
            <P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000 
            size=2>在OpenService函数中,您经由服务的内部名称(与您在CreateService的pszServiceName参数中传递的值相同)而将被OpenSCManager回传的handle传递出去,然后将被要求的存取删除。现在您已经拥有了可指定服务的handle,那么您便可传递由OpenService回传的handle值,以经由呼叫DeleteService的方式来将服务删除:</FONT></P>
            <DIV style="LINE-HEIGHT: 25px; BACKGROUND-COLOR: #d7d7d7"><FONT 
            style="LINE-HEIGHT: 25px" face=Arial size=3><PRE style="LINE-HEIGHT: 25px">BOOL DeleteService(SC_HANDLE hService);</PRE></FONT></DIV>
            <P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000 
            size=2>DeleteService并没有真正地将服务删除,它只是被删除的服务标记起来而已。只有当服务停止执行以及在所有开启该服务的handle被关闭时,SCM才会删除该服务。</FONT></P>
            <P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000 
            size=2>我还将我的服务以可执行的方式编写,所以它们可以自行从SCM资料库中删除。若从命令列中传递「-remove」参数时,会呼叫像是ServiceRemove的函数,程序代码如下:</FONT></P>
            <DIV style="LINE-HEIGHT: 25px; BACKGROUND-COLOR: #d7d7d7"><FONT 
            style="LINE-HEIGHT: 25px" face=Arial size=3><PRE style="LINE-HEIGHT: 25px">void ServiceRemove(PCTSTR pszInternalName) { <BR> // 开启SCM资料库 <BR> SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT); <BR> // 开启服务以做删除动作 <BR> SC_HANDLE hService = OpenService(hSCM, pszInternalName, DELETE); <BR> //将被删除的服务做标记 <BR> //注意:除非所有被开启的handle已被关闭以及服务停止执行,否则该服务 <BR> //不会被删除 <BR> DeleteService(hService); <BR> // 关闭服务与SCM <BR> CloseServiceHandle(hService); <BR> CloseServiceHandle(hSCM); <BR>}</PRE></FONT></DIV>
            <HR style="LINE-HEIGHT: 25px">

            <P><FONT style="LINE-HEIGHT: 25px" face=Arial color=#3e77d7 size=3 
            Black><B style="LINE-HEIGHT: 25px">说明</B></FONT> </P>
            <P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000 
            size=2>为了使您更清楚的缘故,上述的程序代码中并不包含任何的错误检查。OpenSCManager、OpenService与DeleteService可能会因为任何的原因而执行失败。当您在应用程序中加入类似的程序代码时,请加入适当的错误检查机制。</FONT></P>
            <HR style="LINE-HEIGHT: 25px">
            </A><A style="LINE-HEIGHT: 25px" name=204003>
            <P><FONT style="LINE-HEIGHT: 25px" face=arial color=#3e70d7 
            size=5><B style="LINE-HEIGHT: 25px">启动与控制一个服务<BR 
            style="LINE-HEIGHT: 25px"> </B></FONT></P>
            <P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000 
            size=2>如前所述,许多服务皆包含在一个客户端应用程序中,以允许管理员可以去启动、停止、暂停、继续执行以及使用其他的方式来控制一个服务。编写一个服务控制程序是非常容易的。在此为您说明它如何工作:程序使用SC_MANAGER_ 
            CONNECT存取权限,并经由呼叫OpenSCManager函数,在被要求的机器上第一次开启SCM。然后程序会呼叫OpenService以开启您想要经由结合SERVICE_ 
            START, SERVICE_STOP、SERVICE_PAUSE_CONTINUE、SERVICE_USER_DEFINED_ 
            CONTROL、与SERVICE_INTERROGATE存取权限控制的服务。当服务被开启后,呼叫StartService即可启动它:</FONT></P>
            <DIV style="LINE-HEIGHT: 25px; BACKGROUND-COLOR: #d7d7d7"><FONT 
            style="LINE-HEIGHT: 25px" face=Arial size=3><PRE style="LINE-HEIGHT: 25px">BOOL StartService( <BR> SC_HANDLE&nbsp;&nbsp; hService, <BR> DWORD&nbsp;&nbsp;&nbsp;&nbsp; dwArgc, <BR> PCTSTR*&nbsp;&nbsp;&nbsp;&nbsp;pszArgv);</PRE></FONT></DIV>
            <P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000 
            size=2>hService参数指示了被开启的服务,而dwArgc与pszArgv参数则指示了您想要传递至服务的ServiceMain函数中之一组参数。大部份的服务不使用这些参数,所以通常会传递0与NULL给最后二个参数。请记得如果您启动的服务依存于其他服务或群组时,若启动一个服务即会导致许多服务一起启动。以下为一些StartService执行失败的主要原因:</FONT></P><FONT 
            style="LINE-HEIGHT: 25px" face=arial color=#000000 size=2>
            <UL style="LINE-HEIGHT: 25px">
              <LI 
              style="LINE-HEIGHT: 25px">从OpenService回传的handle没有拥有SERVICE_START的存取权。<BR 
              style="LINE-HEIGHT: 25px">  
              <LI style="LINE-HEIGHT: 25px">服务的可执行文件不在被指定的目录中。<BR 
              style="LINE-HEIGHT: 25px">  
              <LI style="LINE-HEIGHT: 25px">服务已经处于执行、停用或被标记为删除的状态。<BR 
              style="LINE-HEIGHT: 25px">  
              <LI style="LINE-HEIGHT: 25px">SCM的资料库已被锁定(本章稍后会讨论更多关于这方面的内容)。<BR 
              style="LINE-HEIGHT: 25px">  
              <LI style="LINE-HEIGHT: 25px">服务依存于另一个不存在或启动失败的服务。<BR 
              style="LINE-HEIGHT: 25px">  
              <LI style="LINE-HEIGHT: 25px">使用者帐户无法执行该服务。<BR 
              style="LINE-HEIGHT: 25px">  
              <LI style="LINE-HEIGHT: 25px">服务没有在一定的时间内回应要求。<BR 
              style="LINE-HEIGHT: 25px">  </LI></UL></FONT>
            <P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000 
            size=2>注意一旦服务的主要线程被建立起来后,StartService函数便会立即返回,所以服务不能准备去处理这些经由StartService返回的客户端要求之控制码或handle。并且,当它正在初始化或发生死结时(持续80秒),服务不能呼叫StartService。这个问题是因为当启动一个服务时,SCM会将SCM资料库锁定以预防另一个服务开始执行。</FONT></P>
            <P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000 
            size=2>一旦服务开始执行,您便可以呼叫ControlService传送控制给它:</FONT></P>
            <DIV style="LINE-HEIGHT: 25px; BACKGROUND-COLOR: #d7d7d7"><FONT 
            style="LINE-HEIGHT: 25px" face=Arial size=3><PRE style="LINE-HEIGHT: 25px">BOOL ControlService( <BR> SC_HANDLE&nbsp;&nbsp;&nbsp;&nbsp; hService, <BR> DWORD&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; dwControl, <BR> SERVICE_STATUS* pss);</PRE></FONT></DIV>
            <P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000 
            size=2>再一次说明,hService 
            p参数指示了您希望控制之被开启的服务。dwControl参数指示了您希望服务做什么而且它可为以下所列值之一:</FONT></P><FONT 
            style="LINE-HEIGHT: 25px" face=arial color=#000000 size=2>
            <UL style="LINE-HEIGHT: 25px">
              <LI style="LINE-HEIGHT: 25px">SERVICE_CONTROL_STOP<BR 
              style="LINE-HEIGHT: 25px">  
              <LI style="LINE-HEIGHT: 25px">SERVICE_CONTROL_PAUSE<BR 
              style="LINE-HEIGHT: 25px">  
              <LI style="LINE-HEIGHT: 25px">SERVICE_CONTROL_CONTINUE<BR 
              style="LINE-HEIGHT: 25px">  
              <LI style="LINE-HEIGHT: 25px">SERVICE_CONTROL_INTERROGATE<BR 
              style="LINE-HEIGHT: 25px">  
              <LI style="LINE-HEIGHT: 25px">SERVICE_CONTROL_PARAMCHANGE<BR 
              style="LINE-HEIGHT: 25px">  </LI></UL></FONT>
            <P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000 
            size=2>注意这些控制码与您的HandlerEx函数所接收的值相同(如第叁章中所讨论的)。除了这些值之外,您可以传送一个范围为128至255的使用者定义控制码。注意如果您传递一个SERVICE_CONTROL_SHUTDOWN的值,则ControlService会执行失败,只有系统可以传送这个控制码至一个服务的控制函数中。</FONT></P>
            <P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000 
            size=2>ControlService的最后一个参数pss必须指向一个SERVICE_STATUS结构。该函数会初始化这个结构的成员并回报服务之最后被回报的状态资料。您可以在ControlService返回后检查这个资讯,以察看服务如何工作。这里有一些ControlService可能会执行失败的主要原因:</FONT></P><FONT 
            style="LINE-HEIGHT: 25px" face=arial color=#000000 size=2>
            <UL style="LINE-HEIGHT: 25px">
              <LI style="LINE-HEIGHT: 25px">从OpenService回传的handle没有适当的存取权。<BR 
              style="LINE-HEIGHT: 25px">  
              <LI 
              style="LINE-HEIGHT: 25px">因为另一个服务依存于它,使得服务无法被停止。在这个情形下,您的应用程序必须先停止服务的依存关系。<BR 
              style="LINE-HEIGHT: 25px">  
              <LI 
              style="LINE-HEIGHT: 25px">控制码是无效的或是它不被服务所接受。请记得第叁章中所提,当它呼叫SetServiceStatus时,服务会设定SERVICE_STATUS结构的dwControlsAccepted成员。<BR 
              style="LINE-HEIGHT: 25px">  
              <LI 
              style="LINE-HEIGHT: 25px">因为服务回报了SERVICE_STOPPED、SERVICE_START_PENDING、或SERVICE_STOP_PENDING,使得控制码无法被传送至服务中。<BR 
              style="LINE-HEIGHT: 25px">  
              <LI style="LINE-HEIGHT: 25px">服务没有执行。<BR 
              style="LINE-HEIGHT: 25px">  
              <LI style="LINE-HEIGHT: 25px">务没有在一定的时间内(30秒内)被HandlerEx函数回传。<BR 
              style="LINE-HEIGHT: 25px">  </LI></UL></FONT>
            <P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000 
            size=2>无疑地,如果服务的控制函数处理了这个呼叫,则您会预期SERVICE_STATUS结构已被适当地初始化并且回传。然而如果您试图去传送一个SERVICE_CONTROL_INTERROGATE控制至一个被停止执行的服务时,您认为SERVICE_STATUS结构的内容会是什么呢?嗯,您将会很乐意知道Microsoft已经加强了ControlService函数,所以如果函数执行失败并跟随着一个ERROR_INVALID_SERVICE_CONTROL、ERROR_SERVICE_CANNOT_ACCEPT_CTRL或ERROR_SERVICE_NOT_ACTIVE的错误码时,它会回传一个有效的SERVICE_STATUS结构。以下的程序代码说明了如何停止一个服务的方法:</FONT></P>
            <DIV style="LINE-HEIGHT: 25px; BACKGROUND-COLOR: #d7d7d7"><FONT 
            style="LINE-HEIGHT: 25px" face=Arial size=3><PRE style="LINE-HEIGHT: 25px">void StopService(PCTSTR pszInternalName) { <BR> // 开启SCM与被要求的服务 <BR> SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT); <BR> SC_HANDLE hService = OpenService(hSCM, pszInternalName, <BR>&nbsp;&nbsp;SERVICE_STOP | SERVICE_QUERY_STATUS); <BR> // 告诉服务停止执行 <BR> SERVICE_STATUS ss; <BR> ControlService(hService, SERVICE_CONTROL_STOP, &amp;ss); <BR> // 等待至15秒以让服务停止执行 <BR> WaitForServiceState(hService, SERVICE_STOPPED, &amp;ss, 15000); <BR> // 关闭服务与SCM <BR> CloseServiceHandle(hService); <BR> CloseServiceHandle(hSCM); <BR>}</PRE></FONT></DIV>
            <HR style="LINE-HEIGHT: 25px">

            <P><FONT style="LINE-HEIGHT: 25px" face=Arial color=#3e77d7 size=3 
            Black><B style="LINE-HEIGHT: 25px">说明</B></FONT> </P>
            <P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000 
            size=2>为了更清楚地说明,上述的程序代码中并不包含任何的错误控制处理。OpenSCManager、OpenService与ControlService可能会因为许多的理由而执行失败。当您在应用程序中加入类似的程序代码时,请适当地加入错误控制机制。</FONT></P>
            <HR style="LINE-HEIGHT: 25px">

            <P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000 
            size=2>您应该注意到StopService呼叫了WaitForServiceState函数。WaitForServiceState函数并不是一个Windows函数,但也不是我所编写的函数,而它说明了如何适当地去处理一个服务的状态轮询。考虑以下的情形:使用服务嵌入式管理单元,您初始化了一个服务的停止要求,使得SCM去通知被选择的服务应该停止执行。该服务应该经由呼叫SetServiceStatus与在SERVICE_STATUS结构中,被设定为SERVICE_STOP_PENDING的dwCurrentState成员而回应。然而,服务还是没有被停止,所以服务嵌入式管理单元没有更新它的使用者介面,以反映服务已被停止的情形。不幸地,系统没有提供一个当服务状态改变时通知应用程序的方法,所以一个SCP必须周期性地轮询服务以决定何时改变它的状态。WaitForServiceState函数处理了这个轮询的动作。</FONT></P>
            <DIV style="LINE-HEIGHT: 25px; BACKGROUND-COLOR: #d7d7d7"><FONT 
            style="LINE-HEIGHT: 25px" face=Arial size=3><PRE style="LINE-HEIGHT: 25px">BOOL WaitForServiceState(SC_HANDLE hService, DWORD dwDesiredState,&nbsp;&nbsp;<BR> SERVICE_STATUS* pss, DWORD dwMilliseconds) { <BR> BOOL fServiceOk = TRUE; <BR> BOOL fFirstTime = TRUE; // 第一次不要比较状态/检查点 <BR> DWORD dwLastState = 0, dwLastCheckPoint = 0; <BR> DWORD dwTimeout = GetTickCount() + dwMilliseconds; <BR> // 递回至服务到达了被要求的状态、错误发生或逾时为止 <BR> for (;;) { <BR>&nbsp;&nbsp;// 取得目前服务的状态 <BR>&nbsp;&nbsp;fServiceOk = ::QueryServiceStatus(hService, pss); <BR>&nbsp;&nbsp;// 如果我们不能询问服务,我们会做 <BR>&nbsp;&nbsp;if (!fServiceOk)break; <BR>&nbsp;&nbsp;// 如果服务到达了被要求的状态,我们会做 <BR>&nbsp;&nbsp;if (pss-&gt;dwCurrentState == dwDesiredState) break; <BR>&nbsp;&nbsp;// 如果逾时,我们会做 <BR>&nbsp;&nbsp;if ((dwMilliseconds != INFINITE) &amp;&amp; (dwTimeout &lt; GetTickCount())) { <BR>&nbsp;&nbsp; fServiceOk = FALSE; <BR>&nbsp;&nbsp; SetLastError(ERROR_TIMEOUT); <BR>&nbsp;&nbsp; break; <BR>&nbsp;&nbsp;} <BR>&nbsp;&nbsp;// 如果第一次储存服务的状态/检查点 <BR>&nbsp;&nbsp;if (fFirstTime) { <BR>&nbsp;&nbsp; dwLastState&nbsp;&nbsp; = pss-&gt;dwCurrentState; <BR>&nbsp;&nbsp; dwLastCheckPoint = pss-&gt;dwCheckPoint; <BR>&nbsp;&nbsp; fFirstTime&nbsp;&nbsp; = FALSE; <BR>&nbsp;&nbsp;} else { <BR>&nbsp;&nbsp; // 如果不是第一次且状态被改变,则储存状态/检查点 <BR>&nbsp;&nbsp; if (dwLastState ! = pss-&gt;dwCurrentState) { <BR>&nbsp;&nbsp;&nbsp;&nbsp;dwLastState = pss-&gt;dwCurrentState; <BR>&nbsp;&nbsp;&nbsp;&nbsp;dwLastCheckPoint = pss-&gt;dwCheckPoint; <BR>&nbsp;&nbsp; } else { <BR>&nbsp;&nbsp;&nbsp;&nbsp;// 状态无法被改变;确定检查点没有被减少 <BR>&nbsp;&nbsp;&nbsp;&nbsp;if (pss-&gt;dwCheckPoint &gt;= dwLastCheckPoint) { <BR>&nbsp;&nbsp;&nbsp;&nbsp; // 好的检查点;储存它 <BR>&nbsp;&nbsp;&nbsp;&nbsp; dwLastCheckPoint = pss-&gt;dwCheckPoint; <BR>&nbsp;&nbsp;&nbsp;&nbsp;} else { <BR>&nbsp;&nbsp;&nbsp;&nbsp; // 坏的检查点、服务执行失败,我们会做 <BR>&nbsp;&nbsp;&nbsp;&nbsp; fServiceOk = FALSE; <BR>&nbsp;&nbsp;&nbsp;&nbsp; break; <BR>&nbsp;&nbsp;&nbsp;&nbsp;} <BR>&nbsp;&nbsp; } <BR>&nbsp;&nbsp;} <BR>&nbsp;&nbsp;// 我们没有完成;等待被指定的一段时间 <BR>&nbsp;&nbsp;// 等待提示的1/10轮询 <BR>&nbsp;&nbsp;DWORD dwWaitHint = pss-&gt;dwWaitHint / 10; <BR>&nbsp;&nbsp;// 最多一次一秒 <BR>&nbsp;&nbsp;if (dwWaitHint &lt; 1000) dwWaitHint = 1000; <BR>&nbsp;&nbsp;// 至少每个10秒 <BR>&nbsp;&nbsp;if (dwWaitHint &gt; 10000)dwWaitHint = 10000; <BR>&nbsp;&nbsp;Sleep(dwWaitHint); <BR> } <BR> // 注意:最后一个SERVICE_STATUS被回传至呼叫者处,所以呼叫者可以检 <BR> // 查服务状态与错误码 <BR> return(fServiceOk); <BR>}</PRE></FONT></DIV>
            <P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000 
            size=2>我们知道去做轮询的动作是一件麻烦的事,因为它浪费了宝贵的CPU循环时间,但是在这个情形下,我们真的没有其他选择。幸运地,情况并不像您所想的严重,因为SERVICE_STATUS结构包含了dwWaitHint成员。当一个服务呼叫SetServiceStatus时,dwWaitHint成员必须指示在执行下次轮询服务状态的动作前程序在传送控制码时应该等待多少个毫秒。</FONT></P>
            <P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000 
            size=2>服务控制程序也应该检查在确认它不会被减少的轮询处理程序期间,从服务被回传的检查点。如果一个服务回传了一个小于检查点的值,那么该服务控制程序应该假设服务已经执行失败。</FONT></P>
            <P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000 
            size=2>您将会通知WaitForServiceState去呼叫QueryServiceStatus:</FONT></P>
            <DIV style="LINE-HEIGHT: 25px; BACKGROUND-COLOR: #d7d7d7"><FONT 
            style="LINE-HEIGHT: 25px" face=Arial size=3><PRE style="LINE-HEIGHT: 25px">BOOL QueryServiceStatus( <BR> SC_HANDLE&nbsp;&nbsp;&nbsp;&nbsp; hService, <BR> SERVICE_STATUS* pss);</PRE></FONT></DIV>
            <P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000 
            size=2>QueryServiceStatus询问SCM以回传最后被快取的服务状态资讯(当服务最后被SetServiceStatus呼叫时设定)。呼叫QueryServiceStatus的方法就像透过传递SERVICE_CONTROL_INTERROGATE控制码呼叫ControlService一样,但是使用SERVICE_CONTROL_INTERROGATE呼叫ControlService时,会传送一个动作要求给服务以更新目前之状态

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?