我需要在我的 USB 驱动器上创建一个记录文件活动。
到目前为止我能做什么:
-
它使用 ManagementEventWatcher 检测 USB 存储器的插入和提取(检测内存被移除后的提取):
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;
使事件能够侦听内存中的更改,并且是不允许我安全删除内存的变量,我做了一个测试并启动了应用程序,我将其设置为 falseEnableRaisingEvents = false;
我可以安全地删除内存。
可能的解决方案:
如何在操作系统删除设备之前检测到设备的删除?这样,我可以停止在设备上使用文件系统观察器并安全地删除USB设备。
经过几天的寻找种植问题的答案,我找到了解决方案。
这篇文章对解决问题和这个问题非常有帮助。
如果我们使用服务或 Windows 表单应用程序,解决方案会发生变化。我的答案是基于服务。
服务有一个控制处理程序,用于接收来自 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
结构的指针(告诉函数注册一类设备)和一些标志,其中包括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 - 解决方案
这是回答问题的主要内容 我需要通知应用程序对设备的硬件配置进行更改。具体来说,在 SO 之前更换 USB 存储器将移除内存。多亏了@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;
}
}