当 运行 驱动器上有 FileSystemWatcher 时,无法安全地弹出移除 USB 记忆棒

Cannot eject safely removed a USB memory stick when running a FileSystemWatcher on the drive

我需要在我的 USB 驱动器上创建一个记录文件 activity。

目前我能做的:

  1. 使用ManagementEventWatcher检测U盘的插入和拔出(检测拔出内存后的拔出):

    using System.Management;
    
    var insertQuery = new WqlEventQuery("SELECT * FROM Win32_VolumeChangeEvent WHERE EventType = 2");
    var insertWatcher = new ManagementEventWatcher(insertQuery);
    insertWatcher.EventArrived += DeviceInsertedEvent;
    insertWatcher.Start();
    
  2. 使用FileSystemWatcher我可以记录所有的动作,例如。创建、修改、删除和重命名的文件:

         FileSystemWatcher m_Watcher = new FileSystemWatcher();
         m_Watcher.Path = e.PathDevice + "\";
         string strFilter = "*.*";
         m_Watcher.Filter = strFilter;
         m_Watcher.NotifyFilter = 
                      NotifyFilters.Attributes |
                      NotifyFilters.FileName;
         m_Watcher.IncludeSubdirectories = true;
         m_Watcher.Created += new FileSystemEventHandler(OnCreated);
         m_Watcher.EnableRaisingEvents = true;
    

问题:

由于 FileSystemWatcher 正在查看该单元,如果我尝试安全弹出该单元,它会告诉我不能,因为我的应用程序进程正在使用它。此变量设置为 true EnableRaisingEvents = true; 时启用事件以侦听内存中的变化并且是不允许我安全删除内存的变量,我进行了测试并启动了应用程序并将其设置为false EnableRaisingEvents = false; 我可以安全地删除内存。

可能的解决方案:

如何在操作系统删除设备之前检测到设备已删除?这样我就可以停止在设备上使用 FileSystemWatcher 并安全地移除 USB 设备。

几天后寻找植入问题的答案我找到了解决方案

This article was very helpful to solve the problem and this.

如果我们使用服务或windows form application,解决方案就会改变。 我的回答是基于一个服务.

服务有一个控制处理程序,它接收来自 Windows 的所有消息。这些可能包括停止或暂停服务的代码,或者在我们的例子中,包括设备事件。我们需要注册我们自己的服务处理程序,这样我们才能捕获设备事件。这将禁用所有回调,如 OnStop 除了 OnStart,它在我们告诉 Windows 使用我们的处理程序之前被调用。

Windows API 函数是 RegisterServiceCtrlHandlerEx,它接受服务名称和接收消息时调用的回调函数。我们将在我们服务的 OnStart 函数中调用它。

服务控制处理程序的签名是这样的:

public delegate int ServiceControlHandlerEx(int control,int eventType, IntPtr eventData, IntPtr context);

public partial class Service1 : ServiceBase{

  private FileSystemWatcher fileSystemWatcher;
  private IntPtr deviceNotifyHandle;
  private IntPtr deviceEventHandle;
  private IntPtr directoryHandle;
  private Win32.ServiceControlHandlerEx myCallback;

  public Service1()
  {
     InitializeComponent();
  }

  protected override void OnStart(string[] args)
  {
     base.OnStart(args);
     //
     RegisterDeviceNotification();

    fileSystemWatcher = new FileSystemWatcher();
    fileSystemWatcher.Created += new System.IO.FileSystemEventHandler(fileSystemWatcher_Created);
    fileSystemWatcher.Deleted += new System.IO.FileSystemEventHandler(fileSystemWatcher_Deleted);
    fileSystemWatcher.Changed += new System.IO.FileSystemEventHandler(fileSystemWatcher_Changed);
    fileSystemWatcher.Renamed += new System.IO.RenamedEventHandler(fileSystemWatcher_Renamed);
  }

}

注册设备通知

在 OnStart 方法中,除了注册控制处理程序外,我们还将使用 Win32 API 函数 RegisterDeviceNotification 注册设备通知。我们给它我们服务的句柄,指向 DEV_BROADCAST_DEVICEINTERFACE 结构的指针(告诉函数注册 class 设备)和一些标志,其中有 DEVICE_NOTIFY_SERVICE_HANDLE,它指定例如,调用者是一项服务而不是 window。它 returns 一个句柄,我们必须保留它以便在我们不再需要设备消息时取消注册(例如,我们可以在 SERVICE_CONTROL_STOP 事件中这样做)。

使用此函数可以让我们捕获 DBT_DEVICEARRIVALDBT_DEVICEREMOVECOMPLETE 事件类型。我们通过服务控制处理程序的 eventType 参数获取它们。在那里,我们可以处理 SERVICE_CONTROL_DEVICEEVENT 并做任何我们喜欢的事情。

  public void RegisterDeviceNotification()
  {
      InitArrayDevNotifyHandle();

      myCallback = new Win32.ServiceControlHandlerEx(ControlHandler);
      Win32.RegisterServiceCtrlHandlerEx(service.ServiceName, myCallback, IntPtr.Zero);

      if (service.GetServiceHandle() == 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(service.GetServiceHandle(), buffer, Win32.DEVICE_NOTIFY_SERVICE_HANDLE | Win32.DEVICE_NOTIFY_ALL_INTERFACE_CLASSES);

      if (deviceEventHandle == IntPtr.Zero)
      {
        // TODO handle error
      }

  }

DBT_DEVICEQUERYREMOVE - 解决方案

这里主要是回答问题 我需要通知应用程序更改设备的硬件配置。特别是在 SO 之前更换 USB 存储器将移除存储器。感谢@ZdeněkJelínek,我能够找到事件 DBT_DEVICEQUERYREMOVE 请求删除设备或媒体的权限。此事件在设备即将被删除之前举行。

解决方案是创建一个设备本身的句柄,在 DEV_BROADCAST_HANDLE 结构中使用它,并用它注册到我们的服务控制处理程序。为了完成这一切,需要做几件事:

查找设备的盘符。我能够使用 ManagementEventWatcher class,它允许我订阅设备插入事件,并为我提供有关插入设备的信息。 所有这些都是通过 CreateFileHandle 函数获得设备句柄来完成的。只有在这之后我们才能获得DBT_DEVICEQUERYREMOVE事件,禁用FileSystemWatcher,并允许USB被自由移除。

private int ControlHandler(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)
      {
        string c;
        switch (eventType)
        {
          case Win32.DBT_DEVICEARRIVAL:
           //This is an example ... I do not use the DBT_DEVICEARRIVAL event, but it can be used. Instead use the ManagementEventWatcher class to detect when a device arrives and driveLetter.
              RegisterForHandle(driveLetter);
              fileSystemWatcher.Path = driveLetter + ":\";
              fileSystemWatcher.EnableRaisingEvents = true;
             break;
          case Win32.DBT_DEVICEQUERYREMOVE:

              Win32.DEV_BROADCAST_HDR hdrR;
              Win32.DEV_BROADCAST_HANDLE dbhdl;
              hdrR = (Win32.DEV_BROADCAST_HDR)
              Marshal.PtrToStructure(eventData, typeof(Win32.DEV_BROADCAST_HDR));

              if (hdrR.dbcc_devicetype == Win32.DBT_DEVTYP_HANDLE)
              {
                dbhdl = (Win32.DEV_BROADCAST_HANDLE)
                Marshal.PtrToStructure(eventData, typeof(Win32.DEV_BROADCAST_HANDLE));

                UnregisterHandles();
                fileSystemWatcher.EnableRaisingEvents = false;
                fileSystemWatcher = null;
              }

              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
    }
}

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;
    }
}



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;
    }
}