为什么赢了';我的解决方案是否可以在C#中P/Invoke NotifyServiceStatusChange?
我试图在C#中p/调用,以检查服务何时停止。我设法让它编译并运行,没有任何错误,但现在,当我停止服务时,它似乎不想通知它已死亡。你知道为什么会这样吗?您可以通过将此代码复制到一个空白的控制台应用程序来测试它;只需确保将“我的服务名”替换为您的服务名(下面有两个此字符串的实例)为什么赢了';我的解决方案是否可以在C#中P/Invoke NotifyServiceStatusChange?,c#,.net,winapi,pinvoke,C#,.net,Winapi,Pinvoke,我试图在C#中p/调用,以检查服务何时停止。我设法让它编译并运行,没有任何错误,但现在,当我停止服务时,它似乎不想通知它已死亡。你知道为什么会这样吗?您可以通过将此代码复制到一个空白的控制台应用程序来测试它;只需确保将“我的服务名”替换为您的服务名(下面有两个此字符串的实例) 请在此处阅读相关问题: 重要引用:“系统将指定的回调函数作为异步过程调用(APC)调用到调用线程。调用线程必须进入可警报的等待” 我不记得当您进入Thread.Sleep或某种形式的waithandles等待时,.NET
请在此处阅读相关问题: 重要引用:“系统将指定的回调函数作为异步过程调用(APC)调用到调用线程。调用线程必须进入可警报的等待” 我不记得当您进入Thread.Sleep或某种形式的waithandles等待时,.NET framework 4是否使用了alertable waiting,即使它对异步I/O、内部计时器线程等使用了alertable waiting 但是,只需尝试Thread.Sleep或某种waithandle上的Wait,而不是Console.ReadLine,确保在终止服务时线程被这些API阻止。这可能很神奇——但据我所知,这是一种危险的方式,因为.NET运行时不希望用户代码在APC上执行。至少,不要直接从回调中使用NET framework资源或任何API(尤其是与同步相关的或内存分配的API)——只需设置一些基本变量并退出即可 使用APCs,最安全的解决方案是在某种本机模块中实现回调,并从一些非.NET线程调度回调,通过共享变量、管道或COM接口与托管代码进行互操作 或者,正如Hans Passant在您的问题的另一份副本中所建议的,从托管代码进行轮询。绝对安全、易于实施、保证工作。
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)
{
}
}
}
Joe Duffy的书是相关信息的极好来源(他涵盖了很多主题,尤其是alertable waits和.NET):
更新:刚刚查阅了Joe Duffy的书,没错,在APC上调度.NET代码可能会导致死锁、访问冲突和通常不可预测的行为。因此,答案很简单:不要从托管线程执行APC。如果您想保留当前尝试的功能,则需要多线程
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.");
}
}
从@Motes的答案中简化了很多内容…(编辑:我把它放在一个类中,人们可以使用它轻松地等待服务停止;它将阻止 再次编辑:如果您在函数中的任何位置使用GC.Collect()强制垃圾收集,请确保此操作有效……事实证明,您确实需要服务状态进程。
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)
{
}
}
}
另一个编辑:如果中止线程,请确保它可以工作(注意:无法中止休眠线程,因此如果计划中止此线程…请确保至少给它一个超时,以便在超时命中后可以运行终结器),还添加了超时。还确保了操作系统线程与当前.NET线程的1对1映射。
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)
{
}
}
}
使用系统;
使用System.Runtime.InteropServices;
使用系统线程;
命名空间服务助手
{
类ServiceHelper
{
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
公共类服务通知
{
公共uint DWT版本;
公共IntPtr PFR回调;
公共IntPtr pContext;
公共信息通报状态;
公共服务状态\流程服务状态;
触发公共uint数据通知;
公共IntPtr PSZ服务名称;
};
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
公共结构服务\u状态\u进程
{
公共uint dwServiceType;
国家公共部门;
接受公共单位;
公共uint dwWin32ExitCode;
公共uint dwServiceSpecificExitCode;
公共交通检查站;
公共uint dwWaitHint;
公共uint-dwProcessId;
公共uint服务标志;
};
[DllImport(“advapi32.dll”)]
静态外部IntPtr OpenService(IntPtr hSCManager,字符串lpServiceName,uint dwDesiredAccess);
[DllImport(“advapi32.dll”)]
静态外部IntPtr OpenSCManager(字符串machineName、字符串databaseName、uint dwAccess);
[DllImport(“advapi32.dll”)]
静态外部uint NotifyServiceStatusChange(IntPtr hService、uint dwNotifyMask、IntPtr pNotifyBuffer);
[DllImportAttribute(“kernel32.dll”)]
静态外部uint SleepEx(uint DWM毫秒,布尔包表);
[DllImport(“advapi32.dll”)]
静态外部布尔闭合服务句柄(IntPtr hSCObject);
委托无效状态更改CallbackDelegate(IntPtr参数);
///
///阻止,直到服务停止、终止或发现已终止。
///
///您要等待的服务的名称。
///您希望等待的时间量。uint.MaxValue是默认值,它将强制此线程无限期等待。
公共静态void WaitForServiceStop(字符串serviceName,uint timeout=uint.MaxValue)
{
//确保此线程的标识与本机OS线程一一对应。
Thread.BeginThreadAffinity();
GCHandle notifyHandle=默认值(GCHandle);
StatusChangedCallbackDelegate changeDelegate=ReceivedStatusChangedEvent;
IntPtr hSCM=IntPtr.0;
IntPtr hService=IntPtr.Zero;
尝试
{
hSCM=OpenSCManager(null,null,(uint)0xF003F);
如果(hSCM!=IntPtr.Zero)