Невозможно безопасно извлечь извлеченную карту памяти USB при запуске FileSystemWatcher на диске
Мне нужно создать запись файла активности на моем USB-накопителе.
Что я могу сделать до сих пор:
Он обнаруживает вставку и извлечение (обнаруживает извлечение после удаления памяти) из памяти USB с помощью ManagementEventWatcher:
using System.Management; var insertQuery = new WqlEventQuery("SELECT * FROM Win32_VolumeChangeEvent WHERE EventType = 2"); var insertWatcher = new ManagementEventWatcher(insertQuery); insertWatcher.EventArrived += DeviceInsertedEvent; insertWatcher.Start();
С помощью
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;
включить события, чтобы прослушать изменения в памяти, и это тот, который не позволяет мне безопасно удалить память, я сделал тест и запустил приложение, и я установил его в ложь EnableRaisingEvents = false;
и я могу безопасно удалить память.
Возможное решение:
Как я могу обнаружить удаление устройства до того, как операционная система удалит его? Таким образом, я могу прекратить использование FileSystemWatcher на устройстве и безопасно удалить USB-устройство.
1 ответ
После нескольких дней поиска ответа на посаженную проблему я нашел решение.
Эта статья была очень полезна для решения этой проблемы.
Решение меняется, если мы используем сервис или приложение Windows Form. Мой ответ основан на услуге.
Сервисы имеют обработчик управления, который получает все сообщения из Windows. Они могут включать коды для остановки или приостановки службы или, как в нашем случае, события устройства. Нам нужно зарегистрировать наш собственный обработчик службы, чтобы мы могли отлавливать события устройства. Это отключит все обратные вызовы, такие как OnStop, кроме OnStart, который вызывается перед тем, как мы скажем Windows использовать наш обработчик.
Функция Windows API для этого - RegisterServiceCtrlHandlerEx, которая принимает имя службы и функцию обратного вызова для вызова при получении сообщения. Мы будем вызывать его в функции OnStart нашего сервиса.
Подпись обработчика управления службами выглядит так:
открытый делегат 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
struct (сообщающая функции о регистрации для класса устройств) и некоторые флаги, среди которых есть DEVICE_NOTIFY_SERVICE_HANDLE
, который указывает, что вызывающая сторона является службой, а не окном, например. Он возвращает дескриптор, который мы должны сохранить, чтобы отменить регистрацию, когда нам больше не нужны сообщения устройства (например, мы могли бы сделать это в SERVICE_CONTROL_STOP
событие).
Использование этой функции позволяет нам захватить DBT_DEVICEARRIVAL
а также DBT_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 - Решение
Здесь главное, чтобы ответить на вопрос, который мне был нужен, уведомляет о внесении изменений в аппаратную конфигурацию устройства. В частности, изменение памяти USB, прежде чем SO удалит память. Благодаря @ZdeněkJelínek я смог найти событие DBT_DEVICEQUERYREMOVE
это разрешение запрашивается для удаления устройства или части носителя. Это событие проводится как раз перед тем, как устройство будет удалено.
Решение состоит в том, чтобы создать дескриптор самого устройства, использовать его в DEV_BROADCAST_HANDLE
структура, и зарегистрируйтесь в нашем обработчике управления службами. Для того, чтобы выполнить все это, требуется несколько вещей:
Найдите, какая буква диска устройства. Я был в состоянии использовать ManagementEventWatcher
класс, который позволил мне подписаться на событие вставки устройства, и он предоставляет мне информацию о вставленном устройстве. Все это было сделано для того, чтобы получить дескриптор устройства, используя 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;
}
}