在C#中根据HardwareID获取驱动程序信息的实现代码
近日在工作中需要根据设备的HardwareID来获取设备的驱动程序信息,比如驱动程序版本等。经过摸索,得到了两种不同的解决办法,两种办法各有千秋,写出来给大家分享。
1使用WMI中的Win32_PnPSignedDriver类
Win32_PnPSignedDriver的详细信息:http://msdn2.microsoft.com/en-us/library/aa394354.aspx
使用WMI(WindowsManagementInstrumentation)是最为方便的方法。可以根据下面的程序片段来得到我们所需要的DriverVersion。
privatestringGetDriverVersion(stringhardwareID) { stringqueryString="SELECTHardwareID,DriverVersionFROMWin32_PnPSignedDriver"; SelectQueryselectQuery=newSelectQuery(queryString); ManagementObjectSearchersearcher=newManagementObjectSearcher(selectQuery); foreach(ManagementObjectmoinsearcher.Get()) { objecttempID=mo["HardwareID"]; if(tempID!=null&&tempID.ToString().ToUpper()==hardwareID.Trim().ToUpper()) { returnmo["DriverVersion"].ToString(); } } return"UnknownVersion"; }
这样取得驱动程序的方式是非常简洁的,但是有一个非常严重的问题就是效率问题。平均说来,每执行一次查询,得到一个DriverVersion需要大约3秒的时间。对于我们的应用来说,这个时间是不可以接受的。也许你会说,为什么不用更多的限定符号来进一步减少查询的次数呢?
如果我们把连接字符串改成:
stringqueryString="SELECTHardwareID,DriverVersionFROMWin32_PnPSignedDriverWHEREHardwareID='somehardware'";
程序的效率并没有明显的改进。而且还发现一个问题,如果我们somehardware里面含有一个'\'(也就是HardwareID='some\\hardware'),那么一定会得到一个“InvalidQuery”异常。但是在WMITOOLS里面查询又是正常的,希望达人出来指点一下。最后根据MSDN的描述,只有WindowsVista,WindowsXP和Windows2003支持这个类。由于我们的程序需要跑在2000下,因此这种方法是行不通的了。
2使用PInvoke
由于无法使用WMI,因此就想到了使用PInvoke的方式调用WindowsAPI。通过查询MSDN,知道可以使用SetupDixxxx这种函数来实现我们的功能。基本的思路如下:
(1)利用SetupDiGetClassDevs这个函数得到一个含有所有设备信息的类。
(2)利用SetupDiEnumDeviceInfo得到某个具体设备的信息,保存在一个名为SP_DEVINFO_DATA的结构中。
(3)利用SetupDiGetDeviceRegistryProperty得到设备的HardwareID,和输入的HardwareID比较
(4)如果两个HardwareID是一样的,那么就利用SetupDiBuildDriverInfoList得到这个设备的驱动程序信息列表
(5)利用SetupDiEnumDriverInfo遍历驱动程序信息列表,得到所有需要的信息,保存在一个名为SP_DRVINFO_DATA的结构中
(6)从SP_DRVINFO_DATA中就可以得到驱动程序的版本。是一个DWORDLONG类型的数,需要转换成x.x.x.x的结构
要值得注意的是上述函数都封装在setupapi.dll中,要使用这些函数,需要安装WindowsDDK。
在C#中,我们利用pInvoke的方式来调用WindowsAPI的时候,需要注意类型的对应和结构对齐。比如上面的SP_DEVINFO_DATA结构需要按照如下方式声明
[StructLayout(LayoutKind.Sequential,Pack=4,CharSet=CharSet.Auto)] publicstructSP_DEVINFO_DATA { publicintcbSize; publicGuidClassGuid; publicIntPtrDevInst; publicIntPtrReserved; }
要注意的是LayoutKind.Sequential,Pack=4和publicIntPtrReserved。如果不按照这样声明,无法调用成功。
SP_DRVINFO_DATA也可以按照一样的方式进行声明。
[StructLayout(LayoutKind.Sequential,Pack=4,CharSet=CharSet.Auto)] publicstructSP_DRVINFO_DATA { publicintcbSize; publicintDriverType; publicIntPtrReserved; [MarshalAs(UnmanagedType.ByValTStr,SizeConst=256)] publicstringDescription; [MarshalAs(UnmanagedType.ByValTStr,SizeConst=256)] publicstringMfgName; [MarshalAs(UnmanagedType.ByValTStr,SizeConst=256)] publicstringProviderName; publicFILETIMEDriverDate; publiculongDriverVersion; }
对于最后的从DWORDLONG转换成x.x.x.x的版本,可以按照下面的方式转换。DWORDLONG是8字节的无符号整数,x.x.x.x中的x是从0到65536的无符号整数,占2个字节。因此可以直接把8字节的整数分成4个2字节的整数,最后合起来就是版本号了。假设版本version=1407379348914176,将version转换成2进制数为:
101000000000000000100001010001010000000000000000000
-------------------------------------------------------------------
5126000
因此,可以得到版本是5.1.2600.0。
可以用下面这个示例函数来得到版本信息
//version=1407379348914176,转换后的版本为5.1.2600.0 privatestringGetVersionFromLong(ulongversion) { ulongbaseNumber=0xFFFF; StringBuildersb=newStringBuilder(); ulongtemp=0L; for(intoffset=48;offset>=0;offset-=16) { temp=(version>>offset)&baseNumber; sb.Append(temp.ToString()+"."); } returnsb.ToString(); }
通过调用API这种方式,速度得到了很大的提高,1秒之内就可以完成一次查询。而且适合于Win2000,WinXP,Win2003和Vista。