I'm trying to listen for macOS sleep/wake events using C# .Net Core in Visual Studio for Mac. After searching the forum and various documentation I've written some code that should apparently receive these notifications. Here it is:
using System;
using System.Runtime.InteropServices;
using System.Text;
using CFNotificationCenterRef = System.IntPtr;
namespace NativeMac
{
// I've noticed that some people are using this attribute to decorate their callback method.
[AttributeUsage(AttributeTargets.Method)]
public sealed class MonoPInvokeCallbackAttribute : Attribute
{
public MonoPInvokeCallbackAttribute(Type delegateType)
{
DelegateType = delegateType;
}
public Type DelegateType { get; }
}
// Delegate definition for a notification center callback.
delegate void CFNotificationCallback(CFNotificationCenterRef center, IntPtr observer, IntPtr name, IntPtr obj, IntPtr userInfo);
// Required enum for the call to CFNotificationCenterAddObserver.
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1008:Enums should have zero value", Justification = "There is no zero value specified for NotificationSuspensionBehavior.")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1028:Enum Storage should be Int32", Justification = "Required to be long to work with IntPtr.")]
public enum CFNotificationSuspensionBehavior : long
{
Drop = 1,
Coalesce = 2,
Hold = 3,
DeliverImmediately = 4
}
/// <summary>
/// The intention is for this class to allow us to register for the Mac's native sleep and wake events.
/// </summary>
public static class MacPowerMode
{
private enum CFStringEncoding : uint
{
UTF16 = 0x0100,
UTF16BE = 0x10000100,
UTF16LE = 0x14000100,
ASCII = 0x0600
}
/// <summary>
/// Native methods we can call on the Mac.
/// </summary>
private static class NativeMethods
{
private const string FoundationFramework = "/System/Library/Frameworks/Foundation.framework/Foundation";
private const string AppKitFramework = "/System/Library/Frameworks/AppKit.framework/AppKit";
[DllImport(AppKitFramework, CharSet = CharSet.Ansi)]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA2101:Specify marshaling for P/Invoke string arguments", Justification = "objc_getClass method requires CharSet.Ansi to work.")]
public static extern IntPtr objc_getClass(string name);
[DllImport(FoundationFramework, EntryPoint = "objc_msgSend")]
public static extern IntPtr objc_msgSend_retIntPtr(IntPtr target, IntPtr selector);
[DllImport(AppKitFramework)]
public static extern IntPtr NSSelectorFromString(IntPtr cfstr);
[DllImport(FoundationFramework)]
public static extern void CFRelease(IntPtr handle);
[DllImport(FoundationFramework)]
public static extern IntPtr CFStringCreateWithBytes(IntPtr allocator, IntPtr buffer, long bufferLength, CFStringEncoding encoding, bool isExternalRepresentation);
[DllImport(FoundationFramework)]
public static extern unsafe void CFNotificationCenterAddObserver(CFNotificationCenterRef center, IntPtr observer,
CFNotificationCallback callback, IntPtr name, IntPtr obj,
CFNotificationSuspensionBehavior suspensionBehavior);
}
public static void Initialise()
{
Console.WriteLine("Init Mac power mode test.");
// Get the appropriate notification centre for system notifications.
var nsWorkspace = NativeMethods.objc_getClass("NSWorkspace");
var sharedWorkspace = NativeMethods.objc_msgSend_retIntPtr(nsWorkspace, GetSelector("sharedWorkspace"));
var notificationCenter = NativeMethods.objc_msgSend_retIntPtr(sharedWorkspace, GetSelector("notificationCenter"));
// Create the names of the notifications that we will be listening for.
var nsWorkspaceWillSleepNotification = CreateCFString("NSWorkspaceWillSleepNotification");
var nsWorkspaceDidWakeNotification = CreateCFString("NSWorkspaceDidWakeNotification");
// Listen out for when the Mac is about to sleep. (Test by selecting 'sleep' from the Apple Menu.)
NativeMethods.CFNotificationCenterAddObserver(center: notificationCenter,
observer: notificationCenter,
callback: NotificationCallbackDelegate,
name: nsWorkspaceWillSleepNotification,
obj: IntPtr.Zero,
suspensionBehavior: CFNotificationSuspensionBehavior.DeliverImmediately);
// Listen out for when the Mac is awoken. (Test by pressing space bar whilst sleeping, to wake Mac.)
NativeMethods.CFNotificationCenterAddObserver(center: notificationCenter,
observer: notificationCenter,
callback: NotificationCallbackDelegate,
name: nsWorkspaceDidWakeNotification,
obj: IntPtr.Zero,
suspensionBehavior: CFNotificationSuspensionBehavior.DeliverImmediately);
}
private static IntPtr GetSelector(string name)
{
IntPtr cfstrSelector = CreateCFString(name);
IntPtr selector = NativeMethods.NSSelectorFromString(cfstrSelector);
NativeMethods.CFRelease(cfstrSelector);
return selector;
}
private static unsafe IntPtr CreateCFString(string aString)
{
var bytes = Encoding.Unicode.GetBytes(aString);
fixed (byte* b = bytes)
{
var cfStr = NativeMethods.CFStringCreateWithBytes(IntPtr.Zero, (IntPtr)b, bytes.Length, CFStringEncoding.UTF16, false);
return cfStr;
}
}
// Delegate definition for a notification center callback.
private delegate void CFNotificationCallback(CFNotificationCenterRef center, IntPtr observer, IntPtr name, IntPtr obj, IntPtr userInfo);
private static CFNotificationCallback NotificationCallbackDelegate = NotificationCallback;
[MonoPInvokeCallback(typeof(CFNotificationCallback))]
private static void NotificationCallback(CFNotificationCenterRef centerPtr, IntPtr observer, IntPtr name, IntPtr obj, IntPtr userInfo)
{
Console.WriteLine($"NotificationCallback received {name}");
}
}
}
There are no errors reported when I call the code, however the callback method never receives a notification for either sleep or wake. I've even tried creating a simple Cocoa app in XCode to see if I can receive the events at all, and that worked fine.
Various links that I've researched:
Apple Documentation Archive: Registering and unregistering for sleep and wake notifications
Apple Developer Documentation: CFNotificationCenter
Xamarin C# Implementation of CFNotificationCenter
What am I doing wrong?
EDIT (11:30 23/09/2020): I've created a static delegate and changed the code above accordingly as per Hans Passant's suggestion in the comments, the code runs but I still do not receive a notification.
I building a dll with golang, and need the dll receive a callback to trigger function directly from the dll
I tried execute callback on golang library but dont work
This is my code on golang
package main
import "C"
import "fmt"
type externFunc func(int)
//export Connect
func Connect(fn externFunc) {
fmt.Println("fn ",fn)
for i:= 0; i<3;i++{
// this is the func/method received from c#, and tried execute from golang
fn(i)
}
}
and so i build the library
go build -buildmode=c-shared -o library.dll main.go
this is my code on c#
class Program
{
static void Main(string[] args)
{
Connect()
}
private static unsafe void Connect()
{
Dll.CallbackDelegate cb = Callback;
// here sent the method to dll, to wait the multiple calls
Dll.Connect(cb);
}
private static unsafe void Callback(int num)
{
Console.WriteLine("call #: "+num);
}
}
class Dll {
private const string DllPath = "library.dll";
private IntPtr _dllHandle = IntPtr.Zero;
private static DllHelper _instance;
public static DllHelper Instance
{
get { return _instance ?? (_instance = new DllHelper()); }
}
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr LoadLibrary(string libname);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern bool FreeLibrary(IntPtr hModule);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern void FreeLibraryAndExitThread(IntPtr hModule, UInt32 dwExitCode);
[DllImport("kernel32.dll", CharSet = CharSet.Ansi)]
private static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);
internal Dll()
{
_dllHandle = IntPtr.Zero;
LoadDLL();
}
internal void LoadDLL()
{
bool? freeResult = null;
if (_dllHandle != IntPtr.Zero)
{
freeResult = FreeLibrary(_dllHandle);
}
// Load dll ..
_dllHandle = LoadLibrary(DllPath);
if (_dllHandle == IntPtr.Zero)
{
int errorCode = Marshal.GetLastWin32Error();
// Marshal.
throw new Exception(string.Format("Failed to load library (ErrorCode: {0})", errorCode));
}
}
and this part load the function from library
public unsafe delegate void CallbackDelegate(int num);
private unsafe delegate void ConnectDelegate(CallbackDelegate pFunc);
internal unsafe void Connect(CallbackDelegate pFunc)
{
var funcaddr = GetProcAddress(_dllHandle, "Connect");
var function = Marshal.GetDelegateForFunctionPointer(funcaddr, typeof(ConnectDelegate)) as
ConnectDelegate;
function.Invoke(pFunc);
}
}
this is the result when tried execute my console program created on C# with the go library
unexpected fault address 0xffffffffffffffff
fatal error: fault
[signal 0xc0000005 code=0x0 addr=0xffffffffffffffff pc=0x627ca9ab]
I think need receive an uinptr or something like that in the Connect and convert
the pointer on a type func but reading on other post says the type externFunc func is an func signature, and this is already a pointer.
Any idea what I need to make callbacks?
I want to get a handle to the main window of the process I just started by calling Process.Start().
This is easy when the process is not already running as the result of Process.Start() is not null:
var process = Process.Start(fileName);
if (process != null)
{
var handle = process.MainWindowHandle;
}
However if the process is already running then the result is null. In this case is there any way (managed or otherwise) to tell where the request was routed and which process consumed the start request?
For me, this seems like an XY-problem.
Before you start your own process, I would check if there already is a process running from the fileName you specified. If so, then use the handle of the process you found, otherwise start your own process.
If your fileName is an exe or bat or some other kind of executable, the method below should work. If it as a document or something like a document, I'm quite sure this method won't work without further adjustments.
Basically, the code would look like this (pseudo-code!):
//see below for method definition
List<Process> runningProcesses = ProcessUtil.GetProcessesForExecutable(fileName);
Process process;
if(runningProcesses.Count == 0)
{
process = Process.Start(fileName);
} else
{
process = runningProcesses[0];
// maybe some more code to handle more than one running process
}
var handle = process.MainWindowHandle;
All that is left is the implementation of ProcessUtil.GetProcessesForExecutable.
There are three possibillities to get a process by name resp. by executable name, but two of them have major downsides:
Process.GetProcessesByName(string)
Downside here is, that the process name may differ from the executable name. Thus you might not find a running process by giving this method a filename.
Process.GetProcesses() with either iterating over the whole array or some LINQ-select
Is safe in terms of executable name vs. process name, but quite slow with 700-800ms per execution in my tests.
The last possibility is using P/Invoke. Why P/Invoke? Turns out it was the fastest one with 5-6ms per execution in my tests.
I implemented this once in a project using P/Invoke, being inspired by pinvoke.net:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
namespace Util
{
/// <summary>
/// Utility methods for processes
/// </summary>
public static class ProcessUtil
{
//Code is inspired by http://www.pinvoke.net/default.aspx/kernel32.createtoolhelp32snapshot
[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Auto)]
static extern IntPtr CreateToolhelp32Snapshot([In]UInt32 dwFlags, [In]UInt32 th32ProcessID);
[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Auto)]
static extern bool Process32First([In]IntPtr hSnapshot, ref PROCESSENTRY32 lppe);
[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Auto)]
static extern bool Process32Next([In]IntPtr hSnapshot, ref PROCESSENTRY32 lppe);
[DllImport("kernel32", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CloseHandle([In] IntPtr hObject);
//inner enum used only internally
[Flags]
private enum SnapshotFlags : uint
{
HeapList = 0x00000001,
Process = 0x00000002,
Thread = 0x00000004,
Module = 0x00000008,
Module32 = 0x00000010,
Inherit = 0x80000000,
All = 0x0000001F,
NoHeaps = 0x40000000
}
//inner struct used only internally
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
private struct PROCESSENTRY32
{
const int MAX_PATH = 260;
internal UInt32 dwSize;
internal UInt32 cntUsage;
internal UInt32 th32ProcessID;
internal IntPtr th32DefaultHeapID;
internal UInt32 th32ModuleID;
internal UInt32 cntThreads;
internal UInt32 th32ParentProcessID;
internal Int32 pcPriClassBase;
internal UInt32 dwFlags;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_PATH)]
internal string szExeFile;
}
/// <summary>
/// Returns a list with running processes for a given executable path.
/// </summary>
/// <param name="exec">full path to the executable</param>
/// <returns>a list containing all processes currently run by the given executable</returns>
public static List<Process> GetProcessesForExecutable(string exec)
{
List<Process> toReturn = new List<Process>();
IntPtr handleToSnapShot = IntPtr.Zero;
FileInfo fi = new FileInfo(exec);
if (!fi.Exists)
{
return toReturn;
}
try
{
PROCESSENTRY32 procEntry = new PROCESSENTRY32();
procEntry.dwSize = (UInt32)Marshal.SizeOf(typeof(PROCESSENTRY32));
handleToSnapShot = CreateToolhelp32Snapshot((uint)SnapshotFlags.Process, 0);
if (Process32First(handleToSnapShot, ref procEntry))
{
do
{
if (fi.Name.Equals(procEntry.szExeFile))
{
Process p = Process.GetProcessById((int)procEntry.th32ProcessID);
if (p.MainModule.FileName.Equals(fi.FullName))
toReturn.Add(p);
}
} while (Process32Next(handleToSnapShot, ref procEntry));
}
}
catch (Exception ex)
{
//some error handling would be neccessary here
}
finally
{
CloseHandle(handleToSnapShot);
}
return toReturn;
}
}
}
I try to change the hostname via kernel32.dll import and the function SetComputerName. SetComputerName function
Mainclass:
namespace Castell
{
class Program
{
private static string hostname { get; set; }
setHostname();
private static void setHostname()
{
hostname = "TEST123456789";
int errorcode = ImportDLL.SetComputerName(hostname);
Console.WriteLine(Marshal.GetLastWin32Error());
}
}
}
Import Class:
namespace Castell
{
class ImportDLL
{
[DllImport("Kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int SetComputerName(string hostname);
}
}
Result of the Marshal.GetLastWin32Error() is "6". So that means:
ERROR_INVALID_HANDLE
6 (0x6)
The handle is invalid.
Dont know whats wrong with the handle.
You are just doing it wrong. The return type of SetComputerName() is bool, not int. It returns false when the function failed. The hard rule in the winapi is that you should only ever obtain the error code when the function failed. Or to put it another way, Windows doesn't explicitly set the error code back to 0 when the function is successful. Only then use Marshal.GetLastWin32Error() to retrieve the error code. Otherwise done automatically by the Win32Exception class constructor. Which makes this code work:
public static void SetHostname(string hostname)
{
if (!SetComputerName(hostname)) {
throw new System.ComponentModel.Win32Exception();
}
}
[DllImport("Kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern int SetComputerName(string hostname);
ServiceController serviceController = new ServiceController(someService);
serviceController.Stop();
serviceController.WaitForStopped();
DoSomething();
SomeService works on a sqlserver file. DoSomething() wants to copy that SQL file. If SomeService isn't closed fully it will throw an error because the database file is still locked. In the aforementioned code, I get past the WaitForStopped() method and yet the service doesn't release the database file until after DoSomething(), thus I get an error.
Doing some more investigation, I find that before the DoSomething method call I see that the service controller status shows a stopped and yet looking at some ProcMon logs the service releases the database file after I'm thrown an error from DoSomething.
Also, if I put a Thread.Sleep between the WaitForStopped and the DoSomething method for say... 5 seconds, the database file is released and all is well. Not the solution of surety I'm looking for however.
Any ideas?
Windows Services are a layer on top of processes; in order to be a service, an application must connect to the Service Control Manager and announce which services are available. This connection is handled within the ADVAPI32.DLL library. Once this connection is established, the library maintains a thread waiting for commands from the Service Control Manager, which can then start and stop services arbitrarily. I don't believe the process is required to exit when the last service in it terminates. Though that is what typically happens, the end of the link with the Service Control Manager, which occurs after the last service enters the "Stopped" state, can occur significantly before the process actually terminates, releasing any resources it hasn't already explicitly released.
The Windows Service API includes functionality that lets you obtain the Process ID of the process that is hosting the service. It is possible for a single process to host many services, and so the process might not actually exit when the service you are interested in has terminated, but you should be safe with SQL Server. Unfortunately, the .NET Framework does not expose this functionality. It does, however, expose the handle to the service that it uses internally for API calls, and you can use it to make your own API calls. With a bit of P/Invoke, then, you can obtain the process ID of the Windows Service process, and from there, provided you have the necessary permission, you can open a handle to the process that can be used to wait for it to exit.
Something like this:
[DllImport("advapi32")]
static extern bool QueryServiceStatusEx(IntPtr hService, int InfoLevel, ref SERVICE_STATUS_PROCESS lpBuffer, int cbBufSize, out int pcbBytesNeeded);
const int SC_STATUS_PROCESS_INFO = 0;
[StructLayout(LayoutKind.Sequential)]
struct SERVICE_STATUS_PROCESS
{
public int dwServiceType;
public int dwCurrentState;
public int dwControlsAccepted;
public int dwWin32ExitCode;
public int dwServiceSpecificExitCode;
public int dwCheckPoint;
public int dwWaitHint;
public int dwProcessId;
public int dwServiceFlags;
}
const int SERVICE_WIN32_OWN_PROCESS = 0x00000010;
const int SERVICE_INTERACTIVE_PROCESS = 0x00000100;
const int SERVICE_RUNS_IN_SYSTEM_PROCESS = 0x00000001;
public static void StopServiceAndWaitForExit(string serviceName)
{
using (ServiceController controller = new ServiceController(serviceName))
{
SERVICE_STATUS_PROCESS ssp = new SERVICE_STATUS_PROCESS();
int ignored;
// Obtain information about the service, and specifically its hosting process,
// from the Service Control Manager.
if (!QueryServiceStatusEx(controller.ServiceHandle.DangerousGetHandle(), SC_STATUS_PROCESS_INFO, ref ssp, Marshal.SizeOf(ssp), out ignored))
throw new Exception("Couldn't obtain service process information.");
// A few quick sanity checks that what the caller wants is *possible*.
if ((ssp.dwServiceType & ~SERVICE_INTERACTIVE_PROCESS) != SERVICE_WIN32_OWN_PROCESS)
throw new Exception("Can't wait for the service's hosting process to exit because there may be multiple services in the process (dwServiceType is not SERVICE_WIN32_OWN_PROCESS");
if ((ssp.dwServiceFlags & SERVICE_RUNS_IN_SYSTEM_PROCESS) != 0)
throw new Exception("Can't wait for the service's hosting process to exit because the hosting process is a critical system process that will not exit (SERVICE_RUNS_IN_SYSTEM_PROCESS flag set)");
if (ssp.dwProcessId == 0)
throw new Exception("Can't wait for the service's hosting process to exit because the process ID is not known.");
// Note: It is possible for the next line to throw an ArgumentException if the
// Service Control Manager's information is out-of-date (e.g. due to the process
// having *just* been terminated in Task Manager) and the process does not really
// exist. This is a race condition. The exception is the desirable result in this
// case.
using (Process process = Process.GetProcessById(ssp.dwProcessId))
{
// EDIT: There is no need for waiting in a separate thread, because MSDN says "The handles are valid until closed, even after the process or thread they represent has been terminated." ( http://msdn.microsoft.com/en-us/library/windows/desktop/ms684868%28v=vs.85%29.aspx ), so to keep things in the same thread, the process HANDLE should be opened from the process id before the service is stopped, and the Wait should be done after that.
// Response to EDIT: What you report is true, but the problem is that the handle isn't actually opened by Process.GetProcessById. It's only opened within the .WaitForExit method, which won't return until the wait is complete. Thus, if we try the wait on the current therad, we can't actually do anything until it's done, and if we defer the check until after the process has completed, it won't be possible to obtain a handle to it any more.
// The actual wait, using process.WaitForExit, opens a handle with the SYNCHRONIZE
// permission only and closes the handle before returning. As long as that handle
// is open, the process can be monitored for termination, but if the process exits
// before the handle is opened, it is no longer possible to open a handle to the
// original process and, worse, though it exists only as a technicality, there is
// a race condition in that another process could pop up with the same process ID.
// As such, we definitely want the handle to be opened before we ask the service
// to close, but since the handle's lifetime is only that of the call to WaitForExit
// and while WaitForExit is blocking the thread we can't make calls into the SCM,
// it would appear to be necessary to perform the wait on a separate thread.
ProcessWaitForExitData threadData = new ProcessWaitForExitData();
threadData.Process = process;
Thread processWaitForExitThread = new Thread(ProcessWaitForExitThreadProc);
processWaitForExitThread.IsBackground = Thread.CurrentThread.IsBackground;
processWaitForExitThread.Start(threadData);
// Now we ask the service to exit.
controller.Stop();
// Instead of waiting until the *service* is in the "stopped" state, here we
// wait for its hosting process to go away. Of course, it's really that other
// thread waiting for the process to go away, and then we wait for the thread
// to go away.
lock (threadData.Sync)
while (!threadData.HasExited)
Monitor.Wait(threadData.Sync);
}
}
}
class ProcessWaitForExitData
{
public Process Process;
public volatile bool HasExited;
public object Sync = new object();
}
static void ProcessWaitForExitThreadProc(object state)
{
ProcessWaitForExitData threadData = (ProcessWaitForExitData)state;
try
{
threadData.Process.WaitForExit();
}
catch {}
finally
{
lock (threadData.Sync)
{
threadData.HasExited = true;
Monitor.PulseAll(threadData.Sync);
}
}
}
ServiceController.WaitForStopped()/WaitForStatus() will return once the service implementation claims it has stopped. This doesn't necessary mean the process has released all of its resources and has exited. I've seen database other than SQL Server do this as well.
If you really want to be sure the database is fully and truly stopped, you will have to interface with the database itself, get ahold of the process id and wait for it to exit, wait for locks on the files to be released, ...
In my case I have used the interops:
[StructLayout(LayoutKind.Sequential)]
public struct SC_HANDLE__
{
public int unused;
}
[Flags]
public enum SERVICE_CONTROL : uint
{
STOP = 0x00000001,
PAUSE = 0x00000002,
CONTINUE = 0x00000003,
INTERROGATE = 0x00000004,
SHUTDOWN = 0x00000005,
PARAMCHANGE = 0x00000006,
NETBINDADD = 0x00000007,
NETBINDREMOVE = 0x00000008,
NETBINDENABLE = 0x00000009,
NETBINDDISABLE = 0x0000000A,
DEVICEEVENT = 0x0000000B,
HARDWAREPROFILECHANGE = 0x0000000C,
POWEREVENT = 0x0000000D,
SESSIONCHANGE = 0x0000000E
}
[StructLayout(LayoutKind.Sequential)]
public struct SERVICE_STATUS
{
/// DWORD->unsigned int
public uint dwServiceType;
/// DWORD->unsigned int
public uint dwCurrentState;
/// DWORD->unsigned int
public uint dwControlsAccepted;
/// DWORD->unsigned int
public uint dwWin32ExitCode;
/// DWORD->unsigned int
public uint dwServiceSpecificExitCode;
/// DWORD->unsigned int
public uint dwCheckPoint;
/// DWORD->unsigned int
public uint dwWaitHint;
}
public class NativeMethods
{
public const int SC_MANAGER_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED
| (SC_MANAGER_CONNECT
| (SC_MANAGER_CREATE_SERVICE
| (SC_MANAGER_ENUMERATE_SERVICE
| (SC_MANAGER_LOCK
| (SC_MANAGER_QUERY_LOCK_STATUS | SC_MANAGER_MODIFY_BOOT_CONFIG))))));
/// STANDARD_RIGHTS_REQUIRED -> (0x000F0000L)
public const int STANDARD_RIGHTS_REQUIRED = 983040;
/// SC_MANAGER_CONNECT -> 0x0001
public const int SC_MANAGER_CONNECT = 1;
/// SC_MANAGER_CREATE_SERVICE -> 0x0002
public const int SC_MANAGER_CREATE_SERVICE = 2;
/// SC_MANAGER_ENUMERATE_SERVICE -> 0x0004
public const int SC_MANAGER_ENUMERATE_SERVICE = 4;
/// SC_MANAGER_LOCK -> 0x0008
public const int SC_MANAGER_LOCK = 8;
/// SC_MANAGER_QUERY_LOCK_STATUS -> 0x0010
public const int SC_MANAGER_QUERY_LOCK_STATUS = 16;
/// SC_MANAGER_MODIFY_BOOT_CONFIG -> 0x0020
public const int SC_MANAGER_MODIFY_BOOT_CONFIG = 32;
/// SERVICE_CONTROL_STOP -> 0x00000001
public const int SERVICE_CONTROL_STOP = 1;
/// SERVICE_QUERY_STATUS -> 0x0004
public const int SERVICE_QUERY_STATUS = 4;
public const int GENERIC_EXECUTE = 536870912;
/// SERVICE_RUNNING -> 0x00000004
public const int SERVICE_RUNNING = 4;
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern IntPtr OpenService(IntPtr hSCManager, string lpServiceName, uint dwDesiredAccess);
[DllImport("advapi32.dll", EntryPoint = "OpenSCManagerW")]
public static extern IntPtr OpenSCManagerW(
[In()] [MarshalAs(UnmanagedType.LPWStr)] string lpMachineName,
[In()] [MarshalAs(UnmanagedType.LPWStr)] string lpDatabaseName,
uint dwDesiredAccess);
[DllImport("advapi32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool ControlService(IntPtr hService, SERVICE_CONTROL dwControl, ref SERVICE_STATUS lpServiceStatus);
[DllImport("advapi32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CloseServiceHandle(IntPtr hSCObject);
[DllImport("advapi32.dll", EntryPoint = "QueryServiceStatus", CharSet = CharSet.Auto)]
public static extern bool QueryServiceStatus(IntPtr hService, ref SERVICE_STATUS dwServiceStatus);
[SecurityCritical]
[HandleProcessCorruptedStateExceptions]
public static void ServiceStop()
{
IntPtr manager = IntPtr.Zero;
IntPtr service = IntPtr.Zero;
SERVICE_STATUS status = new SERVICE_STATUS();
if ((manager = OpenSCManagerW(null, null, SC_MANAGER_ALL_ACCESS)) != IntPtr.Zero)
{
if ((service = OpenService(manager, Resources.ServiceName, SC_MANAGER_ALL_ACCESS)) != IntPtr.Zero)
{
QueryServiceStatus(service, ref status);
}
if (status.dwCurrentState == SERVICE_RUNNING)
{
int i = 0;
//not the best way, but WaitStatus didnt work correctly.
while (i++ < 10 && status.dwCurrentState != SERVICE_CONTROL_STOP)
{
ControlService(service, SERVICE_CONTROL.STOP, ref status);
QueryServiceStatus(service, ref status);
Thread.Sleep(200);
}
}
}
if (manager != IntPtr.Zero)
{
var b = CloseServiceHandle(manager);
}
if (service != IntPtr.Zero)
{
var b = CloseServiceHandle(service);
}
}
}
I've seen this before when I stopped a service that was a dependency to another service, and the second service was holding resources I didn't even know it was using. Do you think this might be the case? I know SQL has quite a few different components, but I haven't looked into whether there are multiple services associated with it.
Good luck!
Even using #Jonathan Gilbert excellent answer I still had cases where I could not delete the service executable file after the service process was stopped. I found out I had to also call process.Kill() at the end of it all to totally free resources.
Here is a version of Jonathan Gilbert answer which adds Kill, a timeout, and better thread synchronization:
namespace System.ServiceProcess {
public static class ExtensionMethods {
[DllImport("advapi32")]
static extern bool QueryServiceStatusEx(IntPtr hService, int InfoLevel, ref SERVICE_STATUS_PROCESS lpBuffer, int cbBufSize, out int pcbBytesNeeded);
[StructLayout(LayoutKind.Sequential)] struct SERVICE_STATUS_PROCESS { public int dwServiceType; public int dwCurrentState; public int dwControlsAccepted; public int dwWin32ExitCode; public int dwServiceSpecificExitCode; public int dwCheckPoint; public int dwWaitHint; public int dwProcessId; public int dwServiceFlags; }
const int SC_STATUS_PROCESS_INFO = 0, SERVICE_WIN32_OWN_PROCESS = 0x00000010, SERVICE_INTERACTIVE_PROCESS = 0x00000100, SERVICE_RUNS_IN_SYSTEM_PROCESS = 0x00000001;
record ProcessWaitForExitData(int ProcessId, AutoResetEvent MWaitForThread, int TimeoutMilliseconds);
// wait for the actual process that runs the service to stop
public static void StopAndWaitForProcessToExit(this ServiceController controller, int timeoutMilliseconds=-1) {
var processId = -1;
try {
var ssp = new SERVICE_STATUS_PROCESS();
// check can't obtain service process informatio
if (QueryServiceStatusEx(controller.ServiceHandle.DangerousGetHandle(), SC_STATUS_PROCESS_INFO, ref ssp, Marshal.SizeOf(ssp), out int ignored)
// check can't wait for the service's hosting process to exit because there may be multiple services in the process (dwServiceType is not SERVICE_WIN32_OWN_PROCESS)
&& (ssp.dwServiceType & ~SERVICE_INTERACTIVE_PROCESS) == SERVICE_WIN32_OWN_PROCESS
// check can't wait for the service's hosting process to exit because the hosting process is a critical system process that will not exit (SERVICE_RUNS_IN_SYSTEM_PROCESS flag set)");
&& (ssp.dwServiceFlags & SERVICE_RUNS_IN_SYSTEM_PROCESS) == 0
// chec can't wait for the service's hosting process to exit because the process ID is not known.");
&& ssp.dwProcessId != 0) processId = ssp.dwProcessId;
} catch (Exception) {
}
if (processId==-1) {
controller.Stop(); // stop the service
return; // we did all we can
}
// we need to call WaitForExit before stopping the service so we do it in a separate thread
var mWaitForThread = new AutoResetEvent(false);
var processWaitForExitThread = new Thread(ProcessWaitForExitThreadProc) { IsBackground = Thread.CurrentThread.IsBackground };
processWaitForExitThread.Start(new ProcessWaitForExitData(processId, mWaitForThread, timeoutMilliseconds));
Task.Delay(5).Wait(); // let thread reach WaitForExit, is there a better way ?
controller.Stop(); // stop the service
mWaitForThread.WaitOne(); // wait for process to exit
}
static void ProcessWaitForExitThreadProc(object? state) {
ProcessWaitForExitData threadData = (ProcessWaitForExitData)state!;
try {
using Process process = Process.GetProcessById(threadData.ProcessId);
var stopwatch = Stopwatch.StartNew();
process.WaitForExit(threadData.TimeoutMilliseconds);
process.Kill(true); // free all process resources
var killTimeout = threadData.TimeoutMilliseconds==-1 ? -1 : (int)Math.Max(threadData.TimeoutMilliseconds - stopwatch.ElapsedMilliseconds, 10);
if (!process.WaitForExit(killTimeout)) throw new TimeoutException();
}
catch { }
finally {
threadData.MWaitForThread.Set();
}
}
}
}
To use:
using ServiceController controller = new(serviceName);
controller.StopAndWaitForProcessToExit(timeoutMilliseconds);
Try to use Environment.Exit(1);