Skip to content

Instantly share code, notes, and snippets.

@jbevain
Last active March 22, 2025 10:39
Show Gist options
  • Save jbevain/b295d4597a06de6b14a35710466ceed1 to your computer and use it in GitHub Desktop.
Save jbevain/b295d4597a06de6b14a35710466ceed1 to your computer and use it in GitHub Desktop.
// 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