1、Windows 服务程序2006 年 10 月 14 日 星期六 13:28有那么一类应用程序,是能够为各种用户(包括本地用户和远程用户)所用的,拥有用户授权级进行管理的能力,并且不论用户是否物理的与正在运行该应用程序的计算机相连都能正常执行,这就是所谓的服务了。(一)服务的基础知识Question 1. 什么是服务?它的特征是什么?在 NT/2000 中,服务是一类受到操作系统优待的程序。一个服务首先是一个 Win32 可执行程序,如果要写一个功能完备且强大的服务,需要熟悉动态连接库(Dlls)、结构异常处理、内存映射文件、虚拟内存、设备 I/O、线程及其同步、Unicode 以及其他的由
2、WinAPI 函数提供的应用接口。当然本文讨论的只是建立一个可以安装、运行、启动、停止的没有任何其他功能的服务,所以无需上述知识仍可以继续看下去,我会在过程中将理解本文所需要的知识逐一讲解。第二要知道的是一个服务决不需要用户界面。大多数的服务将运行在那些被锁在某些黑暗的,冬暖夏凉的小屋子里的强大的服务器上面,即使有用户界面一般也没有人可以看到。如果服务提供任何用户界面如消息框,那么用户错过这些消息的可能性就极高了,所以服务程序通常以控制台程序的形式被编写,进入点函数是 main()而不是 WinMain()。也许有人有疑问:没有用户界面的话,要怎样设置、管理一个服务?怎样开始、停止它?服务如何
3、发出警告或错误信息、如何报告关于它的执行情况的统计数据?这些问题的答案就是服务能够被远程管理,Windows NT/2000 提供了大量的管理工具,这些工具允许通过网络上的其它计算机对某台机器上面的服务进行管理。比如 Windows 2000 里面的“控制台”程序(mmc.exe),用它添加“管理单元”就可以管理本机或其他机器上的服务。Question 2. 服务的安全性想要写一个服务,就必须熟悉 Win NT/2000 的安全机制,在上述操作系统之中,所有安全都是基于用户的。换句话说进程、线程、文件、注册表键、信号、事件等等等等都属于一个用户。当一个进程被产生的时候,它都是执行在一个用户的上
4、下文(context),这个用户帐号可能在本机,也可能在网络中的其他机器上,或者是在一个特殊的账号:System Account即系统帐号的上下文如果一个进程正在一个用户帐号下执行,那么这个进程就同时拥有这个用户所能拥有的一切访问权限,不论是在本机还是网络。系统帐号则是一个特殊的账号,它用来标识系统本身,而且运行在这个帐号下的任何进程都拥有系统上的所有访问权限,但是系统帐号不能在域上使用,无法访问网络资源服务也是 Win32 可执行程序,它也需要执行在一个 context,通常服务都是在系统账号下运行,但是也可以根据情况选择让它运行在一个用户账号下,也就会因此获得相应的访问资源的权限。Ques
5、tion 3. 服务的三个组成部分一个服务由三部分组成,第一部分是 Service Control Manager(SCM)。每个 Windows NT/2000 系统都有一个 SCM,SCM 存在于 Service.exe 中,在 Windows 启动的时候会自动运行,伴随着操作系统的启动和关闭而产生和终止。这个进程以系统特权运行,并且提供一个统一的、安全的手段去控制服务。它其实是一个 RPC Server,因此我们可以远程安装和管理服务,不过这不在本文讨论的范围之内。SCM 包含一个储存着已安装的服务和驱动程序的信息的数据库,通过SCM 可以统一的、安全的管理这些信息,因此一个服务程序的安
6、装过程就是将自身的信息写入这个数据库。第二部分就是服务本身。一个服务拥有能从 SCM 收到信号和命令所必需的的特殊代码,并且能够在处理后将它的状态回传给 SCM。第三部分也就是最后一部分,是一个 Service Control Dispatcher(SCP)。它是一个拥有用户界面,允许用户开始、停止、暂停、继续,并且控制一个或多个安装在计算机上服务的 Win32 应用程序。SCP 的作用是与 SCM 通讯,Windows 2000 管理工具中的“服务”就是一个典型的 SCP。在这三个组成部分中,用户最可能去写服务本身,同时也可能不得不写一个与其伴随的客户端程序作为一个 SCP 去和 SCM 通
7、讯,本文只讨论去设计和实现一个服务,关于如何去实现一个 SCP 则在以后的其它文章中介绍。Question 4. 怎样开始设计服务还记得前面我提到服务程序的入口点函数一般都是 main()吗?一个服务拥有很重要的三个函数,第一个就是入口点函数,其实用 WinMain()作为入口点函数也不是不可以,虽然说服务不应该有用户界面,但是其实存在很少的几个例外,这就是下面图中的选项存在的原因。由于要和用户桌面进行信息交互,服务程序有时会以 WinMain()作为入口点函数。入口函数负责初始化整个进程,由这个进程中的主线程来执行。这意味着它应用于这个可执行文件中的所有服务。要知道,一个可执行文件中能够包含
8、多个服务以使得执行更加有效。主进程通知 SCM 在可执行文件中含有几个服务,并且给出每一个服务的 ServiceMain 回调(Call Back)函数的地址。一旦在可执行文件内的所有服务都已经停止运行,主线程就在进程终止前对整个进程进行清除。第二个很重要的函数就是 ServiceMain,我看过一些例子程序里面对自己的服务的进入点函数都固定命名为 ServiceMain,其实并没有规定过一定要那样命名,任何的函数只要符合下列的形式都可以作为服务的进入点函数。VOID WINAPI ServiceMain(DWORD dwArgc, / 参数个数LPTSTR *lpszArgv / 参数串);
9、这个函数由操作系统调用,并执行能完成服务的代码。一个专用的线程执行每一个服务的 ServiceMain 函数,注意是服务而不是服务程序,这是因为每个服务也都拥有与自己唯一对应的 ServiceMain 函数,关于这一点可以用“管理工具”里的“服务”去察看 Win2000 里面自带的服务,就会发现其实很多服务都是由 service.exe 单独提供的。当主线程调用 Win32 函数 StartServiceCtrlDispatcher 的时候,SCM 为这个进程中的每一个服务产生一个线程。这些线程中的每一个都和它的相应的服务的 ServiceMain 函数一起执行,这就是服务总是多线程的原因一个
10、仅有一个服务的可执行文件将有一个主线程,其它的线程执行服务本身。第三个也就是最后的一个重要函数是 CtrlHandler,它必须拥有下面的原型:VOID WINAPI CtrlHandler(DWORD fdwControl /控制命令)像 ServiceMain 一样,CtrlHandler 也是一个回调函数,用户必须为它的服务程序中每一个服务写一个单独的 CtrlHandler 函数,因此如果有一个程序含有两个服务,那么它至少要拥有 5 个不同的函数:作为入口点的 main()或 WinMain(),用于第一个服务的 ServiceMain 函数和 CtrlHandler 函数,以及用于第
11、二个服务的 ServiceMain 函数和 CtrlHandler 函数。SCM 调用一个服务的 CtrlHandler 函数去改变这个服务的状态。例如,当某个管理员用管理工具里的“服务”尝试停止你的服务的时候,你的服务的 CtrlHandler 函数将收到一个 SERVICE_CONTROL_STOP 通知。CtrlHandler 函数负责执行停止服务所需的一切代码。由于是进程的主线程执行所有的 CtrlHandler 函数,因而必须尽量优化你的 CtrlHandler 函数的代码,使它运行起来足够快,以便相同进程中的其它服务的 CtrlHandler 函数能在适当的时间内收到属于它们的通知
12、。而且基于上述原因,你的 CtrlHandler 函数必须要能够将想要传达的状态送到服务线程,这个传递过程没有固定的方法,完全取决于你的服务的用途。(二)对服务的深入讨论之上上一章其实只是概括性的介绍,下面开始才是真正的细节所在。在进入点函数里面要完成 ServiceMain 的初始化,准确点说是初始化一个 SERVICE_TABLE_ENTRY 结构数组,这个结构记录了这个服务程序里面所包含的所有服务的名称和服务的进入点函数,下面是一个 SERVICE_TABLE_ENTRY 的例子:SERVICE_TABLE_ENTRY service_table_entry = “MyFTPd“ , F
13、tpdMain , “MyHttpd“, Httpserv, NULL, NULL ,;第一个成员代表服务的名字,第二个成员是 ServiceMain 回调函数的地址,上面的服务程序因为拥有两个服务,所以有三个 SERVICE_TABLE_ENTRY 元素,前两个用于服务,最后的 NULL 指明数组的结束。接下来这个数组的地址被传递到 StartServiceCtrlDispatcher 函数:BOOL StartServiceCtrlDispatcher(LPSERVICE_TABLE_ENTRY lpServiceStartTable)这个 Win32 函数表明可执行文件的进程怎样通知 S
14、CM 包含在这个进程中的服务。就像上一章中讲的那样,StartServiceCtrlDispatcher 为每一个传递到它的数组中的非空元素产生一个新的线程,每一个进程开始执行由数组元素中的 lpServiceStartTable 指明的 ServiceMain 函数。SCM 启动一个服务程序之后,它会等待该程序的主线程去调 StartServiceCtrlDispatcher。如果那个函数在两分钟内没有被调用,SCM 将会认为这个服务有问题,并调用 TerminateProcess 去杀死这个进程。这就要求你的主线程要尽可能快的调用 StartServiceCtrlDispatcher。St
15、artServiceCtrlDispatcher 函数则并不立即返回,相反它会驻留在一个循环内。当在该循环内时,StartServiceCtrlDispatcher 悬挂起自己,等待下面两个事件中的一个发生。第一,如果 SCM 要去送一个控制通知给运行在这个进程内一个服务的时候,这个线程就会激活。当控制通知到达后,线程激活并调用相应服务的 CtrlHandler 函数。CtrlHandler 函数处理这个服务控制通知,并返回到 StartServiceCtrlDispatcher。StartServiceCtrlDispatcher 循环回去后再一次悬挂自己。第二,如果服务线程中的一个服务中止
16、,这个线程也将激活。在这种情况下,该进程将运行在它里面的服务数减一。如果服务数为零,StartServiceCtrlDispatcher 就会返回到入口点函数,以便能够执行任何与进程有关的清除工作并结束进程。如果还有服务在运行,哪怕只是一个服务,StartServiceCtrlDispatcher 也会继续循环下去,继续等待其它的控制通知或者剩下的服务线程中止。上面的内容是关于入口点函数的,下面的内容则是关于 ServiceMain 函数的。还记得以前讲过的 ServiceMain 函数的的原型吗?但实际上一个 ServiceMain 函数通常忽略传递给它的两个参数,因为服务一般不怎么传递参数
17、。设置一个服务最好的方法就是设置注册表,一般服务在HKEY_LOCAL_MACHINESYSTEMCurrentControlSetServiceServiceNameParameters子键下存放自己的设置,这里的 ServiceName 是服务的名字。事实上,可能要写一个客户应用程序去进行服务的背景设置,这个客户应用程序将这些信息存在注册表中,以便服务读取。当一个外部应用程序已经改变了某个正在运行中的服务的设置数据的时候,这个服务能够用 RegNotifyChangeKeyValue 函数去接受一个通知,这样就允许服务快速的重新设置自己。前面讲到 StartServiceCtrlDispa
18、tcher 为每一个传递到它的数组中的非空元素产生一个新的线程。接下来,一个 ServiceMain 要做些什么呢?MSDN 里面的原文是这样说的:The ServiceMain function should immediately call the RegisterServiceCtrlHandler function to specify a Handler function to handle control requests. Next, it should call the SetServiceStatus function to send status information t
19、o the service control manager. 为什么呢?因为发出启动服务请求之后,如果在一定时间之内无法完成服务的初始化,SCM 会认为服务的启动已经失败了,这个时间的长度在 Win NT 4.0 中是 80 秒,Win2000 中不详基于上面的理由,ServiceMain 要迅速完成自身工作,首先是必不可少的两项工作,第一项是调用 RegisterServiceCtrlHandler 函数去通知 SCM 它的 CtrlHandler 回调函数的地址:SERVICE_STATUS_HANDLE RegisterServiceCtrlHandler(LPCTSTR lpServi
20、ceName, /服务的名字LPHANDLER_FUNCTION lpHandlerProc /CtrlHandler 函数地址) 第一个参数指明你正在建立的 CtrlHandler 是为哪一个服务所用,第二个参数是 CtrlHandler 函数的地址。lpServiceName 必须和在 SERVICE_TABLE_ENTRY 里面被初始化的服务的名字相匹配。RegisterServiceCtrlHandler 返回一个SERVICE_STATUS_HANDLE,这是一个 32 位的句柄。SCM 用它来唯一确定这个服务。当这个服务需要把它当时的状态报告给 SCM 的时候,就必须把这个句柄传给
21、需要它的 Win32 函数。注意:这个句柄和其他大多数的句柄不同,你无需关闭它。SCM 要求 ServiceMain 函数的线程在一秒钟内调用 RegisterServiceCtrlHandler 函数,否则 SCM 会认为服务已经失败。但在这种情况下,SCM 不会终止服务,不过在 NT 4 中将无法启动这个服务,同时会返回一个不正确的错误信息,这一点在 Windows 2000 中得到了修正。在 RegisterServiceCtrlHandler 函数返回后,ServiceMain 线程要立即告诉 SCM 服务正在继续初始化。具体的方法是通过调用 SetServiceStatus 函数传递
22、 SERVICE_STATUS 数据结构。BOOL SetServiceStatus(SERVICE_STATUS_HANDLE hService, /服务的句柄SERVICE_STATUS lpServiceStatus /SERVICE_STATUS 结构的地址) 这个函数要求传递给它指明服务的句柄(刚刚通过调用 RegisterServiceCtrlHandler 得到),和一个初始化的 SERVICE_STATUS 结构的地址:typedef struct _SERVICE_STATUSDWORD dwServiceType; DWORD dwCurrentState; DWORD d
23、wControlsAccepted; DWORD dwWin32ExitCode; DWORD dwServiceSpecificExitCode; DWORD dwCheckPoint; DWORD dwWaitHint; SERVICE_STATUS, *LPSERVICE_STATUS;SERVICE_STATUS 结构含有七个成员,它们反映服务的现行状态。所有这些成员必须在这个结构被传递到 SetServiceStatus 之前正确的设置。成员 dwServiceType 指明服务可执行文件的类型。如果你的可执行文件中只有一个单独的服务,就把这个成员设置成 SERVICE_WIN32_
24、OWN_PROCESS;如果拥有多个服务的话,就设置成 SERVICE_WIN32_SHARE_PROCESS。除了这两个标志之外,如果你的服务需要和桌面发生交互(当然不推荐这样做),就要用“OR”运算符附加上 SERVICE_INTERACTIVE_PROCESS。这个成员的值在你的服务的生存期内绝对不应该改变。成员 dwCurrentState 是这个结构中最重要的成员,它将告诉 SCM 你的服务的现行状态。为了报告服务仍在初始化,应该把这个成员设置成 SERVICE_START_PENDING。在以后具体讲述 CtrlHandler 函数的时候具体解释其它可能的值。成员 dwContro
25、lsAccepted 指明服务愿意接受什么样的控制通知。如果你允许一个 SCP 去暂停/继续服务,就把它设成 SERVICE_ACCEPT_PAUSE_CONTINUE。很多服务不支持暂停或继续,就必须自己决定在服务中它是否可用。如果你允许一个 SCP 去停止服务,就要设置它为 SERVICE_ACCEPT_STOP。如果服务要在操作系统关闭的时候得到通知,设置它为 SERVICE_ACCEPT_SHUTDOWN 可以收到预期的结果。这些标志可以用“OR”运算符组合。成员 dwWin32ExitCode 和 dwServiceSpecificExitCode 是允许服务报告错误的关键,如果希望
26、服务去报告一个 Win32 错误代码(预定义在 WinError.h 中),它就设置 dwWin32ExitCode 为需要的代码。一个服务也可以报告它本身特有的、没有映射到一个预定义的 Win32 错误代码中的错误。为了这一点,要把 dwWin32ExitCode 设置为 ERROR_SERVICE_SPECIFIC_ERROR,然后还要设置成员 dwServiceSpecificExitCode 为服务特有的错误代码。当服务运行正常,没有错误可以报告的时候,就设置成员 dwWin32ExitCode 为 NO_ERROR。最后的两个成员 dwCheckPoint 和 dwWaitHint
27、是一个服务用来报告它当前的事件进展情况的。当成员 dwCurrentState 被设置成 SERVICE_START_PENDING的时候,应该把 dwCheckPoint 设成 0,dwWaitHint 设成一个经过多次尝试后确定比较合适的数,这样服务才能高效运行。一旦服务被完全初始化,就应该重新初始化 SERVICE_STATUS 结构的成员,更改 dwCurrentState 为 SERVICE_RUNNING,然后把 dwCheckPoint 和 dwWaitHint 都改为 0。dwCheckPoint 成员的存在对用户是有益的,它允许一个服务报告它处于进程的哪一步。每一次调用 Se
28、tServiceStatus 时,可以增加它到一个能指明服务已经执行到哪一步的数字,它可以帮助用户决定多长时间报告一次服务的进展情况。如果决定要报告服务的初始化进程的每一步,就应该设置 dwWaitHint 为你认为到达下一步所需的毫秒数,而不是服务完成它的进程所需的毫秒数。在服务的所有初始化都完成之后,服务调用 SetServiceStatus 指明 SERVICE_RUNNING,在那一刻服务已经开始运行。通常一个服务是把自己放在一个循环之中来运行的。在循环的内部这个服务进程悬挂自己,等待指明它下一步是应该暂停、继续或停止之类的网络请求或通知。当一个请求到达的时候,服务线程激活并处理这个请
29、求,然后再循环回去等待下一个请求/通知。如果一个服务由于一个通知而激活,它会先处理这个通知,除非这个服务得到的是停止或关闭的通知。如果真的是停止或关闭的通知,服务线程将退出循环,执行必要的清除操作,然后从这个线程返回。当 ServiceMain 线程返回并中止时,引起在 StartServiceCtrlDispatcher 内睡眠的线程激活,并像在前面解释过的那样,减少它运行的服务的计数。(三)对服务的深入讨论之下 现在我们还剩下一个函数可以在细节上讨论,那就是服务的 CtrlHandler函数。当调用 RegisterServiceCtrlHandler 函数时,SCM 得到并保存这个回调函
30、数的地址。一个 SCP 调一个告诉 SCM 如何去控制服务的 Win32 函数,现在已经有 10 个预定义的控制请求:Control code MeaningSERVICE_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. SERVI
31、CE_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_INTERROG
32、ATE 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_CON
33、TINUE 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
34、 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 Win
35、dows 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、
36、 SERVICE_CONTROL_CONTINUE 控制代码的时候,SetServiceStatus 必须被调用去确认这个代码,并指定你认为服务处理这个状态变化所需要的时间。例如:你的服务收到了停止请求,首先要把 SERVICE_STATUS 结构的 dwCurrentState 成员设置成 SERVICE_STOP_PENDING,这样可以使 SCM 确定你已经收到了控制代码。当一个服务的暂停或停止操作正在执行的时候,必须指定你认为这种操作所需要的时间:这是因为一个服务也许不能立即改变它的状态,它可能必须等待一个网络请求被完成或者数据被刷新到一个驱动器上。指定时间的方法就像我上一章说的那样,
37、用成员 dwCheckPoint 和 dwWaitHint 来指明它完成状态改变所需要的时间。如果需要,可以用增加 dwCheckPoint 成员的值和设置 dwWaitHint 成员的值去指明你期待的服务到达下一步的时间的方式周期性的报告进展情况。当整个启动的过程完成之后,要再一次调用 SetServiceStatus。这时就要把 SERVICE_STATUS 结构的 dwCurrentState 成员设置成 SERVICE_STOPPED,当报告状态代码的同时,一定要把成员 dwCheckPoint 和 dwWaitHint 设置为 0,因为服务已经完成了它的状态变化。暂停或继续服务的时候
38、方法也一样。当 CtrlHandler 函数收到一个 SERVICE_CONTROL_INTERROGATE 控制代码的时候,服务将简单的将 dwCurrentState 成员设置成服务当前的状态,同时,把成员 dwCheckPoint 和 dwWaitHint 设置为 0,然后再调用 SetServiceStatus 就可以了。在操作系统关闭的时候,CtrlHandler 函数收到一个 SERVICE_CONTROL_SHUTDOWN 控制代码。服务根本无须回应这个代码,因为系统即将关闭。它将执行保存数据所需要的最小行动集,这是为了确定机器能及时关闭。缺省时系统只给很少的时间去关闭所有的服务
39、,MSDN 里面说大概是 20 秒的时间,不过那可能是Windows NT 4 的设置,在我的 Windows 2000 Server 里这个时间是 10 秒,你可以手动的修改这个数值,它被记录在 HKEY_LOCAL_MACHINESYSTEMCurrentControlSetControl 子键里面的 WaitToKillServiceTimeout,单位是毫秒。当 CtrlHandler 函数收到任何用户定义的代码时,它应该执行期望的用户自定义行动。除非用户自定义的行动要强制服务去暂停、继续或停止,否则不调 SetServiceStatus 函数。如果用户定义的行动强迫服务的状态发生变化
40、,SetServiceStatus 将被调用去设置 dwCurrentState、dwCheckPoint 和 dwWaitHint,具体控制代码和前面说的一样。如果你的 CtrlHandler 函数需要很长的时间执行操作的话,千万要注意:假如 CtrlHandler 函数在 30 秒内没有返回的话,SCM 将返回一个错误,这不是我们所期望的。所以如果出现上述情况,最好的办法是再建立一个线程,让它去继续执行操作,以便使得 CtrlHandler 函数能够迅速的返回。例如,当收到一个 SERVICE_CONTROL_STOP 请求的时候,就像上面说的一样,服务可能正在等待一个网络请求被完成或者数
41、据被刷新到一个驱动器上,而这些操作所需要的时间是你不能估计的,那么就要建立一个新的线程等待操作完成后执行停止命令,CtrlHandler 函数在返回之前仍然要报告 SERVICE_STOP_PENDING 状态,当新的线程执行完操作之后,再由它将服务的状态设置成 SERVICE_STOPPED。如果当前操作的时间可以估计的到就不要这样做,仍然使用前面交待的方法处理。CtrlHandler 函数我就先讲这些,下面说说服务怎么安装。一个服务程序可以使用 CreateService 函数将服务的信息添加到 SCM 的数据库。SC_HANDLE CreateService( SC_HANDLE hSC
42、Manager, / 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 f
43、ailure 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 是一个标示 S
44、CM 数据库的句柄,可以简单的通过调用 OpenSCManager 得到。SC_HANDLE OpenSCManager( LPCTSTR lpMachineName, / computer name LPCTSTR lpDatabaseName, / SCM database name DWORD dwDesiredAccess / access type );lpMachineName 是目标机器的名字,还记得我在第一章里说过可以在其它的机器上面安装服务吗?这就是实现的方法。对方机器名字必须以“”开始。如果传递 NULL 或者一个空的字符串的话就默认是本机。lpDatabaseName 是
45、目标机器上面 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
46、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 func
47、tion 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. 想要获
48、得访问权限的话,似乎没那么复杂。MSDN 里面说所有进程都被允许获得对所有 SCM 数据库的 SC_MANAGER_CONNECT, SC_MANAGER_ENUMERATE_SERVICE, and SC_MANAGER_QUERY_LOCK_STATUS 权限,这些权限使得你可以连接 SCM 数据库,枚举目标机器上安装的服务和查询目标数据库是否已被锁住。但如果要创建服务,首先你需要拥有目标机器的管理员权限,一般的传递 SC_MANAGER_ALL_ACCESS 就可以了。这个函数返回的句柄可以被 CloseServiceHandle 函数关闭。lpServiceName 是服务的名字,lp
49、DisplayName 是服务在“服务”管理工具里显示的名字。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 启动程