Skip to content

ServiceManager 类

每服务配置对象。一个 ServiceManager 描述EXE知道如何托管的一个Windows服务——其 NameDescription、服务 TypeInstallStartModeInstanceCreator 以及SCM关心的可选字段——并暴露作用于单个服务的方法:InstallUninstall 以及服务运行时用于通知SCM状态转换的 ReportStatus 调用。

INFO

不要直接构造 ServiceManager 实例。改为调用 Services.ConfigureNew——它分配一个新的管理器并将其注册到包的内部集合中,以便调度器可以找到它。

vb
With Services.ConfigureNew
    .Name             = "MyService"
    .Description      = "An example twinBASIC service"
    .Type             = tbServiceTypeOwnProcess
    .InstallStartMode = tbServiceStartOnDemand
    .InstallCmdLine   = """" & App.ModulePath & """ -startService"
    .InstanceCreator  = New ServiceCreator(Of MyService)
End With

参见包概述了解更广泛的生命周期、双线程分离以及围绕安装的提升权限规则。

字段

LaunchArgs

SCM转发给服务的启动时参数。String()。由包的调度器跳板在SCM调用服务线程入口点时填充;不是配置字段。服务线程的 ITbService.EntryPoint 读取它以发现 Services.LaunchService(或SCM,或 sc.exe)传入的参数。

LaunchArgs(0)第一个用户提供的参数——SCM供应的作为 argv[0] 的服务名称在填充数组之前已被丢弃,因此索引与调用者的心智模型匹配。

vb
Sub EntryPoint(ByVal ServiceManager As ServiceManager) _
        Implements ITbService.EntryPoint
    If Join(ServiceManager.LaunchArgs) <> "MySecretPassword" Then
        ServiceManager.ReportStatus vbServiceStatusStopped, &H12345678
        Exit Sub
    End If
    ' ...稳态工作
End Sub

属性

AutoInitializeCOM

调度器跳板是否在调用 ITbService.EntryPoint 之前在服务线程上调用 CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)Boolean,默认 True

如果服务需要不同的单元模型,设置为 False——例如创建MTA工作池的服务。服务必须随后从其 EntryPoint 中自己调用 CoInitializeEx,然后才能接触COM感知的对象。

Description

services.mscsc.exe query 中列出的人类可读描述。String,无默认值。

该值由 Install 通过 ChangeServiceConfig2W(SERVICE_CONFIG_DESCRIPTION) 写入SCM,会应用于新服务或在每次重新安装时刷新。该字段必须在调用 Install 之前赋值;运行时更改直到下次安装才会生效。

DependentServices

此服务依赖的服务名称列表。Variant(),无默认值。

传入 Array("OtherSvc1", "OtherSvc2")。当SCM被要求启动该服务时,它首先自动启动列出的依赖项;如果任何依赖项启动失败,SCM会中止此服务的启动。包将数组打包为 CreateServiceW 期望的双空终止字符串。

vb
.DependentServices = Array("MSMQ", "LanmanServer")

InstallCmdLine

SCM启动服务宿主EXE时将使用的命令行。String,默认 """<App.ModulePath>"""(正在运行的EXE路径,带引号)。

默认值仅在EXE始终作为服务运行时足够。常规模式是覆盖默认值以添加鉴别参数,以便EXE的 Sub Main 可以辨别它处于哪种模式:

vb
.InstallCmdLine = """" & App.ModulePath & """ -startService"

Sub Main 中匹配的 If InStr(Command, "-startService") > 0 Then Services.RunServiceDispatcher 分支是使同一EXE既可作为安装程序/控制面板UI(正常启动时)又可作为服务宿主(由SCM启动时)工作的原因。

该值在 Install 时被捕获到SCM数据库中。安装后更改需要卸载并重新安装服务。

InstallStartMode

服务注册的SCM启动模式。ServiceStartConstants,默认 tbServiceStartOnDemand

典型设置:

仅驱动程序模式(tbServiceStartBoottbServiceStartDriverSystem)对用户模式twinBASIC服务没有意义。

InstanceCreator

调度器在SCM启动服务时用于创建 ITbService 实例的工厂。IServiceCreator,无默认值。

赋值为 New ServiceCreator(Of MyServiceClass),其中 MyServiceClass 是用户的 ITbService 实现:

vb
.InstanceCreator = New ServiceCreator(Of MyService)

RunServiceDispatcher 每次服务启动调用一次 InstanceCreator.CreateInstance()InstanceCreator 是可读写的——底层私有接口同时接受 LetSet 赋值,因此两种语法都可以。

如果只需要运行 Install / Uninstall(例如在独立安装程序内),InstanceCreator 可以保留为 Nothing——调度器仅在SCM实际启动服务时才需要它。

Name

服务在SCM数据库中的名称,由 services.mscsc.exe 使用。String,无默认值。

名称是SCM存储在 HKLM\SYSTEM\CurrentControlSet\Services\<Name> 的内容;它也是 Services.LaunchServiceServices.ControlServiceServices.QueryStateOfService 作为 ServiceName 参数接受的名称。相同的值用于SCM的 DisplayName——包目前不暴露独立的显示名称。

SupportsPausing

是否告知SCM服务接受 SERVICE_CONTROL_PAUSE / SERVICE_CONTROL_CONTINUE 通知。Boolean,默认 False

设置此属性后立即通过 SetServiceStatus 将缓存的 SERVICE_STATUS 重新同步到SCM,因此从 EntryPoint 内部切换——一旦服务过了 StartPending 阶段——在下次SCM查询时生效。大多数支持暂停的服务在 EntryPoint 顶部将属性设置为 True,并在 ChangeState 中处理 vbServiceControlPause / vbServiceControlContinue

如果设置 SupportsPausing 时服务尚未达到已启动状态,重新同步会引发运行时错误5 "Can't update the service state until the service has started"。等到第一次 ReportStatus(vbServiceStatusRunning) 调用之后再切换属性。

Type

Win32服务类型——控制服务是否在自己的进程、共享进程中运行,或者是内核驱动程序。ServiceTypeConstants,默认 tbServiceTypeOwnProcess

典型设置:

仅驱动程序模式(tbServiceTypeSystemDrivertbServiceTypeKernelDriver、…)对用户模式twinBASIC服务没有意义。

方法

Install

在SCM数据库中注册此服务。

语法:manager.Install

SC_MANAGER_CONNECT Or SC_MANAGER_CREATE_SERVICE 打开SCM,用已配置字段调用 CreateServiceW。如果具有相同 Name 的服务已经存在,方法会先删除它(通过 OpenServiceW(SERVICE_DELETE) + DeleteService)然后重试——因此对已存在的服务调用 Install 会覆盖现有注册而非失败。创建成功后,Description 通过 ChangeServiceConfig2W(SERVICE_CONFIG_DESCRIPTION) 写入。

WARNING

Install 写入SCM数据库,需要管理员权限。通常的做法是从提升的安装程序中调用一次,而不是从应用程序的正常启动路径中调用。从twinBASIC IDE内运行通常会失败——IDE很少以提升权限运行。

权限失败时引发运行时错误5,附带描述性消息("Unable to open the Service manager...")或不可恢复的创建失败("CreateServiceW() failed with error code <N>")。

ReportStatus

通知SCM服务的当前状态。由服务从 ITbService.EntryPoint 内部调用(以及从 ITbService.ChangeState 调用以确认待处理的转换)。

语法:manager.ReportStatus CurrentState [, Win32ExitCode [, WaitHint ] ]

CurrentState
必需 ServiceStatusConstants 值——通常为 vbServiceStatusRunningvbServiceStatusStopPendingvbServiceStatusStopped
Win32ExitCode
可选 Long 退出代码。默认 0NO_ERROR)。当因错误报告 vbServiceStatusStopped 时,传入Win32错误代码,或者对于服务特定代码使用魔术值 ERROR_SERVICE_SPECIFIC_ERROR(1066)并将实际代码放在服务特定字段中——但包的API只直接暴露 Win32ExitCode 参数。大多数服务为干净停止传入 0,为错误停止传入小的自定义代码。
WaitHint
可选 给SCM当前待处理转换预计所需的毫秒数上限的 Long。默认 0。仅对待处理状态有意义(vbServiceStatusStartPendingvbServiceStatusStopPendingvbServiceStatusPausePendingvbServiceStatusContinuePending)——SCM将其与自动递增的 dwCheckPoint 字段一起使用来检测卡住的服务。

ReportStatus 自动填充 SERVICE_STATUSdwControlsAccepted 字段——StopvbServiceStatusStartPending 期间以外总是被接受,Pause / ContinueSupportsPausingTrue 时被接受。dwCheckPoint 字段在服务处于待处理状态时自动递增,在 vbServiceStatusRunning / vbServiceStatusStopped 时重置为 0

包的调度器跳板在调用 EntryPoint 之前立即报告 vbServiceStatusStartPending;用户的 EntryPoint 负责后续的 vbServiceStatusRunningvbServiceStatusStopped 转换。

ResyncStatus

通过 SetServiceStatus 将缓存的 SERVICE_STATUS 重新应用到SCM。由 ReportStatusSupportsPausing 设置器自动调用;消费者代码很少需要直接调用此方法。

语法:manager.ResyncStatus

如果在服务获取其SCM状态句柄之前调用(即在调度器跳板调用 RegisterServiceCtrlHandlerExW 之前),引发运行时错误5 "Can't update the service state until the service has started"。从 EntryPoint 内部,ReportStatus 是正确的调用,而非直接调用 ResyncStatus

Uninstall

从SCM数据库中移除此服务。

语法:manager.Uninstall

打开SCM,以 SERVICE_DELETE 打开服务,调用 DeleteService。实际删除由SCM排队并在每个打开的服务句柄关闭后完成——services.msc 可能将服务显示为 "Marked for deletion" 直到宿主进程退出。

WARNING

Uninstall 需要管理员权限。如果无法打开SCM、服务未安装或 DeleteService 失败,引发运行时错误5,附带描述性消息。

另见

twinBASIC及其LOGO版权为作者"韦恩"所有