USB设备的插入和弹出的监听以及软弹出可移动媒体(如Windows的移除USB设备) .
一、监听USB设备的插入和弹出
当USB设备插入或者弹出时,Windows会产生一条全局消息:WM_DEVICECHANGE
我们需要做的是,获得这条消息的wParam参数,如果为DBT_DEVICEARRIVAL则表示有设备插入并可用,
如果是DBT_DEVICEREMOVECOMPLETE则表示有设备已经移除。再查看lParam参数为DBT_DEVTYP_VOLUME时,
就可以取出DEV_BROADCAST_VOLUME结构的卷号dbcv_unitmask,就知道是哪个卷被插入或者弹出。
代码片段如下:
- using System;
- using System.Runtime.InteropServices;
- /// <summary>
- /// 监听设备的插入和拔出
- /// </summary>
- public class DriveDetector
- {
- /// <summary>
- /// 设备插入事件
- /// </summary>
- public event EventHandler<DriveDectctorEventArgs> DeviceArrived = null;
- /// <summary>
- /// 设备拔出事件
- /// </summary>
- public event EventHandler<DriveDectctorEventArgs> DeviceRemoved = null;
- /// <summary>
- /// 消息处理(HwndSourceHook委托的签名)
- /// </summary>
- /// <param name="hwnd"></param>
- /// <param name="msg"></param>
- /// <param name="wParam"></param>
- /// <param name="lParam"></param>
- /// <param name="handled"></param>
- /// <returns></returns>
- public IntPtr WndProc(
- IntPtr hwnd,
- int msg,
- IntPtr wParam,
- IntPtr lParam,
- ref bool handled)
- {
- if (msg == NativeConstants.WM_DEVICECHANGE)
- {
- #warning USB设备检测目前只支持32位系统)
- switch (wParam.ToInt32())
- {
- case NativeConstants.DBT_DEVICEARRIVAL:
- {
- var devType = Marshal.ReadInt32(lParam, 4);
- if (devType == NativeConstants.DBT_DEVTYP_VOLUME)
- {
- var drive = GetDrive(lParam);
- if (DeviceArrived != null)
- {
- var args = new DriveDectctorEventArgs(drive);
- DeviceArrived(this, args); //触发设备插入事件
- }
- }
- }
- break;
- case NativeConstants.DBT_DEVICEREMOVECOMPLETE:
- {
- var devType = Marshal.ReadInt32(lParam, 4);
- if (devType == NativeConstants.DBT_DEVTYP_VOLUME)
- {
- var drive = GetDrive(lParam);
- if (DeviceRemoved != null)
- {
- var args = new DriveDectctorEventArgs(drive);
- DeviceRemoved(this, args);
- }
- }
- }
- break;
- }
- }
- return IntPtr.Zero;
- }
- private static string GetDrive(IntPtr lParam)
- {
- var volume = (DEV_BROADCAST_VOLUME)Marshal.PtrToStructure(lParam, typeof(DEV_BROADCAST_VOLUME));
- var letter = GetLetter(volume.dbcv_unitmask);
- return string.Format("{0}://", letter);
- }
- /// <summary>
- /// 获得盘符
- /// </summary>
- /// <param name="dbcvUnitmask">
- /// 1 = A
- /// 2 = B
- /// 4 = C...
- /// </param>
- /// <returns>结果是A~Z的任意一个字符或者为'?'</returns>
- private static char GetLetter(uint dbcvUnitmask)
- {
- const char nona = '?';
- const string drives = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
- if (dbcvUnitmask == 0) return nona;
- var i = 0;
- var pom = dbcvUnitmask >> 1;
- while (pom != 0)
- {
- pom = pom >> 1;
- i++;
- }
- if (i < drives.Length)
- return drives[i];
- return nona;
- }
- /*
- private static void GetLetterTest()
- {
- for (int i = 0; i < 67108864; i++)
- {
- Console.WriteLine("{0} - {1}", i, GetLetter((uint)i));
- i = i << 1;
- }
- //0 - ?
- //1 - A
- //3 - B
- //7 - C
- //15 - D
- //31 - E
- //63 - F
- //127 - G
- //255 - H
- //511 - I
- //1023 - J
- //2047 - K
- //4095 - L
- //8191 - M
- //16383 - N
- //32767 - O
- //65535 - P
- //131071 - Q
- //262143 - R
- //524287 - S
- //1048575 - T
- //2097151 - U
- //4194303 - V
- //8388607 - W
- //16777215 - X
- //33554431 - Y
- //67108863 - Z
- }*/
- /// <summary>
- /// 设备插入或拔出事件
- /// </summary>
- public class DriveDectctorEventArgs : EventArgs
- {
- /// <summary>
- /// 获得设备卷标
- /// </summary>
- public string Drive { get; private set; }
- public DriveDectctorEventArgs(string drive)
- {
- Drive = drive ?? string.Empty;
- }
- }
- #region Win32 API
- public partial class NativeConstants
- {
- /// WM_DEVICECHANGE -> 0x0219
- public const int WM_DEVICECHANGE = 537;
- /// BROADCAST_QUERY_DENY -> 0x424D5144
- //public const int BROADCAST_QUERY_DENY = 1112363332;
- //public const int DBT_DEVTYP_DEVICEINTERFACE = 5;
- //public const int DBT_DEVTYP_HANDLE = 6;
- public const int DBT_DEVICEARRIVAL = 0x8000; // system detected a new device
- //public const int DBT_DEVICEQUERYREMOVE = 0x8001; // Preparing to remove (any program can disable the removal)
- public const int DBT_DEVICEREMOVECOMPLETE = 0x8004; // removed
- public const int DBT_DEVTYP_VOLUME = 0x00000002; // drive type is logical volume
- }
- [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
- public struct DEV_BROADCAST_VOLUME
- {
- /// DWORD->unsigned int
- public uint dbcv_size;
- /// DWORD->unsigned int
- public uint dbcv_devicetype;
- /// DWORD->unsigned int
- public uint dbcv_reserved;
- /// DWORD->unsigned int
- public uint dbcv_unitmask;
- /// WORD->unsigned short
- public ushort dbcv_flags;
- }
- [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
- public struct OVERLAPPED
- {
- /// ULONG_PTR->unsigned int
- public uint Internal;
- /// ULONG_PTR->unsigned int
- public uint InternalHigh;
- /// Anonymous_7416d31a_1ce9_4e50_b1e1_0f2ad25c0196
- public Anonymous_7416d31a_1ce9_4e50_b1e1_0f2ad25c0196 Union1;
- /// HANDLE->void*
- public System.IntPtr hEvent;
- }
- [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Explicit)]
- public struct Anonymous_7416d31a_1ce9_4e50_b1e1_0f2ad25c0196
- {
- /// Anonymous_ac6e4301_4438_458f_96dd_e86faeeca2a6
- [System.Runtime.InteropServices.FieldOffsetAttribute(0)]
- public Anonymous_ac6e4301_4438_458f_96dd_e86faeeca2a6 Struct1;
- /// PVOID->void*
- [System.Runtime.InteropServices.FieldOffsetAttribute(0)]
- public System.IntPtr Pointer;
- }
- [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
- public struct Anonymous_ac6e4301_4438_458f_96dd_e86faeeca2a6
- {
- /// DWORD->unsigned int
- public uint Offset;
- /// DWORD->unsigned int
- public uint OffsetHigh;
- }
- [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
- public struct SECURITY_ATTRIBUTES
- {
- /// DWORD->unsigned int
- public uint nLength;
- /// LPVOID->void*
- public System.IntPtr lpSecurityDescriptor;
- /// BOOL->int
- [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.Bool)]
- public bool bInheritHandle;
- }
- #endregion
- }
现在,你可以在你的UI线程上创建一个DriveDetector对象,监听DeviceArrived和DeviceRemoved事件。
然后通过该对象WndProc方法,传递UI线程上的消息。WPF程序可以直接用HwndSource对象AddHook,此方法的签名
与HwndSourceHook委托相同。WinForm程序也没关系,override窗口的void WndProc(ref Message m)方法,按
DriveDetector对象的WndProc签名格式,将m数据传入,或者干脆自己写个WinForm版本的。
我的演示代码效果图如下:(注,上述代码未提供显示代码,请自己编写)
另外,关于磁盘容量的监视可以使用FileSystemWatcher对象。
请参考:http://msdn.microsoft.com/zh-cn/library/cc437966.aspx
摘取代码如下:
- using System;
- using System.IO;
- using System.Security.Permissions;
- public class Watcher
- {
- public static void Main()
- {
- Run();
- }
- [PermissionSet(SecurityAction.Demand, Name="FullTrust")]
- public static void Run()
- {
- string[] args = System.Environment.GetCommandLineArgs();
- // If a directory is not specified, exit program.
- if(args.Length != 2)
- {
- // Display the proper way to call the program.
- Console.WriteLine("Usage: Watcher.exe (directory)");
- return;
- }
- // Create a new FileSystemWatcher and set its properties.
- FileSystemWatcher watcher = new FileSystemWatcher();
- watcher.Path = args[1];
- /* Watch for changes in LastAccess and LastWrite times, and
- the renaming of files or directories. */
- watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite
- | NotifyFilters.FileName | NotifyFilters.DirectoryName;
- // Only watch text files.
- //watcher.Filter = "*.txt";
- watcher.IncludeSubdirectories=true;
- // Add event handlers.
- watcher.Changed += new FileSystemEventHandler(OnChanged);
- watcher.Created += new FileSystemEventHandler(OnChanged);
- watcher.Deleted += new FileSystemEventHandler(OnChanged);
- watcher.Renamed += new RenamedEventHandler(OnRenamed);
- // Begin watching.
- watcher.EnableRaisingEvents = true;
- // Wait for the user to quit the program.
- Console.WriteLine("Press /'q/' to quit the sample.");
- while(Console.Read()!='q');
- }
- // Define the event handlers.
- private static void OnChanged(object source, FileSystemEventArgs e)
- {
- // Specify what is done when a file is changed, created, or deleted.
- Console.WriteLine("File: " + e.FullPath + " " + e.ChangeType);
- }
- private static void OnRenamed(object source, RenamedEventArgs e)
- {
- // Specify what is done when a file is renamed.
- Console.WriteLine("File: {0} renamed to {1}", e.OldFullPath, e.FullPath);
- }
- }
二、软件弹出可移动媒体
网上有一部分关于这方法的代码,我的代码是从MSDN获取的VC++版本,需要调用多个API函数,转换为C#代码如下:
参考:http://support.microsoft.com/kb/165721
- using System;
- using System.Runtime.InteropServices;
- using System.Threading;
- /// <summary>
- /// 弹出可移动媒体
- /// </summary>
- /// <see cref="http://support.microsoft.com/kb/165721"/>
- public static class Eject
- {
- private static void ReportError(string szMsg)
- {
- const string szErrorFormat = "Error {0}: {1}";
- var error = string.Format(szErrorFormat, GetLastError(), szMsg);
- Console.Error.WriteLine(error);
- }
- private static IntPtr OpenVolume(char driveLetter)
- {
- const string volumeFormat = "////.//{0}:";
- const string rootFormat = "{0}://";
- int accessFlags;
- var rootName = string.Format(rootFormat, driveLetter);
- var driveType = GetDriveTypeW(rootName);
- switch (driveType)
- {
- case DRIVE_REMOVABLE:
- accessFlags = GENERIC_READ | GENERIC_WRITE;
- break;
- case DRIVE_CDROM:
- accessFlags = GENERIC_READ;
- break;
- default:
- Console.Error.WriteLine("Cannot eject. Drive type is incorrect.");
- return new IntPtr(INVALID_HANDLE_VALUE);
- }
- var volumeName = string.Format(volumeFormat, driveLetter);
- var hVolume = CreateFileW(
- volumeName,
- accessFlags,
- FILE_SHARE_READ | FILE_SHARE_WRITE,
- IntPtr.Zero,
- OPEN_EXISTING,
- 0,
- IntPtr.Zero);
- if (hVolume == new IntPtr(INVALID_HANDLE_VALUE))
- ReportError("CreateFile");
- return hVolume;
- }
- private static bool CloseVolume(IntPtr hVolume)
- {
- return CloseHandle(hVolume);
- }
- private static bool LockVolume(IntPtr hVolume)
- {
- const int LOCK_TIMEOUT = 10000; //10 Seconds
- const int LOCK_RETRIES = 20;
- var sleepAmount = LOCK_TIMEOUT / LOCK_RETRIES;
- //Do this in a loop until a timeout period has expired
- for (int tryCount = 0; tryCount < LOCK_RETRIES; tryCount++)
- {
- int dwBytesReturned;
- if (DeviceIoControl(
- hVolume,
- FSCTL_LOCK_VOLUME,
- IntPtr.Zero, 0,
- IntPtr.Zero, 0,
- out dwBytesReturned,
- IntPtr.Zero))
- return true; //return
- Thread.Sleep(sleepAmount);
- }
- return false;
- }
- private static bool DismountVolume(IntPtr hVolume)
- {
- int dwBytesReturned;
- return DeviceIoControl(
- hVolume,
- FSCTL_DISMOUNT_VOLUME,
- IntPtr.Zero, 0,
- IntPtr.Zero, 0,
- out dwBytesReturned,
- IntPtr.Zero);
- }
- private static bool PresentRemovalOfVolume(IntPtr hVolume,bool preventRemoval)
- {
- PREVENT_MEDIA_REMOVAL pmrBuffer;
- pmrBuffer.PreventMediaRemoval = preventRemoval;
- var size = Marshal.SizeOf(pmrBuffer);
- IntPtr ptr = Marshal.AllocHGlobal(size);
- try
- {
- Marshal.StructureToPtr(pmrBuffer, ptr, false);
- int dwBytesReturned;
- return DeviceIoControl(
- hVolume,
- IOCTL_STORAGE_MEDIA_REMOVAL,
- ptr, (uint) size,
- IntPtr.Zero, 0,
- out dwBytesReturned,
- IntPtr.Zero
- );
- }
- finally
- {
- Marshal.DestroyStructure(ptr, pmrBuffer.GetType());
- }
- }
- private static bool AutoEjectVolume(IntPtr hVolume)
- {
- int dwBytesReturned;
- return DeviceIoControl(
- hVolume,
- IOCTL_STORAGE_EJECT_MEDIA,
- IntPtr.Zero, 0,
- IntPtr.Zero, 0,
- out dwBytesReturned,
- IntPtr.Zero);
- }
- private static bool RemoveVolumeDefinition(string deviceName)
- {
- return DefineDosDeviceW(DDD_REMOVE_DEFINITION, deviceName, null);
- }
- public static bool EjectVolume(char driveLetter, bool removeVolumeDefinition)
- {
- var removeSafely = false;
- var autoEject = false;
- //Open the volume.
- var hVolume = OpenVolume(driveLetter);
- if (hVolume == new IntPtr(INVALID_HANDLE_VALUE))
- return false;
- //Lock and dismount the volume.
- if(LockVolume(hVolume)&&DismountVolume(hVolume))
- {
- removeSafely = true;
- //Set prevent removal to false and eject the volume.
- if (PresentRemovalOfVolume(hVolume, false) && AutoEjectVolume(hVolume))
- autoEject = true;
- }
- //Close the volume so other processes can use the drive.
- if (!CloseVolume(hVolume))
- return false;
- if(autoEject)
- {
- Console.Out.WriteLine("Media in Drive {0} has been ejected safely.",driveLetter);
- }
- else
- {
- if (removeSafely)
- {
- Console.Out.WriteLine("Media in Drive {0} can be safely removed.", driveLetter);
- }
- else
- {
- Console.Error.WriteLine("Media in Drive {0} is working, and can't be safely removed.", driveLetter);
- return false;
- }
- }
- if(removeVolumeDefinition) RemoveVolumeDefinition(string.Format("{0}:", driveLetter));
- return true;
- }
- public static void Usage()
- {
- Console.Out.WriteLine("Usage: Eject <drive letter>");
- Console.Out.WriteLine();
- }
- static void Main(string[] args)
- {
- if(args.Length != 1)
- {
- Usage();
- return;
- }
- if(!EjectVolume(args[0][0], true))
- {
- Console.Error.WriteLine("Failure ejecting drive {0}.",args[0][0]);
- }
- }
- #region WIN32 API
- /// Return Type: DWORD->unsigned int
- [System.Runtime.InteropServices.DllImportAttribute("kernel32.dll", EntryPoint = "GetLastError")]
- public static extern uint GetLastError();
- /// Return Type: UINT->unsigned int
- ///lpRootPathName: LPCWSTR->WCHAR*
- [System.Runtime.InteropServices.DllImportAttribute("kernel32.dll", EntryPoint = "GetDriveTypeW")]
- public static extern uint GetDriveTypeW([System.Runtime.InteropServices.InAttribute()] [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.LPWStr)] string lpRootPathName);
- /// Return Type: HANDLE->void*
- ///lpFileName: LPCWSTR->WCHAR*
- ///dwDesiredAccess: DWORD->unsigned int
- ///dwShareMode: DWORD->unsigned int
- ///lpSecurityAttributes: LPSECURITY_ATTRIBUTES->_SECURITY_ATTRIBUTES*
- ///dwCreationDisposition: DWORD->unsigned int
- ///dwFlagsAndAttributes: DWORD->unsigned int
- ///hTemplateFile: HANDLE->void*
- [System.Runtime.InteropServices.DllImportAttribute("kernel32.dll", EntryPoint = "CreateFileW")]
- public static extern System.IntPtr CreateFileW([System.Runtime.InteropServices.InAttribute()] [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.LPWStr)] string lpFileName, int dwDesiredAccess, uint dwShareMode, [System.Runtime.InteropServices.InAttribute()] System.IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, [System.Runtime.InteropServices.InAttribute()] System.IntPtr hTemplateFile);
- /// Return Type: BOOL->int
- ///hObject: HANDLE->void*
- [System.Runtime.InteropServices.DllImportAttribute("kernel32.dll", EntryPoint = "CloseHandle")]
- [return: System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.Bool)]
- public static extern bool CloseHandle([System.Runtime.InteropServices.InAttribute()] System.IntPtr hObject);
- /// Return Type: BOOL->int
- ///hDevice: HANDLE->void*
- ///dwIoControlCode: DWORD->unsigned int
- ///lpInBuffer: LPVOID->void*
- ///nInBufferSize: DWORD->unsigned int
- ///lpOutBuffer: LPVOID->void*
- ///nOutBufferSize: DWORD->unsigned int
- ///lpBytesReturned: LPDWORD->DWORD*
- ///lpOverlapped: LPOVERLAPPED->_OVERLAPPED*
- [System.Runtime.InteropServices.DllImportAttribute("kernel32.dll", EntryPoint = "DeviceIoControl")]
- [return: System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.Bool)]
- public static extern bool DeviceIoControl([System.Runtime.InteropServices.InAttribute()] System.IntPtr hDevice, uint dwIoControlCode, [System.Runtime.InteropServices.InAttribute()] System.IntPtr lpInBuffer, uint nInBufferSize, System.IntPtr lpOutBuffer, uint nOutBufferSize, out int lpBytesReturned, System.IntPtr lpOverlapped);
- /// DRIVE_REMOVABLE -> 2
- public const int DRIVE_REMOVABLE = 2;
- /// DRIVE_CDROM -> 5
- public const int DRIVE_CDROM = 5;
- /// INVALID_HANDLE_VALUE -> -1
- public const int INVALID_HANDLE_VALUE = -1;
- /// GENERIC_READ -> (0x80000000L)
- public const int GENERIC_READ = -2147483648;
- /// GENERIC_WRITE -> (0x40000000L)
- public const int GENERIC_WRITE = 1073741824;
- /// FILE_SHARE_READ -> 0x00000001
- public const int FILE_SHARE_READ = 1;
- /// FILE_SHARE_WRITE -> 0x00000002
- public const int FILE_SHARE_WRITE = 2;
- /// OPEN_EXISTING -> 3
- public const int OPEN_EXISTING = 3;
- //WinIoCtl.h
- //
- //#define CTL_CODE( DeviceType, Function, Method, Access ) ( /
- // ((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method) /
- //)
- private const int FILE_DEVICE_FILE_SYSTEM = 0x00000009;
- private const int METHOD_BUFFERED = 0;
- private const int FILE_ANY_ACCESS = 0;
- //#define FSCTL_LOCK_VOLUME CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 6, METHOD_BUFFERED, FILE_ANY_ACCESS)
- public const int FSCTL_LOCK_VOLUME = ((FILE_DEVICE_FILE_SYSTEM) << 16) | ((FILE_ANY_ACCESS) << 14) | ((6) << 2) | (METHOD_BUFFERED);
- public const int FSCTL_UNLOCK_VOLUME = ((FILE_DEVICE_FILE_SYSTEM) << 16) | ((FILE_ANY_ACCESS) << 14) | ((7) << 2) | (METHOD_BUFFERED);
- public const int FSCTL_DISMOUNT_VOLUME = ((FILE_DEVICE_FILE_SYSTEM) << 16) | ((FILE_ANY_ACCESS) << 14) | ((8) << 2) | (METHOD_BUFFERED);
- //#define IOCTL_STORAGE_BASE FILE_DEVICE_MASS_STORAGE
- private const int FILE_DEVICE_MASS_STORAGE = 0x0000002d;
- private const int IOCTL_STORAGE_BASE = FILE_DEVICE_MASS_STORAGE;
- //#define FILE_READ_ACCESS ( 0x0001 ) // file & pipe
- private const int FILE_READ_ACCESS = 0x0001;
- //#define IOCTL_STORAGE_MEDIA_REMOVAL CTL_CODE(IOCTL_STORAGE_BASE, 0x0201, METHOD_BUFFERED, FILE_READ_ACCESS)
- public const int IOCTL_STORAGE_MEDIA_REMOVAL =
- ((IOCTL_STORAGE_BASE) << 16) | ((FILE_READ_ACCESS) << 14) | ((0x0201) << 2) | (METHOD_BUFFERED);
- //#define IOCTL_STORAGE_EJECT_MEDIA CTL_CODE(IOCTL_STORAGE_BASE, 0x0202, METHOD_BUFFERED, FILE_READ_ACCESS)
- public const int IOCTL_STORAGE_EJECT_MEDIA =
- ((IOCTL_STORAGE_BASE) << 16) | ((FILE_READ_ACCESS) << 14) | ((0x0202) << 2) | (METHOD_BUFFERED);
- [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
- public struct PREVENT_MEDIA_REMOVAL
- {
- /// BOOLEAN->BYTE->unsigned char
- [MarshalAs(UnmanagedType.I1)]
- public bool PreventMediaRemoval;
- }
- #region Remove Volume Definition
- /// DDD_REMOVE_DEFINITION -> 0x00000002
- public const int DDD_REMOVE_DEFINITION = 2;
- /// Return Type: BOOL->int
- ///dwFlags: DWORD->unsigned int
- ///lpDeviceName: LPCWSTR->WCHAR*
- ///lpTargetPath: LPCWSTR->WCHAR*
- [System.Runtime.InteropServices.DllImportAttribute("kernel32.dll", EntryPoint="DefineDosDeviceW")]
- [return: System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.Bool)]
- public static extern bool DefineDosDeviceW(uint dwFlags, [System.Runtime.InteropServices.InAttribute()] [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.LPWStr)] string lpDeviceName, [System.Runtime.InteropServices.InAttribute()] [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.LPWStr)] string lpTargetPath) ;
- #endregion
- #endregion
- }
预览:(注,弹出可移动磁盘,并不会删除驱动器号,但设备已经被弹出,如图中的h盘)
注:已改进,安全弹出后,通过DDD_REMOVE_DEFINITION移除h盘。