我正在尝试在C#中调用NotifyServiceStatusChange event以检查服务何时停止。我设法让它编译并运行没有任何错误,但现在,当我停止服务时,它似乎不想通知它已经死了。任何想法为什么会这样?您可以通过将此代码复制到空白控制台应用程序中来测试它;只需确保将“我的服务名称”替换为您的服务名称(下面有两个此字符串实例)。
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.");
}
}
}
答案 0 :(得分:4)
如果您想保留目前正在尝试的功能,则需要多线程。
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.");
}
}
答案 1 :(得分:3)
重要引用:“系统调用指定的回调函数作为排队到调用线程的异步过程调用(APC)。调用线程必须输入可警告的等待”
我不记得.NET框架4是否在您输入Thread.Sleep或某种形式的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 。
答案 2 :(得分:2)
从@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);
/// <summary>
/// Block until a service stops, is killed, or is found to be already dead.
/// </summary>
/// <param name="serviceName">The name of the service you would like to wait for.</param>
/// <param name="timeout">An amount of time you would like to wait for. uint.MaxValue is the default, and it will force this thread to wait indefinitely.</param>
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)
{
}
}
}
是的!我们做到了。这是一次多么艰难的旅程。