一、目的:
当計算機有一個或者多個
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
這樣一個唯一的標識,我們就可以以它為橋梁,找到相關的所有信息了。
參考: