我想编写一个服务来监听设备通知(USB 媒体插入、移除)。在 C# 服务中侦听设备通知的问题是,这System.Windows.Forms.Control.WndProc
是不可用的,因为 Windows 服务没有任何窗口。
我在如何编写这样的服务上找到了这个 HowTo。那篇文章的作者找到了一种解决方法,它可以让服务侦听设备通知而不是服务控制消息,因此,该服务不再支持 OnStop()。
(26.01.13 更新:)
遗憾的是,我并不真正了解服务控制管理器和 Windows API。我想知道是否可以同时注册服务控制消息和 USB 设备通知,或者这是否真的是服务收听设备通知的唯一选择。我还没有找到任何(我可以理解的)信息来解决我的问题。
是否可以在System.Windows.Forms.Control.WndProc
不生成窗口的情况下使用(我只需要添加 System.Windows.Forms 程序集,对吗?)。
(更新 27.01.13 :)
我刚刚发现了这个问题:无法在 Windows 7 上从 Windows 服务启动桌面应用程序
第二个答案说,Windows 服务在 Windows Vista 中进行了以安全为中心的改造,现在在会话 0 中创建了 GUI 元素即使选中了“允许服务与桌面交互”。这是否意味着,我可以创建一个 Windows 窗体,然后接收 USB 设备事件(因此,我不需要弄乱ServiceControlHandler
?这样做有什么警告或问题吗?
简而言之,我需要一个执行以下操作之一的解决方案:
- 使 OnStop 再次可用,或
- 在 Windows C# 服务中提供另一种侦听 USB 设备通知的方法
我的源代码目前如下。它与我在第一段中链接的 HowTo 提供的源代码几乎相同。我所做的唯一区别是删除 FileSystemWatcher 私有字段及其所有用法,因为我不需要 FileSystemWatcher。
USBBackup.cs(服务本身 - 使用排除在我的源代码中但完整的语句):
namespace USBBackup
{
public partial class USBBackup : ServiceBase
{
private IntPtr deviceNotifyHandle;
private IntPtr deviceEventHandle;
private IntPtr directoryHandle;
private Win32.ServiceControlHandlerEx myCallback;
private int ServiceControlHandler(int control, int eventType, IntPtr eventData, IntPtr context)
{
if (control == Win32.SERVICE_CONTROL_STOP || control == Win32.SERVICE_CONTROL_SHUTDOWN)
{
UnregisterHandles();
Win32.UnregisterDeviceNotification(deviceEventHandle);
base.Stop();
}
else if (control == Win32.SERVICE_CONTROL_DEVICEEVENT)
{
switch (eventType)
{
case Win32.DBT_DEVICEARRIVAL:
Win32.DEV_BROADCAST_HDR hdr;
hdr = (Win32.DEV_BROADCAST_HDR)Marshal.PtrToStructure(eventData, typeof(Win32.DEV_BROADCAST_HDR));
if (hdr.dbcc_devicetype == Win32.DBT_DEVTYP_DEVICEINTERFACE)
{
Win32.DEV_BROADCAST_DEVICEINTERFACE deviceInterface;
deviceInterface = (Win32.DEV_BROADCAST_DEVICEINTERFACE)Marshal.PtrToStructure(eventData, typeof(Win32.DEV_BROADCAST_DEVICEINTERFACE));
string name = new string(deviceInterface.dbcc_name);
name = name.Substring(0, name.IndexOf('\0')) + "\\";
StringBuilder stringBuilder = new StringBuilder();
Win32.GetVolumeNameForVolumeMountPoint(name, stringBuilder, 100);
uint stringReturnLength = 0;
string driveLetter = "";
Win32.GetVolumePathNamesForVolumeNameW(stringBuilder.ToString(), driveLetter, (uint)driveLetter.Length, ref stringReturnLength);
if (stringReturnLength == 0)
{
// TODO handle error
}
driveLetter = new string(new char[stringReturnLength]);
if (!Win32.GetVolumePathNamesForVolumeNameW(stringBuilder.ToString(), driveLetter, stringReturnLength, ref stringReturnLength))
{
// TODO handle error
}
RegisterForHandle(driveLetter[0]);
}
break;
case Win32.DBT_DEVICEQUERYREMOVE:
UnregisterHandles();
break;
}
}
return 0;
}
private void UnregisterHandles()
{
if (directoryHandle != IntPtr.Zero)
{
Win32.CloseHandle(directoryHandle);
directoryHandle = IntPtr.Zero;
}
if (deviceNotifyHandle != IntPtr.Zero)
{
Win32.UnregisterDeviceNotification(deviceNotifyHandle);
deviceNotifyHandle = IntPtr.Zero;
}
}
private void RegisterForHandle(char c)
{
Win32.DEV_BROADCAST_HANDLE deviceHandle = new Win32.DEV_BROADCAST_HANDLE();
int size = Marshal.SizeOf(deviceHandle);
deviceHandle.dbch_size = size;
deviceHandle.dbch_devicetype = Win32.DBT_DEVTYP_HANDLE;
directoryHandle = CreateFileHandle(c + ":\\");
deviceHandle.dbch_handle = directoryHandle;
IntPtr buffer = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(deviceHandle, buffer, true);
deviceNotifyHandle = Win32.RegisterDeviceNotification(this.ServiceHandle, buffer, Win32.DEVICE_NOTIFY_SERVICE_HANDLE);
if (deviceNotifyHandle == IntPtr.Zero)
{
// TODO handle error
}
}
private void RegisterDeviceNotification()
{
myCallback = new Win32.ServiceControlHandlerEx(ServiceControlHandler);
Win32.RegisterServiceCtrlHandlerEx(this.ServiceName, myCallback, IntPtr.Zero);
if (this.ServiceHandle == IntPtr.Zero)
{
// TODO handle error
}
Win32.DEV_BROADCAST_DEVICEINTERFACE deviceInterface = new Win32.DEV_BROADCAST_DEVICEINTERFACE();
int size = Marshal.SizeOf(deviceInterface);
deviceInterface.dbcc_size = size;
deviceInterface.dbcc_devicetype = Win32.DBT_DEVTYP_DEVICEINTERFACE;
IntPtr buffer = default(IntPtr);
buffer = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(deviceInterface, buffer, true);
deviceEventHandle = Win32.RegisterDeviceNotification(this.ServiceHandle, buffer, Win32.DEVICE_NOTIFY_SERVICE_HANDLE | Win32.DEVICE_NOTIFY_ALL_INTERFACE_CLASSES);
if (deviceEventHandle == IntPtr.Zero)
{
// TODO handle error
}
}
public USBBackup()
{
InitializeComponent();
}
public static IntPtr CreateFileHandle(string driveLetter)
{
// open the existing file for reading
IntPtr handle = Win32.CreateFile(
driveLetter,
Win32.GENERIC_READ,
Win32.FILE_SHARE_READ | Win32.FILE_SHARE_WRITE,
0,
Win32.OPEN_EXISTING,
Win32.FILE_FLAG_BACKUP_SEMANTICS | Win32.FILE_ATTRIBUTE_NORMAL,
0);
if (handle == Win32.INVALID_HANDLE_VALUE)
{
return IntPtr.Zero;
}
else
{
return handle;
}
}
protected override void OnStart(string[] args)
{
base.OnStart(args);
RegisterDeviceNotification();
}
}
}
Win32.cs:
namespace USBBackup
{
public class Win32
{
public const int DEVICE_NOTIFY_SERVICE_HANDLE = 1;
public const int DEVICE_NOTIFY_ALL_INTERFACE_CLASSES = 4;
public const int SERVICE_CONTROL_STOP = 1;
public const int SERVICE_CONTROL_DEVICEEVENT = 11;
public const int SERVICE_CONTROL_SHUTDOWN = 5;
public const uint GENERIC_READ = 0x80000000;
public const uint OPEN_EXISTING = 3;
public const uint FILE_SHARE_READ = 1;
public const uint FILE_SHARE_WRITE = 2;
public const uint FILE_SHARE_DELETE = 4;
public const uint FILE_ATTRIBUTE_NORMAL = 128;
public const uint FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
public static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
public const int DBT_DEVTYP_DEVICEINTERFACE = 5;
public const int DBT_DEVTYP_HANDLE = 6;
public const int DBT_DEVICEARRIVAL = 0x8000;
public const int DBT_DEVICEQUERYREMOVE = 0x8001;
public const int DBT_DEVICEREMOVECOMPLETE = 0x8004;
public const int WM_DEVICECHANGE = 0x219;
public delegate int ServiceControlHandlerEx(int control, int eventType, IntPtr eventData, IntPtr context);
[DllImport("advapi32.dll", SetLastError = true)]
public static extern IntPtr RegisterServiceCtrlHandlerEx(string lpServiceName, ServiceControlHandlerEx cbex, IntPtr context);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetVolumePathNamesForVolumeNameW(
[MarshalAs(UnmanagedType.LPWStr)]
string lpszVolumeName,
[MarshalAs(UnmanagedType.LPWStr)]
string lpszVolumePathNames,
uint cchBuferLength,
ref UInt32 lpcchReturnLength);
[DllImport("kernel32.dll")]
public static extern bool GetVolumeNameForVolumeMountPoint(string
lpszVolumeMountPoint, [Out] StringBuilder lpszVolumeName,
uint cchBufferLength);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr RegisterDeviceNotification(IntPtr IntPtr, IntPtr NotificationFilter, Int32 Flags);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern uint UnregisterDeviceNotification(IntPtr hHandle);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr CreateFile(
string FileName, // file name
uint DesiredAccess, // access mode
uint ShareMode, // share mode
uint SecurityAttributes, // Security Attributes
uint CreationDisposition, // how to create
uint FlagsAndAttributes, // file attributes
int hTemplateFile // handle to template file
);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool CloseHandle(IntPtr hObject);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct DEV_BROADCAST_DEVICEINTERFACE
{
public int dbcc_size;
public int dbcc_devicetype;
public int dbcc_reserved;
[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 16)]
public byte[] dbcc_classguid;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)]
public char[] dbcc_name;
}
[StructLayout(LayoutKind.Sequential)]
public struct DEV_BROADCAST_HDR
{
public int dbcc_size;
public int dbcc_devicetype;
public int dbcc_reserved;
}
[StructLayout(LayoutKind.Sequential)]
public struct DEV_BROADCAST_HANDLE
{
public int dbch_size;
public int dbch_devicetype;
public int dbch_reserved;
public IntPtr dbch_handle;
public IntPtr dbch_hdevnotify;
public Guid dbch_eventguid;
public long dbch_nameoffset;
public byte dbch_data;
public byte dbch_data1;
}
}
}