作者:小 琛
欢迎转载,请标明出处
本博文针对Windows系统的service进行相关分享,注意内容为:作用、底层管理、开发使用API(根据开发基本流程)、其它注意事项
文章目录
Windows service
Windows对service的管理机制
相关API
Windows下的大部分函数,都有A版本和W版本,这里使用W版本(Unicode)作为例子
CloseServiceHandle
这里我将关闭句柄的函数放到了第一位,通常来说不合理,但我希望以此强调一点:对于service的任何操作,一定要在你的相关操作结束后,将句柄关闭,否则你可能会遇到各种奇怪的错误,并且很难排查,详细内容可以看标题“使用规范”
BOOL CloseServiceHandle(
[in] SC_HANDLE hSCObject
);
- 参数
- [in] hSCObject
服务控制管理器对象或要关闭的服务对象的句柄。服务控制管理器对象的句柄由 OpenSCManager函数返回,服务对象的句柄由 OpenService或 CreateService函数返回。
- 返回值
如果函数成功,则返回值非零。
如果函数失败,则返回值为零。要获取扩展的错误信息,请调用 GetLastError。
OpenSCManager
建立与指定计算机上的服务控制管理器的连接并打开指定的服务控制管理器数据库。 无论你是要对现有的service进行操作还是新创建service,都必须拿到可以操控本系统服务控制管理库的句柄。
SC_HANDLE OpenSCManagerW(
[in, optional] LPCWSTR lpMachineName,
[in, optional] LPCWSTR lpDatabaseName,
[in] DWORD dwDesiredAccess
);
- 参数
- lpMachineName
目标计算机的名称,如果设置为NULL,则默认为本计算机 - lpDatabaseName
服务控制管理器数据库的名称。此参数应设置为 SERVICES_ACTIVE_DATABASE。实际中,通常设置为NULL,系统默认帮我们设置为SERVICES_ACTIVE_DATABASE - dwDesiredAccess
对服务控制管理器的访问权限,尽可能缩小权限范围。详细内容,看这里
- 返回值
如果函数成功,则返回值是指定服务控制管理器数据库的句柄。
如果函数失败,则返回值为 NULL。要获取扩展的错误信息,请调用 GetLastError。 - 注意事项
- 调用失败通常是权限问题,如果当前用户在连接到另一台计算机上的服务时没有适当的访问权限,则 OpenSCManager函数调用将失败。
- 只有具有管理员权限的进程才能打开可由 CreateService函数使用的数据库句柄。
- 返回的句柄仅对调用 OpenSCManager函数的进程有效,且CloseServiceHandle函数可以关闭该句柄,这点是非常关键的,因此,对于服务尽可能做到:用时再拿句柄,用完即使释放,否则就会造成一些难以排查的错误。
CreateService
创建服务对象并将其添加到指定的服务控制管理器数据库。
SC_HANDLE CreateServiceW(
[in] SC_HANDLE hSCManager,
[in] LPCWSTR lpServiceName,
[in, optional] LPCWSTR lpDisplayName,
[in] DWORD dwDesiredAccess,
[in] DWORD dwServiceType,
[in] DWORD dwStartType,
[in] DWORD dwErrorControl,
[in, optional] LPCWSTR lpBinaryPathName,
[in, optional] LPCWSTR lpLoadOrderGroup,
[out, optional] LPDWORD lpdwTagId,
[in, optional] LPCWSTR lpDependencies,
[in, optional] LPCWSTR lpServiceStartName,
[in, optional] LPCWSTR lpPassword
);
- 参数
-
[in] hSCManager
服务控制管理器数据库的句柄。此句柄由 OpenSCManager函数返回,并且必须具有SC_MANAGER_CREATE_SERVICE访问权限。 -
[in] lpServiceName
要安装的服务的名称。最大字符串长度为 256 个字符。服务控制管理器数据库保留字符的大小写,但服务名称比较始终不区分大小写。正斜杠 (/) 和反斜杠 () 不是有效的服务名称字符。 -
[in, optional] lpDisplayName
用户界面程序用来标识服务的显示名称。此字符串的最大长度为 256 个字符。该名称在服务控制管理器中保留大小写。显示名称比较始终不区分大小写。 -
[in] dwDesiredAccess
对服务的访问权限,更多内容看这里 -
[in] dwServiceType
服务类型。此参数可以是以下值之一。
值 | 意义 |
---|---|
SERVICE_ADAPTER | 预订的 |
SERVICE_FILE_SYSTEM_DRIVER | 文件系统驱动服务 |
SERVICE_KERNEL_DRIVER | 核心驱动服务 |
SERVICE_RECOGNIZER_DRIVER | 预订的 |
SERVICE_WIN32_OWN_PROCESS | 在自己的进程中运行的服务 |
SERVICE_WIN32_SHARE_PROCESS | 与一个或多个其他服务共享进程的服务 |
- [in] dwStartType
服务启动选项。此参数可以是以下值之一。
值 | 意义 |
---|---|
SERVICE_AUTO_START | 预订的 |
SERVICE_BOOT_START | 由系统加载程序启动的设备驱动程序 |
SERVICE_DEMAND_START | 当进程调用StartService函数 时由服务控制管理器启动的服务 |
SERVICE_RECOGNIZER_DRIVER | 预订的 |
SERVICE_DISABLED | 无法启动的服务 |
SERVICE_SYSTEM_START | 由IoInitSystem函数启动的设备驱动程序 |
- [in] dwErrorControl
如果此服务无法启动,则错误的严重性和所采取的操作。此参数可以是以下值之一:
值 | 意义 |
---|---|
SERVICE_ERROR_CRITICAL | 如果可能,启动程序会在事件日志中记录错误。 |
SERVICE_ERROR_IGNORE | 启动程序忽略错误并继续启动操作 |
SERVICE_ERROR_NORMAL | 启动程序在事件日志中记录错误但继续启动操作 |
SERVICE_ERROR_SEVERE | 启动程序在事件日志中记录错误 |
-
[in, optional] lpBinaryPathName
服务二进制文件的完全限定路径。如果路径包含空格,则必须用引号引起来,以便正确解释。例如,“d:\my share\myservice.exe”应指定为“d:\my share\myservice.exe”。 -
[in, optional] lpLoadOrderGroup
此服务所属的负载排序组的名称。一帮设置为NULL -
[out, optional] lpdwTagId
指向变量的指针,该变量接收在lpLoadOrderGroup参数中指定的组中唯一的标记值。一般设置为NULL -
[in, optional] lpDependencies
指向系统必须在此服务之前启动的服务或加载排序组的空分隔名称的双空终止数组的指针。如果服务没有依赖项,则指定 NULL -
[in, optional] lpServiceStartName
服务应在其下运行的帐户的名称。 -
[in, optional] lpPassword
lpServiceStartName参数指定的帐户名的密码。
如果 lpServiceStartName参数指定的帐户名称是托管服务帐户的名称或虚拟帐户名称,则lpPassword参数必须为 NULL。
驱动程序服务将忽略密码。
- 返回值
如果函数成功,则返回值是服务的句柄。
如果函数失败,则返回值为 NULL。如果需要具体的错误信息,请调用 GetLastError。
关于错误,以下内容可以参考
错误码 | 解析 |
---|---|
ERROR_ACCESS_DENIED | SCM 数据库的句柄没有SC_MANAGER_CREATE_SERVICE访问权限 |
ERROR_CIRCULAR_DEPENDENCY | 指定了循环服务依赖项 |
ERROR_DUPLICATE_SERVICE_NAME | 显示名称已作为服务名称或另一个显示名称存在于服务控制管理器数据库中 |
ERROR_INVALID_HANDLE | 指定的服务控制管理器数据库的句柄无效 |
ERROR_INVALID_NAME | 指定的服务名称无效 |
ERROR_INVALID_PARAMETER | 指定的参数无效 |
ERROR_INVALID_SERVICE_ACCOUNT | 指定的服务名称无效 |
ERROR_INVALID_NAME | 指定的用户帐户名称不存在 |
ERROR_SERVICE_EXISTS | 此数据库中已存在指定的服务 |
ERROR_SERVICE_MARKED_FOR_DELETE | 此数据库中已存在指定的服务,并已标记为删除 |
最后一种错误,是一个非常恶心的内容,我在使用规范中将详细讲述,并帮助大家尽可能的避免
OpenService
打开现有服务。通常情况下,当我们想获取某个服务的句柄时,会使用该API,但切记,使用后调用CloseServiceHandle进行关闭
SC_HANDLE OpenServiceW(
[in] SC_HANDLE hSCManager,
[in] LPCWSTR lpServiceName,
[in] DWORD dwDesiredAccess
);
- 参数
-
[in] hSCManager
服务控制管理器数据库的句柄。OpenSCManager函数返回此句柄。 -
[in] lpServiceName
要打开的服务的名称。和创建服务对象时由CreateService函数的lpServiceName参数指定的名称对应
名称比较始终不区分大小写。正斜杠 (/) 和反斜杠 () 是无效的服务名称字符。 -
[in] dwDesiredAccess
对服务的访问权限,具体看这里
- 返回值
如果函数成功,则返回值是服务的句柄。
如果函数失败,则返回值为 NULL。要获取扩展的错误信息,请调用 GetLastError。
常见的错误,看这里
返回码 | 描述 |
---|---|
ERROR_ACCESS_DENIED | 该句柄无权访问该服务 |
ERROR_INVALID_HANDLE | 指定的句柄无效 |
ERROR_INVALID_NAME | 指定的服务名称无效 |
ERROR_SERVICE_DOES_NOT_EXIST | 指定的服务不存在 |
注意:返回的句柄仅对调用 OpenService的进程有效,因此仍要遵循,随时用,随时取,同时在使用结束后,调用 CloseServiceHandle函数来关闭。
StartService
启动现有的服务
BOOL StartServiceW(
[in] SC_HANDLE hService,
[in] DWORD dwNumServiceArgs,
[in, optional] LPCWSTR *lpServiceArgVectors
);
- 参数
-
[in] hService
服务的句柄。此句柄由 OpenService或 CreateService函数返回,它必须具有 SERVICE_START 访问权限。 -
[in] dwNumServiceArgs
lpServiceArgVectors数组中的字符串数。如果lpServiceArgVectors为 NULL,则此参数可以为零。 -
[in, optional] lpServiceArgVectors
要作为参数传递给服务的ServiceMain函数的以 null 结尾的字符串。如果没有参数,则此参数可以为 NULL。
- 返回值
如果函数成功,则返回值非零。
如果函数失败,则返回值为零。要获取扩展的错误信息,调用 GetLastError。
以下为一些你可能需要的错误处理情况:
返回码 | 描述 |
---|---|
ERROR_ACCESS_DENIED | 该句柄没有 SERVICE_START 访问权限。 |
ERROR_INVALID_HANDLE | 句柄无效 |
ERROR_SERVICE_ALREADY_RUNNING | 该句柄的另一个实例已经在运行 |
ERROR_SERVICE_DISABLED | 该服务已被禁用 |
ERROR_SERVICE_NO_THREAD | 无法为服务创建进程 |
ERROR_INVALID_HANDLE | 句柄无效 |
ERROR_SERVICE_REQUEST_TIMEOUT | 该服务的进程已启动,但它没有调用 StartServiceCtrlDispatcher,或者调用 StartServiceCtrlDispatcher 的线程 可能在控制处理程序函数中被阻塞。 |
ERROR_SERVICE_DEPENDENCY_DELETED | 该服务依赖于不存在或已标记为删除的服务 |
ERROR_SERVICE_MARKED_FOR_DELETE | 该服务已经被删除 |
最后两种情况,非常恶心,一定要在使用中避免让你的程序出现,具体内容我在使用规范中进行了总结。