程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 更多編程語言 >> 匯編語言 >> 實戰DeviceIoControl之五:列舉已安裝的存儲設備

實戰DeviceIoControl之五:列舉已安裝的存儲設備

編輯:匯編語言

Q 前幾次我們討論的都是設備名比較清楚的情況,有了設備名(路徑),就可以直接調用CreateFile打開設備,進行它所支持的I/O操作了。如果事先並不能確切知道設備名,如何去訪問設備呢?

A 訪問設備必須用設備句柄,而得到設備句柄必須知道設備路徑,這個套路以你我之力是改變不了的。每 個設備都有它所屬類型的GUID,我們順著這個GUID就能獲得設備路徑。 GUID是同類或同種設備的全球唯一識別碼,它是一個128 bit(16字節)的整形數,真實面目為

typedef struct _GUID
{
 unsigned long  Data1;
 unsigned short Data2;
 unsigned short Data3;
 unsigned char  Data4[8];
} GUID, *PGUID;

例如, Disk類的GUID為“53f56307-b6bf-11d0-94f2-00a0c91efb8b”,在我們的程序裡可以定義為

const GUID DiskClassGuid = {0x53f56307L, 0xb6bf, 0x11d0, {0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb,0x8b)};

或者用一個宏來定義

DEFINE_GUID(DiskClassGuid, 0x53f56307L, 0xb6bf, 0x11d0, 0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb,0x8b);

通過GUID 找出設備路徑,需要用到一組設備管理的API函數SetupDiGetClassDevs, SetupDiEnumDeviceInterfaces,SetupDiGetInterfaceDeviceDetail,SetupDiDestroyDeviceInfoList,以 及結構SP_DEVICE_INTERFACE_DATA, SP_DEVICE_INTERFACE_DETAIL_DATA。

有關信息請查閱MSDN ,這裡就不詳細介紹了。 實現GUID到設備路徑的代碼如下:

// 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(lpGuid,  // 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;
}
調用 GetDevicePath函數時要注意,pszDevicePath是個指向字符串指針的指針,例如可以這樣
int i;
char* szDevicePath[MAX_DEVICE]; // 設備路徑
// 分配需要的空間
for(i=0; i<MAX_DEVICE; i++) szDevicePath[i] = new char[256]; // 取設備路徑
nDevice = ::GetDevicePath((LPGUID)&DiskClassGuid, szDevicePath);
// 逐一獲取設備信息
for(i=0; i<nDevice; i++)
{
 // 打開設備
 hDevice = ::OpenDevice (szDevicePath[i]);
 if(hDevice != INVALID_HANDLE_VALUE)
 {
  ... ... // I/O操作
 ::CloseHandle(hDevice);
 }
}
// 釋放空間
for (i=0;i<MAX_DEVICE;i++) delete []szDevicePath[i];

本例的Project中除了要包含winioctl.h外,還要包含initguid.h,setupapi.h,以及連接 setupapi.lib。

Q 得到設備路徑後,就可以到下一步,用CreateFile打開設備,然後用 DeviceIoControl 進行讀寫了吧?

A 是的。盡管該設備路徑與以前我們接觸的那些不太一樣。本是“\\.\PhysicalDrive0”,現在鳥槍換炮,

變成了類似這樣的一副尊容:

“\\?\ide#diskmaxtor_2f040j0__________________________vam51jj0#3146563447534558202020202020202020202020#{53f56307-b6bf-11d0-94f2-00a0c91efb8b}”。

其實這個設備名在注冊表 的某處可以找到,例如在Win2000中這個名字位於

HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\Disk\Enum\0,

只不過“#”換成了 “\”。分析一下這樣的設備路徑,你會發現很有趣的東西,它們是由接口類型、產品型號、 固件版本、序列號、計算機名、GUID等信息組合而成的。當然,它是沒有規范的,不能指望從這裡面得 到你希望知道的東西。

用CreateFile打開設備後,對於存儲設備, IOCTL_DISK_GET_DRIVE_GEOMETRY,IOCTL_STORAGE_GET_MEDIA_TYPES_EX等I/O控制碼照常使用。

今天我們討論一個新的控制碼:IOCTL_STORAGE_QUERY_PROPERTY,獲取設備屬性信息,希望得到系統中 所安裝的各種固定的和可移動的硬盤、優盤和CD/DVD-ROM/R/W的接口類型、序列號、產品ID等信息。

// IOCTL控制碼

#define IOCTL_STORAGE_QUERY_PROPERTY  CTL_CODE (IOCTL_STORAGE_BASE, 0x0500,
METHOD_BUFFERED, FILE_ANY_ACCESS) // 存儲設備的總線類型
typedef enum _STORAGE_BUS_TYPE {
   BusTypeUnknown = 0x00,
   BusTypeScsi,
   BusTypeAtapi,
   BusTypeAta,
   BusType1394,
   BusTypeSsa,
   BusTypeFibre,
   BusTypeUsb,
   BusTypeRAID,
   BusTypeMaxReserved = 0x7F
} STORAGE_BUS_TYPE, *PSTORAGE_BUS_TYPE;
// 查詢存儲設備 屬性的類型
typedef enum _STORAGE_QUERY_TYPE {
   PropertyStandardQuery = 0,       // 讀取描述
   PropertyExistsQuery,         // 測試是否支持
   PropertyMaskQuery,          // 讀取指定的描述
   PropertyQueryMaxDefined  // 驗證數據
} STORAGE_QUERY_TYPE, *PSTORAGE_QUERY_TYPE;
// 查詢存儲設備還是適配 器屬性
typedef enum _STORAGE_PROPERTY_ID {
   StorageDeviceProperty = 0,  // 查詢設備屬性
   StorageAdapterProperty  // 查詢適配器屬性
} STORAGE_PROPERTY_ID, *PSTORAGE_PROPERTY_ID;
// 查詢屬性輸入的數據結構
typedef struct _STORAGE_PROPERTY_QUERY {
STORAGE_PROPERTY_ID PropertyId; // 設備/適配器
STORAGE_QUERY_TYPE QueryType; // 查詢類型 
   UCHAR AdditionalParameters[1]; // 額外的數據(僅定義了象征性的1個字節)
} STORAGE_PROPERTY_QUERY, *PSTORAGE_PROPERTY_QUERY;
// 查詢屬性輸出的數據結構
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;
// 取設備屬性信息
// hDevice -- 設備句柄
// 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;
}

Q 我用這個方法從IOCTL_STORAGE_QUERY_PROPERTY返回的數據中,沒有得到CDROM和USB接口的外置硬 盤的序列號、產品ID等信息。但從設備路徑上看,明明是有這些信息的,為什麼它沒有填充到 STORAGE_DEVICE_DESCRIPTOR中呢?再就是為什麼硬盤序列號本是“D22P7KHE”,為什麼它填 充的是“3146563447534558202020202020202020202020”這種形式呢?

A 對這兩個問 題我也是心存疑惑,但又不敢妄加猜測,正琢磨著向微軟請教呢。

P.bhw98

{
PADDING-RIGHT: 0px;
PADDING-LEFT: 0px;
FONT-SIZE: 9pt;
PADDING-BOTTOM: 0px;
MARGIN: 10px 0px 5px;
LINE-HEIGHT: normal;
PADDING-TOP: 0px;
FONT -FAMILY: Verdana, Arial
}
PRE.bhw98
{
FONT-SIZE: 9pt;
PADDING- RIGHT: 5px;
PADDING-LEFT: 5px;
PADDING-BOTTOM: 5px;
MARGIN: 5px 0px;
LINE-HEIGHT: normal;
PADDING-TOP: 5px;
BACKGROUND-COLOR: #f0f0f0
}
PRE.diag
{
FONT-SIZE: 9pt;
PADDING-RIGHT: 5px;
PADDING-LEFT: 5px;
PADDING-BOTTOM: 5px;
MARGIN: 5px 0px;
LINE-HEIGHT: normal;
PADDING-TOP: 5px;
}
CODE.bhw98
{
FONT-SIZE: 9pt;
COLOR: #000000
}
TABLE.bhw98
{
BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid;
FONT-SIZE: 9pt;
MARGIN: 3px 0px 10px;
BORDER-LEFT: #808080 1px solid;
LINE-HEIGHT: normal;
BORDER-BOTTOM: #808080 1px solid;
FONT-FAMILY: Verdana, Arial
}
TD.bhw98
{
BORDER-RIGHT: darkgray 1px solid;
PADDING-RIGHT: 10px;
BORDER-TOP: darkgray 1px solid;
PADDING-LEFT: 5px;
FONT-SIZE: 9pt;
PADDING-BOTTOM: 0px;
MARGIN: 0px;
BORDER-LEFT: darkgray 1px solid;
LINE-HEIGHT: normal;
PADDING-TOP: 3px;
BORDER-BOTTOM: darkgray 1px solid;
FONT-FAMILY: Verdana, Arial;
BACKGROUND-COLOR: #f0f0f0
}
STRONG.bhw98
{
FONT-WEIGHT: bolder;
FONT-SIZE: 20pt;
COLOR: #228b22;
FONT-STYLE: italic;
FONT-FAMILY: Verdana, Arial
}
LI.bhw98
{
FONT-SIZE: 9pt;
MARGIN: 3px 0px 0px 3px;
LINE-HEIGHT: normal;
FONT -FAMILY: Verdana, Arial
}
H1.bhw98
{
MARGIN-TOP: 25px;
FONT- WEIGHT: bolder;
FONT-SIZE: 12pt; MARGIN-BOTTOM: 5px;
LINE-HEIGHT: normal;
FONT-FAMILY: Verdana, Arial
}
H2.bhw98
{
MARGIN-TOP: 20px;
FONT -WEIGHT: bolder;
FONT-SIZE: 10.5pt;
MARGIN-BOTTOM: 5px;
LINE-HEIGHT: normal;
FONT-FAMILY: Verdana, Arial
}
H3.bhw98
{
MARGIN-TOP: 15px;
FONT-WEIGHT: bolder;
FONT-SIZE: 9pt;
MARGIN-BOTTOM: 5px;
LINE- HEIGHT: normal;
FONT-FAMILY: Verdana, Arial
}
SPAN.key
{
COLOR: #0000ff
}
SPAN.num
{
COLOR: #800000
}
SPAN.str
{
COLOR: #8b008b
}
SPAN.rem
{
COLOR: #008000
}

Q 在 NT/2000/XP 中,如何讀取 CMOS 數據?

Q 在 NT/2000/XP 中,如何控制 speaker 發聲?

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved