VC++位移操作>>和<<以及邏輯驅動器插拔產生的掩碼dbv.dbcv_unitmask進行分析的相關代碼
- 一、VC++位移操作符<<和>>
- 1、右位移操作符 >>:
- 2、左位移操作符 <<:
- 二、邏輯驅動器插拔產生的掩碼 dbv.dbcv_unitmask 進行分析的相關代碼
- 1、設備發生改變的消息 WM_DEVICECHANGE
- A、微軟官方 VC++ 代碼:
- B、驅動器號與設備掩碼 dbcv_unitmask 的關系
- 2、設備狀態發生改變 WM_DEVICECHANGE 的改進 C# 代碼:
一、VC++位移操作符<<和>>
1、右位移操作符 >>:
右移運算符>>,是將一個數的二進制位全部右移若干位,低位移出(舍棄)。高位的空位補符號位,即正數補零,負數補1。
語法格式: 需要移位的數字 >> 移位的次數
例如:
11 >> 2,等于2,是將數字11的二進制右移2位,即00001011------>00000010(2);
2 >> 1:相當于2/2
99 >> 1: 相當于 99/2 向下取整為49
99 >> 2:相當于 99/pow(2,2)向下取整為24
999 >> i:相當于999/pow(2,i)
整數 >> i:相當于將這個整數化為二進制整數,并去掉這個數的末尾的 i 位數字
cout << (2 >> 1) << endl;//輸出為 1
cout << (99 >> 1) << endl;//輸出為 49
cout << (99 >> 2) << endl;// 24
cout << (999 >> 3) << endl;// 124
cout << (1000 >> 4) << endl;// 62
2、左位移操作符 <<:
左移運算符<<,是將一個數的二進制位全部左移若干位,低位右補0,高位左移后溢出,舍棄。
語法格式: 需要移位的數字 << 移位的次數
例如:
將a的二進制數左移2位,右補0。
若a=15,即二進制數00001111,左移2位得00111100,即十進制數60。
3 << 2,則是將數字3左移2位。即00000011----->00001100(12);
2 << 1:相當于22。
99 << 1: 相當于 992 。
99 << 2:相當于 99*pow(2,2)。
999 << i:相當于999 * pow(2,i)。
整數 << i:相當于將這個整數化為二進制整數,并在這個數的末尾加上 i 個0。
cout << (2 << 1) << endl; //輸出為 4
cout << (99 << 1) << endl; //輸出為 198
cout << (99 << 2) << endl; // 396
cout << (999 << 3) << endl; // 7992
cout << (1000 << 4) << endl; // 16000
數字1 << 左移:
1 << i = pow(2,i)
2 << i = pow(2,i) * 2
3 << i = pow(2,i) * 3
x << i = pow(2,i) * x
cout << (1 << 0) << endl; // 1
cout << (1 << 1) << endl; // 2
cout << (1 << 2) << endl; // 4
cout << (1 << 3) << endl; // 8
cout << (1 << 4) << endl; // 16
二、邏輯驅動器插拔產生的掩碼 dbv.dbcv_unitmask 進行分析的相關代碼
1、設備發生改變的消息 WM_DEVICECHANGE
Windows 在設備發生的情況下,如可插拔設備 U盤、移動硬盤、USB串口的插入和彈出,光盤插入和彈出,會有相應的響應,在這里對于編程的識別進行探討:
Windows 定義了一些控制代碼和指令,當添加新設備或介質(如 CD 或 DVD)以及刪除現有設備或介質時,Windows 會給所有頂級窗口發送一組默認設備改變的消息 WM_DEVICECHANGE ,在微軟官方文檔中有具體描述:
檢測介質插入或刪除
每個 WM_DEVICECHANGE 消息都有描述更改的關聯事件,以及提供有關更改的詳細信息的結構。 該結構包含與事件無關的標頭 DEV_BROADCAST_HDR,后跟與事件相關的成員。 與事件相關的成員描述應用事件的設備。 若要使用此結構,應用程序必須首先確定事件類型和設備類型。 然后,它們可以使用正確的結構執行適當的操作。
當用戶將新的 CD 或 DVD 插入驅動器時,應用程序會收到 WM_DEVICECHANGE 消息和 DBT_DEVICEARRIVAL 事件。 應用程序必須檢查事件,以確保到達的設備的類型是卷(dbch_devicetype 成員是 DBT_DEVTYP_VOLUME),并且更換會影響介質(dbcv_flags 成員是 DBTF_MEDIA)。
當用戶從驅動器中刪除 CD 或 DVD 時,應用程序會收到 WM_DEVICECHANGE 消息和 DBT_DEVICEREMOVECOMPLETE 事件。 同樣,應用程序必須檢查事件,以確保要刪除的設備是卷,并且更換會影響介質。
A、微軟官方 VC++ 代碼:
//C++
#include <windows.h>
#include <dbt.h>
#include <strsafe.h>
#pragma comment(lib, "user32.lib" )void Main_OnDeviceChange( HWND hwnd, WPARAM wParam, LPARAM lParam );
char FirstDriveFromMask( ULONG unitmask ); //prototype/*------------------------------------------------------------------Main_OnDeviceChange( hwnd, wParam, lParam )描述:處理發送到應用程序頂級窗口的 WM_DEVICECHANGE 消息。
--------------------------------------------------------------------*/void Main_OnDeviceChange( HWND hwnd, WPARAM wParam, LPARAM lParam ){PDEV_BROADCAST_HDR lpdb = (PDEV_BROADCAST_HDR)lParam;TCHAR szMsg[80];switch(wParam ){case DBT_DEVICEARRIVAL:// 檢查驅動器中是否插入了CD或DVD。if (lpdb -> dbch_devicetype == DBT_DEVTYP_VOLUME)//驅動類型為邏輯卷{PDEV_BROADCAST_VOLUME lpdbv = (PDEV_BROADCAST_VOLUME)lpdb;if (lpdbv -> dbcv_flags & DBTF_MEDIA){StringCchPrintf( szMsg, sizeof(szMsg)/sizeof(szMsg[0]), TEXT("驅動器 %c :介質已插入。\n"), FirstDriveFromMask(lpdbv ->dbcv_unitmask) );MessageBox( hwnd, szMsg, TEXT("WM_DEVICECHANGE"), MB_OK );}}break;case DBT_DEVICEREMOVECOMPLETE:// 檢查是否已從驅動器中彈出CD或DVD。if (lpdb -> dbch_devicetype == DBT_DEVTYP_VOLUME)//驅動類型為邏輯卷{PDEV_BROADCAST_VOLUME lpdbv = (PDEV_BROADCAST_VOLUME)lpdb;if (lpdbv -> dbcv_flags & DBTF_MEDIA){StringCchPrintf( szMsg, sizeof(szMsg)/sizeof(szMsg[0]), TEXT("驅動器 %c: 介質已彈出。\n" ),FirstDriveFromMask(lpdbv ->dbcv_unitmask) );MessageBox( hwnd, szMsg, TEXT("WM_DEVICECHANGE" ), MB_OK );}}break;default:/*由于其他設備或原因,處理其他 WM_DEVICECHANGE 通知。*/ ;}
}/*------------------------------------------------------------------FirstDriveFromMask( unitmask )
描述
從驅動器號掩碼中查找第一個有效的驅動器號。
掩碼的格式必須為 bit 0=A、bit 1=B、bit 2=C,依此類推。
當相應的位設置為1時,將定義有效的驅動器號。
返回找到的第一個驅動器號。
--------------------------------------------------------------------*/char FirstDriveFromMask( ULONG unitmask ){char i;for (i = 0; i < 26; ++i){if (unitmask & 0x1)break;unitmask = unitmask >> 1;}return( i + 'A' );
}
B、驅動器號與設備掩碼 dbcv_unitmask 的關系
從上面C++代碼上可以看到對于位操作的右移指令 >>,從中可以看到驅動器掩碼與驅動器號的對應關系:
驅動器號 | 字母值 | unitmask | unitmask | unitmask |
---|---|---|---|---|
卷號字母 | 10進制數 | 16進制數 | 10進制數 | 二進制數 |
A | 65 | 1 | 1=2^0 | 10^00 =0000000000000001 |
B | 66 | 2 | 2=2^1 | 10^01 =0000000000000010 |
C | 67 | 4 | 4=2^2 | 10^02 =0000000000000100 |
D | 68 | 8 | 8=2^3 | 10^03 =0000000000001000 |
E | 69 | 10 | 16=2^4 | 10^04 =0000000000010000 |
F | 70 | 20 | 32=2^5 | 10^05 =0000000000100000 |
G | 71 | 40 | 64=2^6 | 10^06 =0000000001000000 |
H | 72 | 80 | 128=2^7 | 10^07 =0000000010000000 |
I | 73 | 100 | 256=2^8 | 10^08 =0000000100000000 |
J | 74 | 200 | 512=2^9 | 10^09 =0000001000000000 |
K | 75 | 400 | 1024=2^10 | 10^10 =000001000000000 |
L | 76 | 800 | 2048=2^11 | 10^11 =000010000000000 |
M | 77 | 1000 | 4096=2^12 | 10^12 =000100000000000 |
N | 78 | 2000 | 8096=2^13 | 10^13 =001000000000000 |
O | 79 | 4000 | 16384=2^14 | 10^14 =010000000000000 |
P | 80 | 8000 | 32768=2^15 | 10^15 =100000000000000 |
Q | 以此類推 |
從上表對應關系可以看出,對于驅動器號 A 盤的 unitmask 值,不能再執行右位移>>操作。
在對移動U盤和移動硬盤編寫的識別程序中,參照應用微軟以上 C++ 代碼遷移到 C# 中,可以發現只有一個分區的移動U盤和移動硬盤完全沒有問題。但對于移動U盤和移動硬盤有多個分區時,程序就無法完整識別(Windows 11),unitmask 值會隨著設備插入出現 2 個值,第一個值對應多分區的第一個盤符值,第二個值就無法用微軟提供的程序算法解釋了。通過對多分區移動盤的識別發現,如果移動盤符是 H、I、J、K 四個盤,系統給出了 2 個 unitmask 值,第一個值對應的第一個 H 盤,第二個值比對應的最后一個盤符 K 大,取整后就是 K 盤。
當設備被插入/拔出的時候,WINDOWS會向每個窗體發送WM_DEVICECHANGE 消息,當消息的wParam 值等于 DBT_DEVICEARRIVAL 時,表示Media設備被插入并且已經可用;如果wParam值等于DBT_DEVICEREMOVECOMPLETE,表示Media設備已經被移出。
它們的lParam都指向一個 DEV_BROADCAST_HDR結構體,其原形如下:
//C++
typedef struct _DEV_BROADCAST_HDR
{DWORD dbch_size;DWORD dbch_devicetype;DWORD dbch_reserved;
} DEV_BROADCAST_HDR, *PDEV_BROADCAST_HDR;
這個結構體僅僅是一個“頭”(HDR),其后還有附加數據,dbch_size表示結構體實例的字節數,當其中的dbch_devicetype字段值等于DBT_DEVTYP_VOLUME時,表示當前設備是邏輯驅動器,且lParam實際上指向的應該是DEV_BROADCAST_VOLUME 結構體實例,DEV_BROADCAST_VOLUME 結構體原形如下:
//C++
typedef struct _DEV_BROADCAST_VOLUME
{DWORD dbcv_size;DWORD dbcv_devicetype;DWORD dbcv_reserved;DWORD dbcv_unitmask;WORD dbcv_flags;
} DEV_BROADCAST_VOLUME, *PDEV_BROADCAST_VOLUME;
其中dbcv_unitmask 字段表示當前改變的驅動器掩碼,第一位表示驅動器號A,第二位表示驅動器號B,第三位表示驅動器號C,以此類推…… dbcv_flags 表示驅動器的類別,如果等于1,則是光盤驅動器;如果是2,則是網絡驅動器;如果是硬盤、U盤則都等于0
所以,我只需要在程序中捕捉WM_DEVICECHANGE 消息,然后根據具體情況去處理即可。
2、設備狀態發生改變 WM_DEVICECHANGE 的改進 C# 代碼:
//C# 響應消息聲明
public const int WM_DEVICECHANGE = 0x219;//U盤插入后,OS的底層會自動檢測到,然后向應用程序發送“硬件設備狀態改變“的消息
public const int DBT_DEVICEARRIVAL = 0x8000; //就是用來表示U盤可用的。一個設備或媒體已被插入一塊,現在可用。
public const int DBT_DEVICEQUERYREMOVE = 0x8001; //審批要求刪除一個設備或媒體作品。任何應用程序也不能否認這一要求,并取消刪除。
public const int DBT_DEVICEQUERYREMOVEFAILED = 0x8002; //請求刪除一個設備或媒體片已被取消。
public const int DBT_DEVICEREMOVECOMPLETE = 0x8004; //一個設備或媒體片已被刪除。
public const int DBT_DEVICEREMOVEPENDING = 0x8003; //一個設備或媒體一塊即將被刪除。不能否認的。
//C#
public static List<char> Volumes = new List<char>();protected override void DefWndProc(ref Message m){if (m.Msg == WM_DEVICECHANGE)//設備發生改變{int wp = m.WParam.ToInt32();//存儲設備插/拔/彈if (wp == DBT_DEVICEARRIVAL || wp == DBT_DEVICEQUERYREMOVE || wp == DBT_DEVICEREMOVECOMPLETE || wp == DBT_DEVICEREMOVEPENDING) {DEV_BROADCAST_HDR dbhdr = (DEV_BROADCAST_HDR)Marshal.PtrToStructure(m.LParam, typeof(DEV_BROADCAST_HDR));if (dbhdr.dbch_devicetype == 2)//驅動類型為DBT_DEVTYP_VOLUME 邏輯卷{DEV_BROADCAST_VOLUME dbv = (DEV_BROADCAST_VOLUME)Marshal.PtrToStructure(m.LParam, typeof(DEV_BROADCAST_VOLUME));if (dbv.dbcv_flags == 0)//驅動器標識硬盤和U盤{char[] volums = GetVolumes(dbv.dbcv_unitmask);int len = volums.Length;int start = (int)volums[0];int end = (int)volums[len - 1];for (int i = start; i <= end; i++){string disk =((char)i).ToString() + ":";if(wp == DBT_DEVICEARRIVAL) //存儲設備插入{//插入設備后的操作}else if (wp == DBT_DEVICEREMOVECOMPLETE) //存儲設備拔/彈出{//拔出設備后的操作}} }} else if (dbhdr.dbch_devicetype == 3)//DBT_DEVTYP_PORT=0x00000003 設備 (串行或并行) {//響應串口并口設備操作} } } base.DefWndProc(ref m); } /// <summary>根據驅動器掩碼返回驅動器號數組</summary>/// <param name="Mask">驅動器掩碼</param>/// <returns>返回驅動器號數組</returns>public static char[] GetVolumes(UInt32 Mask){for (int i = 0; i < 32; i++){//uint p = (uint)Math.Pow(2, i);//原參考代碼uint p = (uint)Math.Floor(Math.Log(Mask, 2));//算出驅動器掩碼 dbcv_unitmask 以2為底的指數,再取整加A的數值就是盤符。//if ((p | Mask) == p)//原參考代碼if (i == p){Volumes.Add((char)('A' + i));}}return Volumes.ToArray();}
移動 U盤的驅動類型是 Removable Media,
移動硬盤的驅動類型是 External hard disk media,
本地硬盤的驅動類型是 Fixed hard disk media。
在對以下 C# 代碼進行測試,插入后的多分區移動磁盤可以識別盤符,是對系統所有磁盤進行判斷,不能對插入、拔出進行響應。
//C#/// <summary>獲取U盤和可移動硬盤盤符名稱</summary>/// <returns></returns>public static List<string> GetDVN(){List<string> lstdisk = new List<string>();ManagementClass mgtcls = new ManagementClass("Win32_DiskDrive");var disks = mgtcls.GetInstances();foreach (ManagementObject mo in disks){if (mo.Properties["mediatype"].Value == null || mo.Properties["mediatype"].Value.ToString() == "External hard disk media" || mo.Properties["mediatype"].Value.ToString() == "Removable Media"){foreach (ManagementObject diskpartition in mo.GetRelated("win32_diskpartition")){foreach (ManagementObject disk in diskpartition.GetRelated("win32_logicaldisk")){lstdisk.Add(disk.Properties["name"].Value.ToString());}}// continue;}}return lstdisk;}
綜合上述代碼總結,可以對設備發生變化時進行的相應的程序操作。