荔园在线

荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀

[回到开始] [上一篇][下一篇]


发信人: oopilix (。。), 信区: Visual
标  题: [zz]Windows服务编写原理及探讨(3)
发信站: 荔园晨风BBS站 (Sat Oct  4 18:20:15 2003), 站内信件

(三)对服务的深入讨论之下

  现在我们还剩下一个函数可以在细节上讨论,那就是服务的CtrlHandler函数


  当调用RegisterServiceCtrlHandler函数时,SCM得到并保存这个回调函数的
地址。一个SCP调一个告诉SCM如何去控制服务的Win32函数,现在已经有10个预定
义的控制请求:

Control code Meaning
SERVICE_CONTROL_STOP Requests the service to stop. The hService handle
must have SERVICE_STOP access.
SERVICE_CONTROL_PAUSE Requests the service to pause. The hService handle
 must have SERVICE_PAUSE_CONTINUE access.
SERVICE_CONTROL_CONTINUE Requests the paused service to resume. The
hService handle must have SERVICE_PAUSE_CONTINUE access.
SERVICE_CONTROL_INTERROGATE Requests the service to update immediately
its current status information to the service control manager. The
hService handle must have SERVICE_INTERROGATE access.
SERVICE_CONTROL_SHUTDOWN Requests the service to perform cleanup tasks,
 because the system is shutting down. For more information, see Remarks.

SERVICE_CONTROL_PARAMCHANGE Windows 2000: Requests the service to reread
 its startup parameters. The hService handle must have
SERVICE_PAUSE_CONTINUE access.
SERVICE_CONTROL_NETBINDCHANGE Windows 2000: Requests the service to
update its network binding. The hService handle must have
SERVICE_PAUSE_CONTINUE access.
SERVICE_CONTROL_NETBINDREMOVE Windows 2000: Notifies a network service
that a component for binding has been removed. The service should reread
 its binding information and unbind from the removed component.
SERVICE_CONTROL_NETBINDENABLE Windows 2000: Notifies a network service
that a disabled binding has been enabled. The service should reread
its binding information and add the new binding.
SERVICE_CONTROL_NETBINDDISABLE Windows 2000: Notifies a network
service that one of its bindings has been disabled. The service should
reread its binding information and remove the binding.

  上表中标有Windows 2000字样的就是2000中新添加的控制代码。除了这些代码
之外,服务也可以接受用户定义的,范围在128-255之间的代码。

  当CtrlHandler函数收到一个SERVICE_CONTROL_STOP、
SERVICE_CONTROL_PAUSE、 SERVICE_CONTROL_CONTINUE控制代码的时候,
SetServiceStatus必须被调用去确认这个代码,并指定你认为服务处理这个状态变
化所需要的时间。

  例如:你的服务收到了停止请求,首先要把SERVICE_STATUS结构的
dwCurrentState成员设置成SERVICE_STOP_PENDING,这样可以使SCM确定你已经收
到了控制代码。当一个服务的暂停或停止操作正在执行的时候,必须指定你认为这
种操作所需要的时间:这是因为一个服务也许不能立即改变它的状态,它可能必须
等待一个网络请求被完成或者数据被刷新到一个驱动器上。指定时间的方法就像我
上一章说的那样,用成员dwCheckPoint和dwWaitHint来指明它完成状态改变所需要
的时间。如果需要,可以用增加dwCheckPoint成员的值和设置dwWaitHint成员的值
去指明你期待的服务到达下一步的时间的方式周期性的报告进展情况。

  当整个启动的过程完成之后,要再一次调用SetServiceStatus。这时就要把
SERVICE_STATUS结构的dwCurrentState成员设置成SERVICE_STOPPED,当报告状态
代码的同时,一定要把成员dwCheckPoint和dwWaitHint设置为0,因为服务已经完
成了它的状态变化。暂停或继续服务的时候方法也一样。

  当CtrlHandler函数收到一个SERVICE_CONTROL_INTERROGATE控制代码的时候,
服务将简单的将dwCurrentState成员设置成服务当前的状态,同时,把成员
dwCheckPoint和dwWaitHint设置为0,然后再调用SetServiceStatus就可以了。

  在操作系统关闭的时候,CtrlHandler函数收到一个
SERVICE_CONTROL_SHUTDOWN控制代码。服务根本无须回应这个代码,因为系统即将
关闭。它将执行保存数据所需要的最小行动集,这是为了确定机器能及时关闭。缺
省时系统只给很少的时间去关闭所有的服务,MSDN里面说大概是20秒的时间,不过
那可能是Windows NT 4的设置,在我的Windows 2000 Server里这个时间是10秒,
你可以手动的修改这个数值,它被记录在
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control子键里面的
WaitToKillServiceTimeout,单位是毫秒。



  当CtrlHandler函数收到任何用户定义的代码时,它应该执行期望的用户自定
义行动。除非用户自定义的行动要强制服务去暂停、继续或停止,否则不调
SetServiceStatus函数。如果用户定义的行动强迫服务的状态发生变化,
SetServiceStatus将被调用去设置dwCurrentState、dwCheckPoint和dwWaitHint,
具体控制代码和前面说的一样。

  如果你的CtrlHandler函数需要很长的时间执行操作的话,千万要注意:假如
CtrlHandler函数在30秒内没有返回的话,SCM将返回一个错误,这不是我们所期望
的。所以如果出现上述情况,最好的办法是再建立一个线程,让它去继续执行操作
,以便使得CtrlHandler函数能够迅速的返回。例如,当收到一个
SERVICE_CONTROL_STOP请求的时候,就像上面说的一样,服务可能正在等待一个网
络请求被完成或者数据被刷新到一个驱动器上,而这些操作所需要的时间是你不能
估计的,那么就要建立一个新的线程等待操作完成后执行停止命令,CtrlHandler
函数在返回之前仍然要报告SERVICE_STOP_PENDING状态,当新的线程执行完操作之
后,再由它将服务的状态设置成SERVICE_STOPPED。如果当前操作的时间可以估计
的到就不要这样做,仍然使用前面交待的方法处理。

  CtrlHandler函数我就先讲这些,下面说说服务怎么安装。一个服务程序可以
使用CreateService函数将服务的信息添加到SCM的数据库。

SC_HANDLE CreateService( SC_HANDLE hSCManager, // handle to SCM database
 LPCTSTR lpServiceName, // name of service to start LPCTSTR
lpDisplayName, // display name DWORD dwDesiredAccess, // type of
access to service DWORD dwServiceType, // type of service DWORD
dwStartType, // when to start service DWORD dwErrorControl, //
severity of service failure LPCTSTR lpBinaryPathName, // name of
binary file LPCTSTR lpLoadOrderGroup, // name of load ordering group
LPDWORD lpdwTagId, // tag identifier LPCTSTR lpDependencies, // array of
 dependency names LPCTSTR lpServiceStartName, // account name LPCTSTR
lpPassword // account password );

  hSCManager是一个标示SCM数据库的句柄,可以简单的通过调用
OpenSCManager得到。

SC_HANDLE OpenSCManager( LPCTSTR lpMachineName, // computer name LPCTSTR
 lpDatabaseName, // SCM database name DWORD dwDesiredAccess // access
type );

  lpMachineName是目标机器的名字,还记得我在第一章里说过可以在其它的机
器上面安装服务吗?这就是实现的方法。对方机器名字必须以“\\”开始。如果传
递NULL或者一个空的字符串的话就默认是本机。

  lpDatabaseName是目标机器上面SCM数据库的名字,但MSDN里面说这个参数要
默认的设置成SERVICES_ACTIVE_DATABASE,如果传递NULL,就默认的打开
SERVICES_ACTIVE_DATABASE。所以我还没有真的搞明白这个参数的存在意义,总之
使用的时候传递NULL就行了。

  dwDesiredAccess是SCM数据库的访问权限,具体值见下表:

Object access Description
SC_MANAGER_ALL_ACCESS Includes STANDARD_RIGHTS_REQUIRED, in addition
to all of the access types listed in this table.
SC_MANAGER_CONNECT Enables connecting to the service control manager.
SC_MANAGER_CREATE_SERVICE Enables calling of the CreateService
function to create a service object and add it to the database.
SC_MANAGER_ENUMERATE_SERVICE Enables calling of the EnumServicesStatus
function to list the services that are in the database.
SC_MANAGER_LOCK Enables calling of the LockServiceDatabase function to
acquire a lock on the database.
SC_MANAGER_QUERY_LOCK_STATUS Enables calling of the
QueryServiceLockStatus function to retrieve the lock status
information for the database.

  想要获得访问权限的话,似乎没那么复杂。MSDN里面说所有进程都被允许获得
对所有SCM数据库的SC_MANAGER_CONNECT, SC_MANAGER_ENUMERATE_SERVICE, and
SC_MANAGER_QUERY_LOCK_STATUS权限,这些权限使得你可以连接SCM数据库,枚举
目标机器上安装的服务和查询目标数据库是否已被锁住。但如果要创建服务,首先
你需要拥有目标机器的管理员权限,一般的传递SC_MANAGER_ALL_ACCESS就可以了
。这个函数返回的句柄可以被CloseServiceHandle函数关闭。

  lpServiceName是服务的名字,lpDisplayName是服务在“服务”管理工具里显
示的名字。

  dwDesiredAccess也是访问的权限,有一个比上面的还长的多的一个表,各位
自己查MSDN吧。我们要安装服务,仍然简单的传递SC_MANAGER_ALL_ACCESS。

  dwServiceType是指你的服务是否和其它的进程相关联,一般是
SERVICE_WIN32_OWN_PROCESS,表示不和任何进程相关联。如果你确认你的服务需
要和某些进程相关联,就设置成SERVICE_WIN32_SHARE_PROCESS。当你的服务要和
桌面相关联的时候,需要设置成SERVICE_INTERACTIVE_PROCESS。

  dwStartType是服务的启动方式。服务有三种启动方式,分别是“自动
(SERVICE_AUTO_START)”“手动(SERVICE_DEMAND_START)”和“禁用
(SERVICE_DISABLED)”。在MSDN里还有另外的两种方式,不过是专为驱动程序设置
的。

  dwErrorControl决定服务如果在系统启动的时候启动失败的话要怎么办。

值 意义
SERVICE_ERROR_IGNORE 启动程序记录错误发生,但继续启动。
SERVICE_ERROR_NORMAL 启动程序记录错误发生,并弹出一个消息框,但仍继续启

SERVICE_ERROR_SEVERE 启动程序记录错误发生,如果是以last-known-good
configuration启动的话,启动会继续。否则会以last-known-good
configuration重新启动计算机。
SERVICE_ERROR_CRITICAL 启动程序记录错误发生,如果可能的话。如果是以
last-known-good configuration启动的话,启动会失败。否则会以
last-known-good configuration重新启动计算机。好严重的错误啊。

  lpBinaryPathName是服务程序的路径。MSDN里面特别提到如果服务路径里面有
空格的话一定要将路径用引号引起来。例如"d:\\my share\\myservice.exe"就一
定要指定为"\"d:\\my share\\myservice.exe\""。

  lpLoadOrderGroup的意义在于,如果有一组服务要按照一定的顺序启动的话,
这个参数用于指定一个组名用于标志这个启动顺序组,不过我还没有用过这个参数
。你的服务如果不属于任何启动顺序组,只要传递NULL或者一个空的字符串就行了


  lpdwTagId是应用了上面的参数之后要指定的值,专用于驱动程序,与本文内
容无关。传递NULL。

  lpDependencies标示一个字符串数组,用于指明一串服务的名字或者一个启动
顺序组。当与一个启动顺序组建立关联的时候,这个参数的含义就是只有你指定的
启动顺序组里有至少一个经过对整个组里所有的成员已经全部尝试过启动后,有至
少一个成员成功启动,你的服务才能启动。不需要建立依存关系的话,仍是传递
NULL或者一个空的字符串。但如果你要指定启动顺序组的话,必须为组名加上
SC_GROUP_IDENTIFIER前缀,因为组名和服务名是共享一个命名空间的。

  lpServiceStartName是服务的启动账号,如果你设置你的服务的关联类型是
SERVICE_WIN32_OWN_PROCESS的话,你需要以DomainName\UserName的格式指定用户
名,如果这个账户在你本机的话,用.\UserName就可以指定。如果传递NULL的话,
会以本地的系统账户登陆。如果是Win NT 4.0或更早的版本的话,如果你指定了
SERVICE_WIN32_SHARE_PROCESS,就必须传递.\System指定服务使用本地的系统账
户。最后,如果你指定了SERVICE_INTERACTIVE_PROCESS,你必须使服务运行在本
机系统账户。

  看名字就知道了,lpPassword是账户的密码。如果指定系统账户的话,传递
NULL。如果账户没有密码的话,传递空字符串。

  总之服务的基本原理就是这样子了,到了这里这篇文章似乎可以
告一段落了,但实际上还有很多内容必须要讨论,所以我还不能草草收笔,敬请关
注下一章。

--

※ 来源:·荔园晨风BBS站 bbs.szu.edu.cn·[FROM: 61.144.235.39]


[回到开始] [上一篇][下一篇]

荔园在线首页 友情链接:深圳大学 深大招生 荔园晨风BBS S-Term软件 网络书店