Skip to content

ITbService 接口

每个 WinServicesLib 项目中的服务类必须实现的契约。三个子过程,每个在服务生命周期的特定点被调用:

包的 ServiceCreator(Of T) 工厂为每次服务启动创建一个实例;调度器跳板在服务的整个生命周期内持有该实例,并将三个生命周期子过程路由到它。

vb
[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 Class

WARNING

EntryPoint 运行在服务线程上。ChangeState 运行在调度器线程(EXE的主线程)上。两个方法并发执行,必须通过类上的共享 Public 标志进行协调——参见包概述上的双线程分离

方法

ChangeState

当控制代码传递给服务时,由SCM调度器线程调用。

语法:service.ChangeState ServiceManager, dwControl, dwEventType, lpEventData

ServiceManager
此服务的 ServiceManager——与传递给 EntryPoint 的实例相同。实现在其上调用 ReportStatus 以确认待处理的转换。
dwControl
标识控制的 ServiceControlCodeConstants 值。SCM可能传递的标准代码包括 vbServiceControlStopvbServiceControlShutdownvbServiceControlPausevbServiceControlContinuevbServiceControlInterrogate,以及承载事件的代码(vbServiceControlSessionChangevbServiceControlPowerEventvbServiceControlDeviceEventvbServiceControlHardwareProfileChange)。128--255范围内的用户定义代码也可以通过 Services.ControlService 传递。
dwEventType
包含具有子代码的代码的事件类型子代码的 Long。否则为 0。参见Microsoft的 HandlerEx 文档了解每个代码的解释。
lpEventData
对于具有事件特定数据结构的代码,为指向该结构的 LongPtr。否则为 vbNullPtr

典型模式是 Select Case dwControl 处理服务关心的代码并忽略其余代码。服务至少需要处理 Stop

vb
Select Case dwControl
    Case vbServiceControlStop, vbServiceControlShutdown
        ServiceManager.ReportStatus vbServiceStatusStopPending
        IsStopping = True       ' 向服务线程发出信号
End Select

ChangeState 不会停止 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 的主体是服务的实际工作。最低职责:

  1. 可选地验证启动条件(通常通过检查 LaunchArgs)。失败路径应调用 ServiceManager.ReportStatus vbServiceStatusStopped, <ExitCode>Exit Sub
  2. 一旦达到稳定状态即调用 ServiceManager.ReportStatus vbServiceStatusRunning
  3. 运行服务的长时间运行循环。循环通常阻塞在某个东西上(手动重置事件上的 WaitForSingleObjectNamedPipeServer.ManualMessageLoopEnter、自定义消息循环、…),当 ChangeState 通过共享标志发出关闭信号时跳出。
  4. 返回前调用 ServiceManager.ReportStatus vbServiceStatusStopped

EntryPoint 子过程返回后,服务线程退出,SCM将服务标记为已停止。

WARNING

EntryPoint 运行在服务线程上,而非调度器线程。两个线程在服务生命周期内并发执行。实现类上的共享 Public 标志(IsStoppingIsPaused、…)协调从 ChangeState 触发的状态变更。

StartupFailed

当SCM握手在 EntryPoint 可以运行之前失败时调用。

语法:service.StartupFailed ServiceManager

ServiceManager
此服务的 ServiceManager

此子过程在 RegisterServiceCtrlHandlerExW 返回零句柄时触发——通常是因为服务在SCM上下文之外启动,或SCM的 RegisterServiceCtrlHandlerExW 拒绝了注册。服务在此状态下没有SCM状态句柄,因此 ServiceManager.ReportStatus 不能从 StartupFailed 内部调用——调用它会引发运行时错误5。

典型实现是仅记录日志的钩子,以便开发人员稍后可以找到失败:

vb
Sub StartupFailed(ByVal ServiceManager As ServiceManager) _
        Implements ITbService.StartupFailed
    LogFailure service_startup_failed, status_changed, CurrentComponentName
End Sub

如果没有有用的失败报告钩子可以添加,空实现也可以——SCM此时已经放弃了启动尝试,无法恢复。

另见

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