ITbService interface

The contract every service class in a WinServicesLib project implements. Three subs, each invoked at a specific point in the service’s lifecycle:

  • EntryPoint — runs the service’s actual work.
  • StartupFailed — invoked when the SCM handshake fails before EntryPoint can run.
  • ChangeState — invoked when the SCM delivers a control code (Stop, Pause, Continue, …).

The package’s ServiceCreator(Of T) factory creates one instance per service start; the dispatcher trampoline holds the instance for the lifetime of the service and routes the three lifecycle subs to it.

[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
            ' ...do work, then yield with WaitForSingleObject / Sleep / etc.
        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
        ' …optional failure-reporting hook
    End Sub
End Class

Important

EntryPoint runs on the service thread. ChangeState runs on the dispatcher thread (the EXE’s main thread). The two methods execute concurrently and must coordinate through shared Public flags on the class — see The two-thread split on the package overview.

Methods

ChangeState

Invoked by the SCM dispatcher thread when a control code is delivered to the service.

Syntax: service.ChangeState ServiceManager, dwControl, dwEventType, lpEventData

ServiceManager
The ServiceManager for this service — the same instance passed to EntryPoint. Use it to call ReportStatus acknowledging the pending transition.
dwControl
A ServiceControlCodeConstants value identifying the control. Standard codes the SCM may deliver include vbServiceControlStop, vbServiceControlShutdown, vbServiceControlPause, vbServiceControlContinue, vbServiceControlInterrogate, and the event-bearing codes (vbServiceControlSessionChange, vbServiceControlPowerEvent, vbServiceControlDeviceEvent, vbServiceControlHardwareProfileChange). User-defined codes in the range 128–255 can also be delivered through Services.ControlService.
dwEventType
A Long carrying the event-type sub-code for the codes that have one. 0 otherwise. See Microsoft’s HandlerEx documentation for the per-code interpretation.
lpEventData
A LongPtr to an event-specific data structure for the codes that have one. vbNullPtr otherwise.

The typical pattern is a Select Case dwControl that handles the codes the service cares about and ignores the rest. The minimum a service needs to handle is Stop:

Select Case dwControl
    Case vbServiceControlStop, vbServiceControlShutdown
        ServiceManager.ReportStatus vbServiceStatusStopPending
        IsStopping = True       ' signal the service thread
End Select

ChangeState does not stop EntryPoint — it only delivers the SCM’s request. The user’s code is responsible for the actual shutdown logic, typically by setting a shared Public flag the service thread polls (IsStopping) or by calling a signal method on a blocking primitive that EntryPoint owns (NamedPipeServer.ManualMessageLoopLeave, SetEvent on a Win32 event handle, …).

The method runs on a different thread than EntryPoint; see The two-thread split for the coordination rules.

EntryPoint

The service’s main routine. Invoked by the package’s dispatcher trampoline on the SCM-spawned service thread once the SCM handshake has completed and the trampoline has reported vbServiceStatusStartPending.

Syntax: service.EntryPoint ServiceManager

ServiceManager
The ServiceManager for this service. Carries the configuration that was set during Sub Main plus the runtime LaunchArgs the SCM passed in. Use it to call ReportStatus on every state transition.

The body of EntryPoint is the service’s actual work. The minimum responsibilities:

  1. Optionally validate startup conditions (typically by inspecting LaunchArgs). Failure paths should call ServiceManager.ReportStatus vbServiceStatusStopped, <ExitCode> and Exit Sub.
  2. Call ServiceManager.ReportStatus vbServiceStatusRunning once steady-state is reached.
  3. Run the service’s long-running loop. The loop typically blocks on something (a WaitForSingleObject on a manual-reset event, a NamedPipeServer.ManualMessageLoopEnter, a custom message loop, …) and breaks out when ChangeState signals shutdown through a shared flag.
  4. Call ServiceManager.ReportStatus vbServiceStatusStopped before returning.

After the EntryPoint sub returns, the service thread exits and the SCM marks the service as stopped.

Important

EntryPoint runs on the service thread, not the dispatcher thread. The two threads execute concurrently for the lifetime of the service. Use shared Public flags on the implementing class (IsStopping, IsPaused, …) to coordinate state changes triggered from ChangeState.

StartupFailed

Invoked when the SCM handshake fails before EntryPoint can run.

Syntax: service.StartupFailed ServiceManager

ServiceManager
The ServiceManager for this service.

This sub fires when RegisterServiceCtrlHandlerExW returns a zero handle — typically because the service was launched outside the SCM context, or the SCM’s RegisterServiceCtrlHandlerExW rejected the registration. The service has no SCM status handle in this state, so ServiceManager.ReportStatus cannot be called from inside StartupFailed — calling it raises run-time error 5.

The typical implementation is a logging-only hook so the failure is recorded somewhere a developer can find it later:

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

If you have no useful failure-reporting hook to add, an empty implementation is fine — the SCM has already given up at this point and no recovery is possible.

See Also