[转]如何获得U盘的详细信息
原文地址:http://blog.csdn.net/jakeyjia/article/details/1424111
一、目的:
当計算機有一個或者多個U盤插入時,系統識別后,該應用可以自動獲取U盤的相關信息,如生産廠商信息,產品名,版本號,VID&PID,產品序列號等等一些U盤的相關信息。
二、實現思路:
1、通過獲得U盤盤符獲得設備句柄
首先必須檢測當前系統連接的U盤設備,在這一方面最便捷的方法是掃描當前系統的各個驅動器,判斷當前系統所連設備屬性是否為DRIVE_REMOVABLE,如果是,表明設備是可移動設備,儅我們排除軟盤后,剩下的就是U盤設備了。
//得到U盘盘符; LPTSTR lpDrives = new TCHAR[MAX_PATH]; DWORD dwLen = ::GetLogicalDriveStrings(MAX_PATH, lpDrives); CString sDrives[26] = {""}; for(DWORD nIndex = 0; nIndex < dwLen / 4; nIndex++) { if(::GetDriveType(lpDrives + nIndex * 4) == DRIVE_REMOVABLE) sDrives[nIndex] += (CString)(lpDrives + nIndex * 4); if(sDrives[nIndex]!=”A://”&& sDrives[nIndex]!=”B://”) { //…具體操作代碼; } } delete lpDrives;
獲得上面這段代碼,獲得了具體的形如“H:/”的設備盤符,並且將這一組盤符存儲在sDevice[26]這樣一個字符串數組中。然後通過具體操作獲得的盤符經過一定的處理將其轉化為形如”////?//H:”的設備路徑,然後再通過CreateFile獲得這個設備的句柄:
HANDLE hDeviceHandle = CreateFile(PATH, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
注意,這裡的第三項參數必須是FILE_SHARE_READ | FILE_SHARE_WRITE,因爲U盤設備是共享設備,第五個參數必須是OPEN_EXISTING,打開現有設備。hDeviceHandle就是我們所需要的U盤句柄了。
獲得這個句柄之後我們將用到一個比較關鍵的函數DeviceIoControl,儅我們對系統的各種設備進行操作時,這個函數是經常要用到的。這個函數中控制碼的種類很多,這裡我們主要討論利用IOCTL_STORAGE_QUERY_PROPERTY這個控制碼,获取设备属性信息,得到系统中所安装的各种固定的和可移动的硬盘、优盘和CD/DVD-ROM/R/W的接口类型、序列号、产品ID等信息。
這裡給出這樣一個函數:
// 取设备属性信息 //(in) hDevice -- 设备句柄 //(out) pDevDesc -- 输出的设备描述和属性信息缓冲区指针(包含连接在一起的两部分) BOOL GetDriveProperty(HANDLE hDevice, PSTORAGE_DEVICE_DESCRIPTOR pDevDesc) { STORAGE_PROPERTY_QUERY Query; // 查询输入参数 DWORD dwOutBytes; // IOCTL输出数据长度 BOOL bResult; // IOCTL返回值 // 指定查询方式 Query.PropertyId = StorageDeviceProperty; Query.QueryType = PropertyStandardQuery; // 用IOCTL_STORAGE_QUERY_PROPERTY取设备属性信息 bResult = ::DeviceIoControl(hDevice, // 设备句柄 IOCTL_STORAGE_QUERY_PROPERTY, // 取设备属性信息 &Query, sizeof(STORAGE_PROPERTY_QUERY), // 输入数据缓冲区 pDevDesc, pDevDesc->Size, // 输出数据缓冲区 &dwOutBytes, // 输出数据长度 (LPOVERLAPPED)NULL); // 用同步I/O return bResult; } 注意,使用這個函數需要引入winioctl.h。在這個頭文件中聲明了STORAGE_DEVICE_DESCRIPTOR這樣一個結構体: // 查询属性输出的数据结构 typedef struct _STORAGE_DEVICE_DESCRIPTOR { ULONG Version; // 版本 ULONG Size; // 结构大小 UCHAR DeviceType; // 设备类型 UCHAR DeviceTypeModifier; // SCSI-2额外的设备类型 BOOLEAN RemovableMedia; // 是否可移动 BOOLEAN CommandQueueing; // 是否支持命令队列 ULONG VendorIdOffset; // 厂家设定值的偏移 ULONG ProductIdOffset; // 产品ID的偏移 ULONG ProductRevisionOffset; // 产品版本的偏移 ULONG SerialNumberOffset; // 序列号的偏移 STORAGE_BUS_TYPE BusType; // 总线类型 ULONG RawPropertiesLength; // 额外的属性数据长度 UCHAR RawDeviceProperties[1]; // 额外的属性数据(仅定义了象征性的1个字节) } STORAGE_DEVICE_DESCRIPTOR, *PSTORAGE_DEVICE_DESCRIPTOR;
調用以上列出的GetDriveProperty時要注意聲明一個STORAGE_DEVICE_DESCRIPTOR變量,並且初始化,可以這樣初始化:
PSTORAGE_DEVICE_DESCRIPTOR DeviceDesc; DeviceDesc=(PSTORAGE_DEVICE_DESCRIPTOR)new BYTE[sizeof(STORAGE_DEVICE_DESCRIPTOR) + 512 - 1]; DeviceDesc->Size = sizeof(STORAGE_DEVICE_DESCRIPTOR) + 512 - 1;
通過這樣我們獲得一個設備對應的屬性數據結構,這樣,我們就可以獲取相關的信息了。
(其中我在Vista+VS2005+WinSDK下只能獲得ProductIdOffset,ProductIdOffset,ProductRevisionOffset等信息,而SerialNumberOffset等信息返回字符串為空串,而且這裡也無法知道U盤的VID&PID,所以以上這兩個無法獲得的信息我們可以通過其它方式獲得,具體見方法2。)
後來我又在win200+VC6+DDK的環境下進行同樣的實驗,發現利用這種方法也同樣是無法獲得SerialNumberOffset,利用這種方法只能獲得硬盤、光驅等硬件設備的SerialNumberOffset,而對於usb設備,包括通過usb連接到電腦上的移動硬盤,也無法通過此种方法獲得。
2、另一種方法是通過設備的GUID號直接找到U盤設備。
實現過程中我們將用到一些WinSDK的函數,SetupDiGetClassDevs,SetupDiEnumDeviceInterfaces,SetupDiGetInterfaceDeviceDetail。具體用法,請參照相關的文檔,這裡不作詳細介紹
另外,MicroSoft定義了一些設備類的GUID。
如:
DEFINE_GUID(DiskClassGuid, 0x53f56307L, 0xb6bf, 0x11d0, 0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b); DEFINE_GUID(CdRomClassGuid, 0x53f56308L, 0xb6bf, 0x11d0, 0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b); DEFINE_GUID(TapeClassGuid, 0x53f5630bL, 0xb6bf, 0x11d0, 0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b); DEFINE_GUID(FloppyClassGuid, 0x53f56311L, 0xb6bf, 0x11d0, 0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b); 我們同樣可以找到對於USB設備的GUID號 DEFINE_GUID(UsbClassGuid, 0xa5dcbf10L, 0x6530, 0x11d2, 0x90, 0x1f, 0x00, 0xc0, 0x4f, 0xb9, 0x51, 0xed) 這樣我們可以通過以下這樣一個函數來實現: // SetupDiGetInterfaceDeviceDetail所需要的输出长度,定义足够大 #define INTERFACE_DETAIL_SIZE (1024) // 根据GUID获得设备路径 // lpGuid: GUID指针 // pszDevicePath: 设备路径指针的指针 // 返回: 成功得到的设备路径个数,可能不止1个 int GetDevicePath(LPGUID lpGuid, LPTSTR* pszDevicePath) { HDEVINFO hDevInfoSet; //设备信息集句柄; SP_DEVICE_INTERFACE_DATA ifdata; PSP_DEVICE_INTERFACE_DETAIL_DATA pDetail; int nCount; BOOL bResult; // 取得一个该GUID相关的设备信息集句柄 hDevInfoSet = ::SetupDiGetClassDevs(UsbClassGuid, // class GUID NULL, // 无关键字 NULL, // 不指定父窗口句柄 DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); // 目前存在的设备 // 失败... if (hDevInfoSet == INVALID_HANDLE_VALUE) { return 0; } // 申请设备接口数据空间 pDetail = (PSP_DEVICE_INTERFACE_DETAIL_DATA)::GlobalAlloc(LMEM_ZEROINIT, INTERFACE_DETAIL_SIZE); pDetail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); nCount = 0; bResult = TRUE; // 设备序号=0,1,2... 逐一测试设备接口,到失败为止 while (bResult) { ifdata.cbSize = sizeof(ifdata); // 枚举符合该GUID的设备接口 bResult = ::SetupDiEnumDeviceInterfaces( hDevInfoSet, // 设备信息集句柄 NULL, // 不需额外的设备描述 lpGuid, // GUID (ULONG)nCount, // 设备信息集里的设备序号 &ifdata); // 设备接口信息 if (bResult) { // 取得该设备接口的细节(设备路径) bResult = SetupDiGetInterfaceDeviceDetail( hDevInfoSet, // 设备信息集句柄 &ifdata, // 设备接口信息 pDetail, // 设备接口细节(设备路径) INTERFACE_DETAIL_SIZE, // 输出缓冲区大小 NULL, // 不需计算输出缓冲区大小(直接用设定值) NULL); // 不需额外的设备描述 if (bResult) { // 复制设备路径到输出缓冲区 ::strcpy(pszDevicePath[nCount], pDetail->DevicePath); // 调整计数值 nCount++; } } } // 释放设备接口数据空间 ::GlobalFree(pDetail); // 关闭设备信息集句柄 ::SetupDiDestroyDeviceInfoList(hDevInfoSet); return nCount; }
以上這個函數中除了要包含winioctl.h外,還要包含initguid.h,setupapi.h,以及連接setupapi.lib。
不一樣的是,以上這樣一個函數讓我們獲得了一個形如"//?/usb#vid_0ef5&pid_2202#2004063008241001#{a5dcbf10-6530-11d2-901f-00c04fb951ed}"的設備路徑,從以上這一串路徑我們不難找到,已經有VID&PID,產品序列號等信息。如果只是需要這兩個信息的話,我們可以通過提取這段字符串的内容,直接就達到獲得VID&PID和產品序列號的目的。
當然,這樣一個路徑,同樣也可以由方法1中的CreateFile打開獲得句柄。
之後的做法與方法1中的一樣,通過DeviceIoControl函數獲得相關的信息。
3、通過HidD_GetAttributes获得其基本属性信息
HID設備是微軟定義的標準人機接口範圍。不用查找設備具體的GUID,直接使用API HidD_GetHidGuid(&guidHID)即可得到GUID。有了GUID通過方法2獲得其設備句柄,通过HidD_GetAttributes获得其基本属性信息。當然,這樣獲得的基本屬性可能不全面,可以結合方法1、2來綜合獲取想要得到的信息。具體實現方法這裡不做詳細介紹。這個方法我沒有試過,但應該可以行的通。
通過以上三种方法基本上可以獲得我們所需的所有關於U盤的所有信息了。
4、通過所獲得的U盤盤符找到與其對應的相關信息
從以上的這幾種方法來看,由於每個方法都是通過對於系統的設備進行掃描然後得出U盤盤符、PID/VID,序列號…等。而且由於這些信息是通過多種方法獲得的,儅計算機連接多個usb設備時,那麽以上的這些信息就無法正確的對應起來,尤其是U盤盤符與其唯一標識Serial Number無法對應,這樣很不利于我們正確的選擇目標usb設備。下面我們來看如何通過U盤盤符找到對應的設備序列號。
第一步,我們仍可以按照方法1种所提到的通過遍歷盤符屬性來找到所有連接到機器上的U盤盤符,然後CreateFile獲得設備句柄。
第二步,在應用DeviceIoControl函數的時候,我們需要引入一個新的查詢方式IOCTL_STORAGE_GET_DEVICE_NUMBER,如
STORAGE_DEVICE_NUMBER sdn; DWORD dwBytesReturned = 0; // 用IOCTL_STORAGE_GET_DEVICE_NUMBER取设备號 bResult = ::DeviceIoControl(hDevice, // 设备句柄 IOCTL_STORAGE_GET_DEVICE_NUMBER, // 取设备属性信息 NULL, 0, // 输入数据缓冲区 sdn, sdn ->Size, // 输出数据缓冲区 & dwBytesReturned, // 输出数据地址 (LPOVERLAPPED)NULL); // 用同步I/O DeviceNumber = pDevDesc.DeviceNumber;
這樣我們可以獲得該盤符對應的設備號,同樣我們可以通過QueryDosDevice來找到該盤符對應的dos設備名:
QueryDosDevice(szDevicePath, //設備路徑,如:“F:”
szDosDeviceName, //查詢返回的dos設備名
MAX_PATH);
到現在爲止,我們通過盤符獲得了兩個信息:DeviceNumber,szDosDeviceName。
第三步用下面這樣一個函數來找到相應的設備序列號:
DEVINST GetDrivesDevInstByDiskNumber (long DiskNumber, char *szDosDeviceName) { …//函數的前半部分與方法2种提到的方法相同目的是爲了獲得一個DivicePath; …//注意這裡用到的GUID應該是DiskClassGuid; HANDLE hDrive = CreateFile(pspdidd->DevicePath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, NULL, NULL); if ( hDrive != INVALID_HANDLE_VALUE ) { STORAGE_DEVICE_NUMBER sdn; DWORD dwBytesReturned = 0; //通過這樣一個句柄,用同樣的方法也可以得到一個設備號。 res = DeviceIoControl(hDrive, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0, &sdn, sizeof(sdn), &dwBytesReturned, NULL); if ( res ) { //這句是關鍵,通過這兩种方法獲得的.DeviceNumber,進行比較,以DeviceNumber作//爲橋梁,找到了對應的設備 if ( DiskNumber == (long)sdn.DeviceNumber ) { CloseHandle(hDrive); SetupDiDestroyDeviceInfoList(hDevInfo); return spdd.DevInst; //這裡是來返回一個DeviceInstance。 } } CloseHandle(hDrive); … } Return 0; }
調用以上這個函數,我們獲得了一個DEVINST,這樣我們就可以通過DDK中CM_Get_Device_ID來獲得設備ID:
char Buf[MAX_PATH];
CM_Get_Device_ID(DevInst,Buf,MAX_PATH,0);
其中的Buf中返回的就是DeviceInstanceID,這是一個形如“USBSTOR/DISK&VEN________&PROD_FREEDIK-LWFORMAT&REV_2.23/2004063008241001”的字符串,我們可以看到字符串的最後一串數字就是我們想要得到的SerialNumber。
通過這樣一個方法,我們可以通過所獲得的U盤盤符找到與其對應的相關信息。因爲我們一旦獲得了SerialNumber這樣一個唯一的標識,我們就可以以它為橋梁,找到相關的所有信息了。
參考: