ServiceControlHandler用于usb设备通知,OnStop()无法实现

时间:2013-01-25 01:59:47

标签: c# winapi service service-control-manager

我想写一个侦听设备通知的服务(USB媒体插入,删除)。在C#服务中侦听设备通知的问题是,System.Windows.Forms.Control.WndProc不可用,因为Windows服务没有任何窗口。
我找到了this HowTo on how to write such a service。该文章的作者找到了一种解决方法,让服务侦听设备通知而不是服务控制消息,因此,该服务不再支持OnStop()。

(更新26.01.13 :)
遗憾的是,我并不了解服务控制管理器和Windows API。我想知道是否可以注册服务控制消息和USB设备通知,或者这是否真的是服务监听设备通知的唯一选择。我还没有找到任何(可以理解的)信息来解决我的问题 可能在不生成窗口的情况下使用System.Windows.Forms.Control.WndProc(我只需要添加System.Windows.Forms程序集,对吗?)。

(更新27.01.13 :)
我刚刚发现了这个问题:Cannot start desktop application from Windows service on Windows 7
第二个answer表示Windows服务在Windows Vista中接收以安全为中心的改造,现在在会话0中创建了GUI元素,即使选中“允许服务与桌面交互”也是如此。这是否意味着,我可以创建一个Windows窗体然后接收USB设备事件(因此,我不需要弄乱ServiceControlHandler?这样做是否有任何警告或问题?

简而言之,我需要一个能够执行以下任一操作的解决方案:

  1. 再次使OnStop可用,或
  2. 提供另一种在Windows C#服务中侦听USB设备通知的方法

  3. 我的源代码目前如下。它几乎与我在第一段中链接的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;
            }
        }
    }
    

1 个答案:

答案 0 :(得分:1)

我以前曾在同一主题上工作过,而我最终走的路是简单地构造一个Window并转发消息。我确信我从第三方获得了相关代码,因为我在2013年对此问题进行了评论时引用了现已失效的链接。

让我们看一下代码。

首先,这是完整的MessageWindow实现:

using System;
using System.Threading;
using System.Windows.Forms;
using System.ComponentModel;
using System.Collections.Generic;

namespace Foo.Windows {
  public class MessageReceivedEventArgs : EventArgs {
    private readonly Message _message;

    public MessageReceivedEventArgs( Message message ) {
      _message = message;
    }

    public Message Message {
      get { return _message; }
    }
  }

  public static class MessageEvents {
    private static object _lock = new object();
    private static MessageWindow _window;
    private static IntPtr _windowHandle;
    private static SynchronizationContext _context;

    public static event EventHandler<MessageReceivedEventArgs> MessageReceived;

    public static void WatchMessage( int message ) {
      EnsureInitialized();
      _window.RegisterEventForMessage( message );
    }

    public static IntPtr WindowHandle {
      get {
        EnsureInitialized();
        return _windowHandle;
      }
    }

    private static void EnsureInitialized() {
      lock( _lock ) {
        if( _window == null ) {
          _context = AsyncOperationManager.SynchronizationContext;
          using( ManualResetEvent mre = new ManualResetEvent( false ) ) {
            Thread t = new Thread( (ThreadStart) delegate {
                                                   _window = new MessageWindow();
                                                   _windowHandle = _window.Handle;
                                                   mre.Set();
                                                   Application.Run();
                                                 } );
            t.Name = "MessageEvents message loop";
            t.IsBackground = true;
            t.Start();

            mre.WaitOne();
          }
        }
      }
    }

    private class MessageWindow : Form {
      private ReaderWriterLock _lock = new ReaderWriterLock();
      private Dictionary<int, bool> _messageSet = new Dictionary<int, bool>();

      public void RegisterEventForMessage( int messageID ) {
        _lock.AcquireWriterLock( Timeout.Infinite );
        _messageSet[ messageID ] = true;
        _lock.ReleaseWriterLock();
      }

      protected override void WndProc( ref Message m ) {
        _lock.AcquireReaderLock( Timeout.Infinite );
        bool handleMessage = _messageSet.ContainsKey( m.Msg );
        _lock.ReleaseReaderLock();

        if( handleMessage ) {
          MessageEvents._context.Send( delegate( object state ) {
            EventHandler<MessageReceivedEventArgs> handler = MessageEvents.MessageReceived;
            if( handler != null )
              handler( null, new MessageReceivedEventArgs( (Message)state ) );
          }, m );
        }

        base.WndProc( ref m );
      }
    }
  }
}

为完整起见,这些是与设备更改检测过程相关的常数:

using System;
using System.Runtime.InteropServices;

namespace Foo.Windows {
  internal class NativeMethods {
    /// <summary>
    /// Notifies an application of a change to the hardware configuration of a device or the computer.
    /// </summary>
    public static Int32 WM_DEVICECHANGE = 0x0219;

    /// <summary>
    /// The system broadcasts the DBT_DEVICEARRIVAL device event when a device or piece of media has been inserted and becomes available.
    /// </summary>
    public static Int32 DBT_DEVICEARRIVAL = 0x8000;

    /// <summary>
    /// Serves as a standard header for information related to a device event reported through the WM_DEVICECHANGE message.
    /// </summary>
    [StructLayout( LayoutKind.Sequential )]
    public struct DEV_BROADCAST_HDR {
      public Int32 dbch_size;
      public Int32 dbch_devicetype;
      public Int32 dbch_reserved;
    }

    public enum DBT_DEVTYP : uint {
      /// <summary>
      /// OEM- or IHV-defined device type.
      /// </summary>
      DBT_DEVTYP_OEM = 0x0000,

      /// <summary>
      /// Logical volume.
      /// </summary>
      DBT_DEVTYP_VOLUME = 0x0002,

      /// <summary>
      /// Port device (serial or parallel).
      /// </summary>
      DBT_DEVTYP_PORT = 0x0003,

      /// <summary>
      /// Class of devices.
      /// </summary>
      DBT_DEVTYP_DEVICEINTERFACE = 0x0005,

      /// <summary>
      /// File system handle.
      /// </summary>
      DBT_DEVTYP_HANDLE = 0x0006
    }

    /// <summary>
    /// Contains information about a OEM-defined device type.
    /// </summary>
    [StructLayout( LayoutKind.Sequential )]
    public struct DEV_BROADCAST_VOLUME {
      public Int32 dbcv_size;
      public Int32 dbcv_devicetype;
      public Int32 dbcv_reserved;
      public Int32 dbcv_unitmask;
      public Int16 dbcv_flags;
    }
  }
}

现在您要做的就是注册您感兴趣的消息并在事件发生时进行处理。这些应该是该过程的相关部分:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Text;
using System.Threading;
using Foo.Windows;

namespace Foo.Core {
  class Daemon {

    private static void InternalRun() {
      MessageEvents.WatchMessage( NativeMethods.WM_DEVICECHANGE );
      MessageEvents.MessageReceived += MessageEventsMessageReceived;
    }

    private static void MessageEventsMessageReceived( object sender, MessageReceivedEventArgs e ) {
      // Check if this is a notification regarding a new device.);
      if( e.Message.WParam == (IntPtr)NativeMethods.DBT_DEVICEARRIVAL ) {
        Log.Info( "New device has arrived" );

        // Retrieve the device broadcast header
        NativeMethods.DEV_BROADCAST_HDR deviceBroadcastHeader =
          (NativeMethods.DEV_BROADCAST_HDR)
          Marshal.PtrToStructure( e.Message.LParam, typeof( NativeMethods.DEV_BROADCAST_HDR ) );

        if( (int)NativeMethods.DBT_DEVTYP.DBT_DEVTYP_VOLUME == deviceBroadcastHeader.dbch_devicetype ) {
          Log.Info( "Device type is a volume (good)." );

          NativeMethods.DEV_BROADCAST_VOLUME volumeBroadcast =
            (NativeMethods.DEV_BROADCAST_VOLUME)
            Marshal.PtrToStructure( e.Message.LParam, typeof( NativeMethods.DEV_BROADCAST_VOLUME ) );

          Log.InfoFormat( "Unit masked for new device is: {0}", volumeBroadcast.dbcv_unitmask );

          int driveIndex = 1;
          int bitCount = 1;
          while( bitCount <= 0x2000000 ) {
            driveIndex++;
            bitCount *= 2;

            if( ( bitCount & volumeBroadcast.dbcv_unitmask ) != 0 ) {
              Log.InfoFormat( "Drive index {0} is set in unit mask.", driveIndex );
              Log.InfoFormat( "Device provides drive: {0}:", (char)( driveIndex + 64 ) );

              int index = driveIndex;

              Thread spawnProcessThread = new Thread( () => SpawnDeviceProcess( string.Format( "{0}", (char)( index + 64 ) ) ) );
              spawnProcessThread.Start();
            }
          }

        } else {
          Log.InfoFormat( "Device type is {0} (ignored).", Enum.GetName( typeof( NativeMethods.DBT_DEVTYP ), deviceBroadcastHeader.dbch_devicetype ) );
        }
      }
    }
  }
}

在我的项目中,我只对检索插入的USB密钥的驱动器号感兴趣。此代码检索该驱动器号,然后为该设备生成专用的处理程序进程。

这是在C#服务中实现的。 System.Windows.Forms必须被引用。应该就可以了。

我也许可以将整个项目放到GitHub上,但是正确清理它似乎非常耗时。我希望这是足够的信息,能够复制结果。