ITbService 接口
每个 WinServicesLib 项目中的服务类必须实现的契约。三个子过程,每个在服务生命周期的特定点被调用:
- EntryPoint -- 运行服务的实际工作。
- StartupFailed -- 当SCM握手在 EntryPoint 可以运行之前失败时调用。
- ChangeState -- 当SCM传递控制代码(Stop、Pause、Continue、…)时调用。
包的 ServiceCreator(Of T) 工厂为每次服务启动创建一个实例;调度器跳板在服务的整个生命周期内持有该实例,并将三个生命周期子过程路由到它。
[COMCreatable(False)]
Class MyService
Implements ITbService
Public IsStopping As Boolean
Sub EntryPoint(ByVal ServiceManager As ServiceManager) _
Implements ITbService.EntryPoint
ServiceManager.ReportStatus vbServiceStatusRunning
Do Until IsStopping
' ...做工作,然后用 WaitForSingleObject / Sleep 等让出
Loop
ServiceManager.ReportStatus vbServiceStatusStopped
End Sub
Sub ChangeState(ByVal ServiceManager As ServiceManager, _
ByVal dwControl As ServiceControlCodeConstants, _
ByVal dwEventType As Long, _
ByVal lpEventData As LongPtr) _
Implements ITbService.ChangeState
Select Case dwControl
Case vbServiceControlStop, vbServiceControlShutdown
ServiceManager.ReportStatus vbServiceStatusStopPending
IsStopping = True
End Select
End Sub
Sub StartupFailed(ByVal ServiceManager As ServiceManager) _
Implements ITbService.StartupFailed
' …可选的失败报告钩子
End Sub
End ClassWARNING
EntryPoint 运行在服务线程上。ChangeState 运行在调度器线程(EXE的主线程)上。两个方法并发执行,必须通过类上的共享 Public 标志进行协调——参见包概述上的双线程分离。
方法
ChangeState
当控制代码传递给服务时,由SCM调度器线程调用。
语法:service.ChangeState ServiceManager, dwControl, dwEventType, lpEventData
- ServiceManager
- 此服务的 ServiceManager——与传递给 EntryPoint 的实例相同。实现在其上调用 ReportStatus 以确认待处理的转换。
- dwControl
- 标识控制的 ServiceControlCodeConstants 值。SCM可能传递的标准代码包括 vbServiceControlStop、vbServiceControlShutdown、vbServiceControlPause、vbServiceControlContinue、vbServiceControlInterrogate,以及承载事件的代码(vbServiceControlSessionChange、vbServiceControlPowerEvent、vbServiceControlDeviceEvent、vbServiceControlHardwareProfileChange)。128--255范围内的用户定义代码也可以通过 Services.ControlService 传递。
- dwEventType
- 包含具有子代码的代码的事件类型子代码的 Long。否则为 0。参见Microsoft的
HandlerEx文档了解每个代码的解释。 - lpEventData
- 对于具有事件特定数据结构的代码,为指向该结构的 LongPtr。否则为
vbNullPtr。
典型模式是 Select Case dwControl 处理服务关心的代码并忽略其余代码。服务至少需要处理 Stop:
Select Case dwControl
Case vbServiceControlStop, vbServiceControlShutdown
ServiceManager.ReportStatus vbServiceStatusStopPending
IsStopping = True ' 向服务线程发出信号
End SelectChangeState 不会停止 EntryPoint——它只传递SCM的请求。用户的代码负责实际的关闭逻辑,通常通过设置服务线程轮询的共享 Public 标志(IsStopping)或调用 EntryPoint 拥有的阻塞原语上的信号方法(NamedPipeServer.ManualMessageLoopLeave、Win32事件句柄上的 SetEvent、…)。
该方法在与 EntryPoint 不同的线程上运行;参见双线程分离了解协调规则。
EntryPoint
服务的主例程。在SCM握手完成且跳板已报告 vbServiceStatusStartPending 后,由包的调度器跳板在SCM生成的服务线程上调用。
语法:service.EntryPoint ServiceManager
- ServiceManager
- 此服务的 ServiceManager。包含在
Sub Main期间设置的配置以及SCM传入的运行时 LaunchArgs。实现为其调用 ReportStatus 进行每个状态转换。
EntryPoint 的主体是服务的实际工作。最低职责:
- 可选地验证启动条件(通常通过检查 LaunchArgs)。失败路径应调用
ServiceManager.ReportStatus vbServiceStatusStopped, <ExitCode>和Exit Sub。 - 一旦达到稳定状态即调用
ServiceManager.ReportStatus vbServiceStatusRunning。 - 运行服务的长时间运行循环。循环通常阻塞在某个东西上(手动重置事件上的
WaitForSingleObject、NamedPipeServer.ManualMessageLoopEnter、自定义消息循环、…),当 ChangeState 通过共享标志发出关闭信号时跳出。 - 返回前调用
ServiceManager.ReportStatus vbServiceStatusStopped。
EntryPoint 子过程返回后,服务线程退出,SCM将服务标记为已停止。
WARNING
EntryPoint 运行在服务线程上,而非调度器线程。两个线程在服务生命周期内并发执行。实现类上的共享 Public 标志(IsStopping、IsPaused、…)协调从 ChangeState 触发的状态变更。
StartupFailed
当SCM握手在 EntryPoint 可以运行之前失败时调用。
语法:service.StartupFailed ServiceManager
- ServiceManager
- 此服务的 ServiceManager。
此子过程在 RegisterServiceCtrlHandlerExW 返回零句柄时触发——通常是因为服务在SCM上下文之外启动,或SCM的 RegisterServiceCtrlHandlerExW 拒绝了注册。服务在此状态下没有SCM状态句柄,因此 ServiceManager.ReportStatus 不能从 StartupFailed 内部调用——调用它会引发运行时错误5。
典型实现是仅记录日志的钩子,以便开发人员稍后可以找到失败:
Sub StartupFailed(ByVal ServiceManager As ServiceManager) _
Implements ITbService.StartupFailed
LogFailure service_startup_failed, status_changed, CurrentComponentName
End Sub如果没有有用的失败报告钩子可以添加,空实现也可以——SCM此时已经放弃了启动尝试,无法恢复。
另见
- WinServicesLib 包 -- 概述、生命周期、双线程分离
- ServiceManager 类 -- 传入每个方法的每服务对象
- ServiceCreator(Of T) 类 -- 为每次服务启动创建 ITbService 实例的工厂
- ServiceControlCodeConstants 枚举 -- ChangeState 分发的值
- ServiceStatusConstants 枚举 -- EntryPoint 通过 ServiceManager.ReportStatus 报告的值