前幾天寫了兩篇關于Pnp管理器的博文,在繼續寫相關博文前我想整理一下Pnp管理器在使用者态導出的功能,也就是我們熟悉的windows裝置管理器。為什麼說windows裝置管理器是Pnp管理器導出在使用者态的功能?因為當系統中加入裝置時,Pnp管理器會在系統中搜尋已有驅動能否比對該裝置(通過Inf檔案的硬體清單和相容清單)。如果比對到一個驅動,那就執行服務端安裝;如果很不幸,沒有比對到合适的驅動,就會執行用戶端安裝。用戶端安裝就是xp下的新硬體向導。這些動作也可以手動觸發,本文整理實作這些動作的程式。實作這些功能依賴于MS提供的SetupDI系列API,這類接口有百來個,一一列舉不大可能,隻能歸納出一些常用用法。
1.先來看下實作裝置管理器的"掃描檢測硬體改動"的功能
#include <windows.h>
#include <cfgmgr32.h>
#include <setupapi.h>
#pragma comment(lib,"cfgmgr32.lib")
int main(int argc, char* argv[])
{
char LocalComputer[MAX_PATH];
DWORD Size = MAX_PATH - 1;
GetComputerName(LocalComputer + 2, &Size);
LocalComputer[0] = '\\';
LocalComputer[1] = '\\';
CONFIGRET cr;
HMACHINE m_hMachine;
cr = CM_Connect_Machine(LocalComputer, &m_hMachine);
if (cr != CR_SUCCESS)
return -1;
DEVNODE dnRoot;
CONFIGRET status;
status = CM_Locate_DevNode_Ex(&dnRoot, NULL, 0, m_hMachine);
if(status != CR_SUCCESS)
return -1;
status = CM_Reenumerate_DevNode(dnRoot,
CM_REENUMERATE_RETRY_INSTALLATION|CM_REENUMERATE_NORMAL);
if(status != CR_SUCCESS)
return -1;
CM_Disconnect_Machine(m_hMachine);
return 0;
}
這段代碼用ddk自帶的編譯環境編譯,要不然會提示找不到CM_Connect_Machine等函數。編譯完成後以管理者運作。為了能在裝置管理器裡看到明顯的現象,我編譯ddk例子中自帶的藍牙裝置驅動,然後把它加入到裝置管理器中(具體操作參考ddk/src/bthecho下的Readme),安裝後把裝置的驅動解除安裝掉,然後運作程式,就可以看到裝置管理器重新整理動作

2.以USB類為例,調用SetupDixxx函數,枚舉屬于USB類裝置下所有裝置。
DEFINE_GUID(GUID_DEVINTERFACE_USB_DEVICE,
0x36fc9e60,0xc465,0x11cf,0x80,0x56,0x44,0x45,0x53,0x54,0x00,0x00);
int main(int argc, char* argv[])
{
HDEVINFO hDevInfo;
SP_DEVINFO_DATA DeviceInfoData;
DWORD idx;
//通過裝置類guid 獲得裝置接口類句柄 系統以Device Information Sets的形式管理一類裝置
//具體資訊參考msdn:https://msdn.microsoft.com/en-us/library/ff541247
hDevInfo = SetupDiGetClassDevs(
(LPGUID)&GUID_DEVINTERFACE_USB_DEVICE,
0,0,
DIGCF_PRESENT);
if(hDevInfo == INVALID_HANDLE_VALUE)
return -1;
//用裝置類句柄枚舉裝置
DeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
for(idx=0;
SetupDiEnumDeviceInfo(hDevInfo, idx, &DeviceInfoData);
idx++)
{
LPTSTR buffer = NULL;
DWORD buffersize = 0;
ULONG len;
//獲得裝置在系統資料庫中的資訊,分兩步走,第一次獲得承載裝置資訊的緩存區大小 第二次獲得裝置描述資訊
DWORD regType;
while (!SetupDiGetDeviceRegistryProperty(
hDevInfo,
&DeviceInfoData,
SPDRP_HARDWAREID,
®Type,
(PBYTE)buffer,
buffersize,
&buffersize))
{
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
{
// Change the buffer size.
if (buffer) LocalFree(buffer);
buffer = (LPTSTR)LocalAlloc(LPTR,buffersize);
}
else
{
// Insert error handling here.
break;
}
}
printf("%s\n",buffer);
//獲得裝置執行個體名
while (!SetupDiGetDeviceRegistryProperty(
hDevInfo,
&DeviceInfoData,
SPDRP_DEVICEDESC,
®Type,
(PBYTE)buffer,
buffersize,
&buffersize))
{
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
{
// Change the buffer size.
if (buffer) LocalFree(buffer);
buffer = (LPTSTR)LocalAlloc(LPTR,buffersize);
}
else
{
// Insert error handling here.
break;
}
}
printf("%s\n",buffer);
}
}
這段代碼不是很難,最麻煩的是查找裝置類的classguid,最簡單的辦法是通過裝置管理器查找。對于這段程式通過裝置管理器-通用串行總線控制器-USB Root Hub-屬性-詳細資訊-裝置類GUID的方式來擷取。下面截圖是程式輸出:
cr = CM_Get_DevNode_Status(&ulStatus,
&ulProblemNumber,
DeviceInfoData.DevInst,
0);
if(CR_SUCCESS != cr)
continue;
// DN_DISABLEABLE or DN_REMOVABLE
if((DN_DISABLEABLE&ulStatus) != 0)
printf("HAS - DN_DISABLEABLE()[%x]/n", DN_DISABLEABLE & ulStatus);
else
continue;
if((DN_REMOVABLE&ulStatus) != 0)
printf("HAS - DN_REMOVABLE()[%x]/n", DN_REMOVABLE & ulStatus);
else
continue;
len = MAX_PATH;
//移除裝置
cr = CM_Request_Device_Eject(
DeviceInfoData.DevInst,
&pnpvietotype,
vetoname,
len,
0
);
if(CR_SUCCESS == cr)
printf("OK - CM_Request_Device_Eject()[%d]/n", cr);
else
printf("ERROR - CM_Request_Device_Eject()[%d]/n", cr);