为什么我的解决方案不适用于C#中的P / Invoke NotifyServiceStatusChange?

我正在尝试P / Invoke C#中的NotifyServiceStatusChange事件来检查服务何时停止。 我设法让它编译并运行没有任何错误,但现在, 当我停止服务时,它似乎不想通知它已死 。 任何想法为什么会这样? 您可以通过将此代码复制到空白控制台应用程序中来测试它; 只需确保将“我的服务名称”替换为您的服务名称(下面有两个此字符串实例)。

using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; namespace ConsoleApplication1 { class Program { public delegate void StatusChanged(IntPtr parameter); public class SERVICE_NOTIFY : MarshalByRefObject { public uint dwVersion; public StatusChanged pfnNotifyCallback; public IntPtr pContext; public uint dwNotificationStatus; public SERVICE_STATUS_PROCESS ServiceStatus; public uint dwNotificationTriggered; public IntPtr pszServiceNames; }; public struct SERVICE_STATUS_PROCESS { public uint dwServiceType; public uint dwCurrentState; public uint dwControlsAccepted; public uint dwWin32ExitCode; public uint dwServiceSpecificExitCode; public uint dwCheckPoint; public uint dwWaitHint; public uint dwProcessId; public uint dwServiceFlags; }; [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)] static extern IntPtr OpenService(IntPtr hSCManager, string lpServiceName, uint dwDesiredAccess); [DllImport("advapi32.dll", EntryPoint = "OpenSCManagerW", ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)] public static extern IntPtr OpenSCManager(string machineName, string databaseName, uint dwAccess); [DllImport("advapi32.dll", SetLastError = true)] public static extern uint NotifyServiceStatusChange(IntPtr hService, uint dwNotifyMask, ref IntPtr pNotifyBuffer); public static SERVICE_NOTIFY notify; public static GCHandle notifyHandle; public static IntPtr unmanagedNotifyStructure; static void Main(string[] args) { IntPtr hSCM = OpenSCManager(null, null, (uint)0xF003F); if (hSCM != IntPtr.Zero) { IntPtr hService = OpenService(hSCM, "My Service Name", (uint)0xF003F); if (hService != IntPtr.Zero) { StatusChanged changeDelegate = ReceivedStatusChangedEvent; notify = new SERVICE_NOTIFY(); notify.dwVersion = 2; notify.pfnNotifyCallback = changeDelegate; notify.pContext = IntPtr.Zero; notify.dwNotificationStatus = 0; SERVICE_STATUS_PROCESS process; process.dwServiceType = 0; process.dwCurrentState = 0; process.dwControlsAccepted = 0; process.dwWin32ExitCode = 0; process.dwServiceSpecificExitCode = 0; process.dwCheckPoint = 0; process.dwWaitHint = 0; process.dwProcessId = 0; process.dwServiceFlags = 0; notify.ServiceStatus = process; notify.dwNotificationTriggered = 0; notify.pszServiceNames = Marshal.StringToHGlobalUni("My Service Name"); notifyHandle = GCHandle.Alloc(notify); unmanagedNotifyStructure = Marshal.AllocHGlobal((IntPtr)(notifyHandle)); NotifyServiceStatusChange(hService, (uint)0x00000001, ref unmanagedNotifyStructure); Console.WriteLine("Waiting for the service to stop. Press enter to exit."); Console.ReadLine(); } } } public static void ReceivedStatusChangedEvent(IntPtr parameter) { Console.WriteLine("Service stopped."); } } } 

如果要保留当前尝试的function,则需要multithreading。

 using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; namespace ConsoleApplication1 { class Program { [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] public class SERVICE_NOTIFY { public uint dwVersion; public IntPtr pfnNotifyCallback; public IntPtr pContext; public uint dwNotificationStatus; public SERVICE_STATUS_PROCESS ServiceStatus; public uint dwNotificationTriggered; public IntPtr pszServiceNames; }; [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] public struct SERVICE_STATUS_PROCESS { public uint dwServiceType; public uint dwCurrentState; public uint dwControlsAccepted; public uint dwWin32ExitCode; public uint dwServiceSpecificExitCode; public uint dwCheckPoint; public uint dwWaitHint; public uint dwProcessId; public uint dwServiceFlags; }; [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)] static extern IntPtr OpenService(IntPtr hSCManager, string lpServiceName, uint dwDesiredAccess); [DllImport("advapi32.dll", EntryPoint = "OpenSCManagerW", ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)] public static extern IntPtr OpenSCManager(string machineName, string databaseName, uint dwAccess); [DllImport("advapi32.dll", SetLastError = true)] public static extern uint NotifyServiceStatusChange(IntPtr hService, uint dwNotifyMask, IntPtr pNotifyBuffer); [DllImportAttribute("kernel32.dll", EntryPoint = "SleepEx")] public static extern uint SleepEx(uint dwMilliseconds, [MarshalAsAttribute(UnmanagedType.Bool)] bool bAlertable); public static SERVICE_NOTIFY notify; public static GCHandle notifyHandle; public static IntPtr unmanagedNotifyStructure; static void Main(string[] args) { IntPtr hSCM = OpenSCManager(null, null, (uint)0xF003F); if (hSCM != IntPtr.Zero) { IntPtr hService = OpenService(hSCM, "Apache2.2", (uint)0xF003F); if (hService != IntPtr.Zero) { StatusChanged changeDelegate = ReceivedStatusChangedEvent; notify = new SERVICE_NOTIFY(); notify.dwVersion = 2; notify.pfnNotifyCallback = Marshal.GetFunctionPointerForDelegate(changeDelegate); notify.pContext = IntPtr.Zero; notify.dwNotificationStatus = 0; SERVICE_STATUS_PROCESS process; process.dwServiceType = 0; process.dwCurrentState = 0; process.dwControlsAccepted = 0; process.dwWin32ExitCode = 0; process.dwServiceSpecificExitCode = 0; process.dwCheckPoint = 0; process.dwWaitHint = 0; process.dwProcessId = 0; process.dwServiceFlags = 0; notify.ServiceStatus = process; notify.dwNotificationTriggered = 0; notify.pszServiceNames = Marshal.StringToHGlobalUni("Apache2.2"); notifyHandle = GCHandle.Alloc(notify, GCHandleType.Pinned); unmanagedNotifyStructure = notifyHandle.AddrOfPinnedObject(); NotifyServiceStatusChange(hService, (uint)0x00000001, unmanagedNotifyStructure); GC.KeepAlive(changeDelegate); Console.WriteLine("Waiting for the service to stop. Press enter to exit."); while (true) { try { string keyIn = Reader.ReadLine(500); break; } catch (TimeoutException) { SleepEx(100,true); } } notifyHandle.Free(); } } } [UnmanagedFunctionPointer(CallingConvention.StdCall)] public delegate void StatusChanged(IntPtr parameter); public static void ReceivedStatusChangedEvent(IntPtr parameter) { Console.WriteLine("Service stopped."); } } } class Reader { private static Thread inputThread; private static AutoResetEvent getInput, gotInput; private static string input; static Reader() { inputThread = new Thread(reader); inputThread.IsBackground = true; inputThread.Start(); getInput = new AutoResetEvent(false); gotInput = new AutoResetEvent(false); } private static void reader() { while (true) { getInput.WaitOne(); input = Console.ReadLine(); gotInput.Set(); } } public static string ReadLine(int timeOutMillisecs) { getInput.Set(); bool success = gotInput.WaitOne(timeOutMillisecs); if (success) return input; else throw new TimeoutException("User did not provide input within the timelimit."); } } 

在这里阅读相关问题: http : //social.msdn.microsoft.com/Forums/vstudio/en-US/f68fb826-036a-4b9c-81e6-4cbd87931feb/notifyservicestatuschange-not-working-in-c-for-windows-service -通知

重要引用:“系统调用指定的回调函数作为排队到调用线程的异步过程调用(APC)。调用线程必须输入可警告的等待”

我不记得.NET框架4是否在您输入Thread.Sleep或某种forms的Wait on waithandles时使用可警告的等待,即使它使用可警告的等待异步I / O,内部计时器线程等。

但是,只需尝试Thread.Sleep或某些等待句式而不是Console.ReadLine,确保在您终止服务时这些API阻止了您的线程。 这可能是神奇的 – 但据我所知,这是一种危险的方式,因为.NET运行时不希望用户代码在APC上执行。 至少,尽量不要直接从回调中使用.NET框架资源或绝对任何API(特别是与同步相关或内存分配) – 只需设置一些原始变量并退出。

使用APC,最安全的解决方案是在某种本机模块中实现回调,并从一些非.NET线程调度 ,通过共享变量,管道或COM接口与托管代码进行互操作。

或者,正如Hans Passant在您的问题的另一个副本中建议的那样,只需从托管代码进行轮询。 绝对安全,易于实施,保证工作。

相关信息的优秀来源是Joe Duffy的书(他涵盖了很多主题,特别是可警告的等待和.NET): http : //www.amazon.com/Concurrent-Programming-Windows-Joe-Duffy/dp/032143482X

更新:刚刚咨询了Joe Duffy的书,是的确,在APC上安排.NET代码可能会导致死锁,访问冲突和一般不可预测的行为。 所以答案很简单: 不要从托管线程做APC

从@Motes的回答中简化了很多这个…(编辑:我把它放到一个人们可以用来轻松等待服务停止的课程中;它会阻止!

再次编辑:如果您在函数中的任何位置使用GC.Collect()强制进行垃圾收集,请确保这样做…结果,您需要SERVICE_STATUS_PROCESS。

另一个编辑:确保它在您中止线程时有效(请注意:不能中止睡眠线程,因此如果您打算中止此线程…那么请确保至少给它一个超时,以便终结器可以在之后运行超时命中),也增加了超时。 还确保将OS线程的1对1映射到当前的.NET线程。

 using System; using System.Runtime.InteropServices; using System.Threading; namespace ServiceAssistant { class ServiceHelper { [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] public class SERVICE_NOTIFY { public uint dwVersion; public IntPtr pfnNotifyCallback; public IntPtr pContext; public uint dwNotificationStatus; public SERVICE_STATUS_PROCESS ServiceStatus; public uint dwNotificationTriggered; public IntPtr pszServiceNames; }; [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] public struct SERVICE_STATUS_PROCESS { public uint dwServiceType; public uint dwCurrentState; public uint dwControlsAccepted; public uint dwWin32ExitCode; public uint dwServiceSpecificExitCode; public uint dwCheckPoint; public uint dwWaitHint; public uint dwProcessId; public uint dwServiceFlags; }; [DllImport("advapi32.dll")] static extern IntPtr OpenService(IntPtr hSCManager, string lpServiceName, uint dwDesiredAccess); [DllImport("advapi32.dll")] static extern IntPtr OpenSCManager(string machineName, string databaseName, uint dwAccess); [DllImport("advapi32.dll")] static extern uint NotifyServiceStatusChange(IntPtr hService, uint dwNotifyMask, IntPtr pNotifyBuffer); [DllImportAttribute("kernel32.dll")] static extern uint SleepEx(uint dwMilliseconds, bool bAlertable); [DllImport("advapi32.dll")] static extern bool CloseServiceHandle(IntPtr hSCObject); delegate void StatusChangedCallbackDelegate(IntPtr parameter); ///  /// Block until a service stops, is killed, or is found to be already dead. ///  /// The name of the service you would like to wait for. /// An amount of time you would like to wait for. uint.MaxValue is the default, and it will force this thread to wait indefinitely. public static void WaitForServiceToStop(string serviceName, uint timeout = uint.MaxValue) { // Ensure that this thread's identity is mapped, 1-to-1, with a native OS thread. Thread.BeginThreadAffinity(); GCHandle notifyHandle = default(GCHandle); StatusChangedCallbackDelegate changeDelegate = ReceivedStatusChangedEvent; IntPtr hSCM = IntPtr.Zero; IntPtr hService = IntPtr.Zero; try { hSCM = OpenSCManager(null, null, (uint)0xF003F); if (hSCM != IntPtr.Zero) { hService = OpenService(hSCM, serviceName, (uint)0xF003F); if (hService != IntPtr.Zero) { SERVICE_NOTIFY notify = new SERVICE_NOTIFY(); notify.dwVersion = 2; notify.pfnNotifyCallback = Marshal.GetFunctionPointerForDelegate(changeDelegate); notify.ServiceStatus = new SERVICE_STATUS_PROCESS(); notifyHandle = GCHandle.Alloc(notify, GCHandleType.Pinned); IntPtr pinnedNotifyStructure = notifyHandle.AddrOfPinnedObject(); NotifyServiceStatusChange(hService, (uint)0x00000001, pinnedNotifyStructure); SleepEx(timeout, true); } } } finally { // Clean up at the end of our operation, or if this thread is aborted. if (hService != IntPtr.Zero) { CloseServiceHandle(hService); } if (hSCM != IntPtr.Zero) { CloseServiceHandle(hSCM); } GC.KeepAlive(changeDelegate); if (notifyHandle != default(GCHandle)) { notifyHandle.Free(); } Thread.EndThreadAffinity(); } } public static void ReceivedStatusChangedEvent(IntPtr parameter) { } } } 

是! 我们做到了。 这是一次多么艰难的旅程。