说明
互动式的服务必须被设定在一个本机帐户下执行。若您试图在非本机帐户下加入一个互动式的服务,则CreateService无法增加服务至SCM资料库中。
如果CreateService成功的将一个服务加入至SCM资料库中,此时会回传一个非NULL的handle。由另一个函数来要求这个handle,以操作服务。当您要结束使用它时,请确定您已将这个handle传递给CloseServiceHandle函数。如果CreateService执行失败,它会回传一个NULL值,并且一个呼叫至GetLastError的函数会回传一个值,以指示执行失败的原因。以下为CreateService可能执行失败的大部份原因:
- 从OpenSCManager回传的handle中,没有SC_MANAGER_CREATE_SERVICE的存取。
- 新的服务指定了一个循环的依存关系。
- 服务的显示名称已经存在。
- 被指定的服务名称是无效的。
- 一个参数为无效的。
- 被指定的使用者帐户不存在。
我经常将服务编写成可执行的服务程序,所以它们能够自行安装。在我的(w)main或(w)WinMain函数中,若以命令列传递一个「-install」参数,则我会呼叫一个ServiceInstall函数(显示在以下的程序片段中)。在上一章中展示的TimeService范例服务程序即说明了这个技术。
void ServiceInstall(PCTSTR pszInternalName, PCTSTR pszDisplayName,
DWORD dwServiceType, DWORD dwStartType, DWORD dwErrorControl) {
// 开启SCM资料库以增加一个服务
SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
// 取得我们的服务之可执行完整路径
char szModulePathname [_MAX_PATH];
GetModuleFileName(NULL, szModulePathname, sizeof(szModulePathname));
// 增加此服务至SCM资料库
SC_HANDLE hService = CreateService(
hSCM, pszInternalName, pszDisplayName, 0, dwServiceType,
dwStartType, dwErrorControl, szModulePathname,
NULL, NULL, NULL, NULL, NULL);
// 关闭新建立之服务与SCM
CloseServiceHandle(hService);
CloseServiceHandle(hSCM);
}
说明
为了要更清楚地说明,所以前面所示之程序代码并没有处理任何的错误检查。所以OpenSCManager与CreateService可能会因为许多原因而执行失败。当您在增加类似上述程序代码至您的应用程序中时,请适当的增加错误检查机制。
从SCM资料库中删除一个服务
一个好的软件套件需要支援解除安装的功能,就像它支援安装功能一样。所以您也要了解如何移除一个服务。为了要将一个服务移除,您必须先开启它:
SC_HANDLE OpenService(
SC_HANDLE hSCManager,
PCTSTR pszInternalName,
DWORD dwDesiredAccess);
在OpenService函数中,您经由服务的内部名称(与您在CreateService的pszServiceName参数中传递的值相同)而将被OpenSCManager回传的handle传递出去,然后将被要求的存取删除。现在您已经拥有了可指定服务的handle,那么您便可传递由OpenService回传的handle值,以经由呼叫DeleteService的方式来将服务删除:
BOOL DeleteService(SC_HANDLE hService);
DeleteService并没有真正地将服务删除,它只是被删除的服务标记起来而已。只有当服务停止执行以及在所有开启该服务的handle被关闭时,SCM才会删除该服务。
我还将我的服务以可执行的方式编写,所以它们可以自行从SCM资料库中删除。若从命令列中传递「-remove」参数时,会呼叫像是ServiceRemove的函数,程序代码如下:
void ServiceRemove(PCTSTR pszInternalName) {
// 开启SCM资料库
SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
// 开启服务以做删除动作
SC_HANDLE hService = OpenService(hSCM, pszInternalName, DELETE);
//将被删除的服务做标记
//注意:除非所有被开启的handle已被关闭以及服务停止执行,否则该服务
//不会被删除
DeleteService(hService);
// 关闭服务与SCM
CloseServiceHandle(hService);
CloseServiceHandle(hSCM);
}说明
为了使您更清楚的缘故,上述的程序代码中并不包含任何的错误检查。OpenSCManager、OpenService与DeleteService可能会因为任何的原因而执行失败。当您在应用程序中加入类似的程序代码时,请加入适当的错误检查机制。
启动与控制一个服务
如前所述,许多服务皆包含在一个客户端应用程序中,以允许管理员可以去启动、停止、暂停、继续执行以及使用其他的方式来控制一个服务。编写一个服务控制程序是非常容易的。在此为您说明它如何工作:程序使用SC_MANAGER_ CONNECT存取权限,并经由呼叫OpenSCManager函数,在被要求的机器上第一次开启SCM。然后程序会呼叫OpenService以开启您想要经由结合SERVICE_ START, SERVICE_STOP、SERVICE_PAUSE_CONTINUE、SERVICE_USER_DEFINED_ CONTROL、与SERVICE_INTERROGATE存取权限控制的服务。当服务被开启后,呼叫StartService即可启动它:
BOOL StartService(
SC_HANDLE hService,
DWORD dwArgc,
PCTSTR* pszArgv);
hService参数指示了被开启的服务,而dwArgc与pszArgv参数则指示了您想要传递至服务的ServiceMain函数中之一组参数。大部份的服务不使用这些参数,所以通常会传递0与NULL给最后二个参数。请记得如果您启动的服务依存于其他服务或群组时,若启动一个服务即会导致许多服务一起启动。以下为一些StartService执行失败的主要原因:
- 从OpenService回传的handle没有拥有SERVICE_START的存取权。
- 服务的可执行文件不在被指定的目录中。
- 服务已经处于执行、停用或被标记为删除的状态。
- SCM的资料库已被锁定(本章稍后会讨论更多关于这方面的内容)。
- 服务依存于另一个不存在或启动失败的服务。
- 使用者帐户无法执行该服务。
- 服务没有在一定的时间内回应要求。
注意一旦服务的主要线程被建立起来后,StartService函数便会立即返回,所以服务不能准备去处理这些经由StartService返回的客户端要求之控制码或handle。并且,当它正在初始化或发生死结时(持续80秒),服务不能呼叫StartService。这个问题是因为当启动一个服务时,SCM会将SCM资料库锁定以预防另一个服务开始执行。
一旦服务开始执行,您便可以呼叫ControlService传送控制给它:
BOOL ControlService(
SC_HANDLE hService,
DWORD dwControl,
SERVICE_STATUS* pss);
再一次说明,hService p参数指示了您希望控制之被开启的服务。dwControl参数指示了您希望服务做什么而且它可为以下所列值之一:
- SERVICE_CONTROL_STOP
- SERVICE_CONTROL_PAUSE
- SERVICE_CONTROL_CONTINUE
- SERVICE_CONTROL_INTERROGATE
- SERVICE_CONTROL_PARAMCHANGE
注意这些控制码与您的HandlerEx函数所接收的值相同(如第叁章中所讨论的)。除了这些值之外,您可以传送一个范围为128至255的使用者定义控制码。注意如果您传递一个SERVICE_CONTROL_SHUTDOWN的值,则ControlService会执行失败,只有系统可以传送这个控制码至一个服务的控制函数中。
ControlService的最后一个参数pss必须指向一个SERVICE_STATUS结构。该函数会初始化这个结构的成员并回报服务之最后被回报的状态资料。您可以在ControlService返回后检查这个资讯,以察看服务如何工作。这里有一些ControlService可能会执行失败的主要原因:
- 从OpenService回传的handle没有适当的存取权。
- 因为另一个服务依存于它,使得服务无法被停止。在这个情形下,您的应用程序必须先停止服务的依存关系。
- 控制码是无效的或是它不被服务所接受。请记得第叁章中所提,当它呼叫SetServiceStatus时,服务会设定SERVICE_STATUS结构的dwControlsAccepted成员。
- 因为服务回报了SERVICE_STOPPED、SERVICE_START_PENDING、或SERVICE_STOP_PENDING,使得控制码无法被传送至服务中。
- 服务没有执行。
- 务没有在一定的时间内(30秒内)被HandlerEx函数回传。
无疑地,如果服务的控制函数处理了这个呼叫,则您会预期SERVICE_STATUS结构已被适当地初始化并且回传。然而如果您试图去传送一个SERVICE_CONTROL_INTERROGATE控制至一个被停止执行的服务时,您认为SERVICE_STATUS结构的内容会是什么呢?嗯,您将会很乐意知道Microsoft已经加强了ControlService函数,所以如果函数执行失败并跟随着一个ERROR_INVALID_SERVICE_CONTROL、ERROR_SERVICE_CANNOT_ACCEPT_CTRL或ERROR_SERVICE_NOT_ACTIVE的错误码时,它会回传一个有效的SERVICE_STATUS结构。以下的程序代码说明了如何停止一个服务的方法:
void StopService(PCTSTR pszInternalName) {
// 开启SCM与被要求的服务
SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
SC_HANDLE hService = OpenService(hSCM, pszInternalName,
SERVICE_STOP | SERVICE_QUERY_STATUS);
// 告诉服务停止执行
SERVICE_STATUS ss;
ControlService(hService, SERVICE_CONTROL_STOP, &ss);
// 等待至15秒以让服务停止执行
WaitForServiceState(hService, SERVICE_STOPPED, &ss, 15000);
// 关闭服务与SCM
CloseServiceHandle(hService);
CloseServiceHandle(hSCM);
}说明
为了更清楚地说明,上述的程序代码中并不包含任何的错误控制处理。OpenSCManager、OpenService与ControlService可能会因为许多的理由而执行失败。当您在应用程序中加入类似的程序代码时,请适当地加入错误控制机制。