Last active
March 22, 2025 10:39
-
-
Save jbevain/b295d4597a06de6b14a35710466ceed1 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Use in a .NET console project | |
// Set <PublishAot>true</PublishAot> and <AllowUnsafeBlocks>true</AllowUnsafeBlocks> in the .csproj | |
// Run dotnet publish | |
// Create service: sc create MyService binPath= "C:\full\path\to\Release\net9.0\win-x64\publish\WatchTemp.exe" | |
using System.Runtime.CompilerServices; | |
using System.Runtime.InteropServices; | |
class Service : IDisposable | |
{ | |
public static string Name = "MyService"; | |
private readonly ManualResetEventSlim _stopEvent = new(initialState: false); | |
public Service() | |
{ | |
// Service initialization | |
} | |
public void Run() | |
{ | |
// Service implementation | |
_stopEvent.Wait(); | |
} | |
public void Dispose() | |
{ | |
_stopEvent.Set(); | |
} | |
} | |
unsafe class ServiceRunner | |
{ | |
private static Service? _service; | |
private static ServiceStatus _status; | |
private static nint _handle; | |
private static char* _serviceName; | |
public static int Main(string[] args) | |
{ | |
_service = new Service(); | |
_serviceName = (char*)Marshal.StringToHGlobalUni(Service.Name); | |
ServiceTableEntry* serviceTable = stackalloc ServiceTableEntry[] | |
{ | |
new() | |
{ | |
lpServiceName = _serviceName, | |
lpServiceProc = &ServiceMain | |
}, | |
default | |
}; | |
if (!StartServiceCtrlDispatcherW(serviceTable)) | |
{ | |
return GetLastError(); | |
} | |
return 0; | |
} | |
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvStdcall) })] | |
private static void ServiceMain(int argc, char** argv) | |
{ | |
_handle = RegisterServiceCtrlHandlerW(_serviceName, &ServiceControlHandler); | |
_status.dwServiceType = ServiceType.OwnProcess; | |
_status.dwServiceSpecificExitCode = 0; | |
ReportServiceStatus(ServiceState.StartPending, 3000); | |
_service = new Service(); | |
ReportServiceStatus(ServiceState.Running, 0); | |
_service.Run(); | |
} | |
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvStdcall) })] | |
private static void ServiceControlHandler(ServiceControl control) | |
{ | |
switch (control) | |
{ | |
case ServiceControl.Stop: | |
case ServiceControl.Shutdown: | |
ReportServiceStatus(ServiceState.StopPending, 0); | |
try | |
{ | |
_service?.Dispose(); | |
} | |
catch | |
{ | |
} | |
ReportServiceStatus(ServiceState.Stopped, 0); | |
break; | |
} | |
} | |
private static void ReportServiceStatus(ServiceState state, int waitHint) | |
{ | |
_status.dwCurrentState = state; | |
_status.dwWin32ExitCode = 0; | |
_status.dwWaitHint = waitHint; | |
if (state == ServiceState.StartPending) | |
{ | |
_status.dwControlsAccepted = 0; | |
} | |
else | |
{ | |
_status.dwControlsAccepted = ServiceAccept.Stop | ServiceAccept.Shutdown; | |
} | |
if (state is ServiceState.Running or ServiceState.Stopped) | |
{ | |
_status.dwCheckPoint = 0; | |
} | |
else | |
{ | |
_status.dwCheckPoint++; | |
} | |
SetServiceStatus(_handle, ref _status); | |
} | |
[StructLayout(LayoutKind.Sequential)] | |
private struct ServiceTableEntry | |
{ | |
public char* lpServiceName; | |
public delegate* unmanaged[Stdcall]<int, char**, void> lpServiceProc; | |
} | |
private enum ServiceControl : int | |
{ | |
Stop = 0x00000001, | |
Shutdown = 0x00000005, | |
} | |
[Flags] | |
private enum ServiceType : int | |
{ | |
OwnProcess = 0x00000010 | |
} | |
private enum ServiceState : int | |
{ | |
Stopped = 0x00000001, | |
StartPending = 0x00000002, | |
StopPending = 0x00000003, | |
Running = 0x00000004 | |
} | |
[Flags] | |
private enum ServiceAccept : int | |
{ | |
Stop = 0x00000001, | |
Shutdown = 0x00000004 | |
} | |
[StructLayout(LayoutKind.Sequential)] | |
private struct ServiceStatus | |
{ | |
public ServiceType dwServiceType; | |
public ServiceState dwCurrentState; | |
public ServiceAccept dwControlsAccepted; | |
public int dwWin32ExitCode; | |
public int dwServiceSpecificExitCode; | |
public int dwCheckPoint; | |
public int dwWaitHint; | |
} | |
[DllImport("kernel32.dll", SetLastError = true)] | |
private static extern int GetLastError(); | |
[DllImport("advapi32.dll", SetLastError = true)] | |
[return: MarshalAs(UnmanagedType.Bool)] | |
private static extern bool StartServiceCtrlDispatcherW(ServiceTableEntry* serviceTable); | |
[DllImport("advapi32.dll", SetLastError = true)] | |
private static extern nint RegisterServiceCtrlHandlerW(char* serviceName, delegate* unmanaged[Stdcall]<ServiceControl, void> serviceControlHandler); | |
[DllImport("advapi32.dll", SetLastError = true)] | |
[return: MarshalAs(UnmanagedType.Bool)] | |
private static extern bool SetServiceStatus(nint serviceStatusHandle, ref ServiceStatus serviceStatus); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment