原文地址:
http://www.cnblogs.com/SunYu/archive/2010/04/29/1723977.htmlcodeproject:
http://www.codeproject.com/KB/system/HwDetect.aspx
简介
现在对于IT的安全来说,热插拨设备是个很大的威胁。在这篇文章中,我将试着开发一个用户应用程序来检测本机系统上的设备改变。例如:插入一个USB设备、Ipod、USB无线网卡等等。这个程序同样也可以停用任何支持插拔的设备。在文章的后面,我会简述一下程序的工作原理和它的局限性。
怎么来检测硬件设备的改变?
事实上,Windows操作系统会对上层程序发送WM_DEVICECHANGE消息来通知设备的改变。我们所要作的仅仅是添加一个句柄来处理这个事件。
Collapse
BEGIN_MESSAGE_MAP(CHWDetectDlg, CDialog)
// ... other handlers
ON_MESSAGE(WM_DEVICECHANGE, OnMyDeviceChange)
END_MESSAGE_MAP()
LRESULT CHWDetectDlg::OnMyDeviceChange(WPARAM wParam, LPARAM lParam)
{
// for more information, see MSDN help of WM_DEVICECHANGE
// this part should not be very difficult to understand
if ( DBT_DEVICEARRIVAL == wParam || DBT_DEVICEREMOVECOMPLETE == wParam ) {
PDEV_BROADCAST_HDR pHdr = (PDEV_BROADCAST_HDR)lParam;
switch( pHdr->dbch_devicetype ) {
case DBT_DEVTYP_DEVICEINTERFACE:
PDEV_BROADCAST_DEVICEINTERFACE pDevInf = (PDEV_BROADCAST_DEVICEINTERFACE)pHdr;
// do something...
break;
case DBT_DEVTYP_HANDLE:
PDEV_BROADCAST_HANDLE pDevHnd = (PDEV_BROADCAST_HANDLE)pHdr;
// do something...
break;
case DBT_DEVTYP_OEM:
PDEV_BROADCAST_OEM pDevOem = (PDEV_BROADCAST_OEM)pHdr;
// do something...
break;
case DBT_DEVTYP_PORT:
PDEV_BROADCAST_PORT pDevPort = (PDEV_BROADCAST_PORT)pHdr;
// do something...
break;
case DBT_DEVTYP_VOLUME:
PDEV_BROADCAST_VOLUME pDevVolume = (PDEV_BROADCAST_VOLUME)pHdr;
// do something...
break;
}
}
return 0;
}
然而默认情况下,Windows操作系统发送WM_DEVICECHANGE有些限制:
1 只有顶层窗体的程序才能收到这个消息
2 仅仅串口、磁盘发生改变,才对每个程序广播这个消息
的确不错,至少你可以知道移动U盘、移动硬盘、光盘被安装或弹出了,通过DEV_BROADCAST_VOLUME.dbcv_unitmask你也可以获得其对应的盘符。但实际上,你不知道底层处理的是哪个物理设备实际上被安装到了系统中。
API:RegisterDeviceNotification()
所以,你不得不调用RegisterDeviceNotification()API来注册其他类型的设备改变,或是你的程序仅仅是一个服务程序、没有顶层窗体的程序。例如:如下的例子是用来注册一个设备类型的接口的:
Collapse
1. DEV_BROADCAST_DEVICEINTERFACE NotificationFilter;
2. ZeroMemory( &NotificationFilter, sizeof(NotificationFilter) );
3. NotificationFilter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
4. NotificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
5. // assume we want to be notified with USBSTOR
6. // to get notified with all interface on XP or above
7. // ORed 3rd param with DEVICE_NOTIFY_ALL_INTERFACE_CLASSES and dbcc_classguid will be ignored
8. NotificationFilter.dbcc_classguid = GUID_DEVINTERFACE_USBSTOR;
9. HDEVNOTIFY hDevNotify = RegisterDeviceNotification(this->GetSafeHwnd(),
amp;NotificationFilter, DEVICE_NOTIFY_WINDOW_HANDLE);
10. if( !hDevNotify ) {
11. // error handling...
12. return FALSE;
13. }
请注意第8行,NotificationFilter.dbcc_classguid
关注的就是你关心的一类设备。
参考这个blog: Doron Holan's blog
一个支持即插即用的设备,有2个不同的GUID相关,一个设备接口GUID, 一个是设备类GUID
设备类GUID:定义了广泛意义上一类设备的GUID,如果你打开设备管理器[我的电脑右键—>设备管理器],默认的是按照“类型”排列的,每一个“类型”就是一个设备类,同时每一个设备类有一个唯一的ID就是设备类GUID。设备GUID定义了此类设备的图标、默认的安全设置、安装属性(例如用户不能手动安装这类设备,而必须通过PNP来遍历),以及其他的设置信息。设备类GUID没有定义对应的I/O接口(请参考术语表),而更像是设备的分组。我认为一个比较好的例子是端口类。串口COM和并口LPT 都是端口类的一部分,但其各有各的I/O接口,而且彼此互不兼容.一个设备仅仅属于一个设备类。我们可以通过设备驱动的INF文件的开头来查看该设备的设备类GUID。
设备接口GUID:定义了相互关联I/O接口的GUID,每一个接口GUID的具体实例都支持基本的I/O设置。设备接口GUID也是对应的驱动程序基于PNP状态来注册、启用、禁用设备。如果需要,一个设备甚至可以注册多个同样GUID的实例(假使每个都有相同的名字)[注:在实际的程序中,多次插拔USB口,确实会驱出相同的串口,例如port12,port12,port12…],尽管在现实世界中完全不需要这样。一个简单的I/O关联接口是键盘设备,每个键盘设备的接口GUID必须相同。
可以通过如下的注册表路径来查看当前设备类GUID, 设备接口GUID:
- \\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class
- \\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\DeviceClasses
常用设备的接口GUID如下:
设备接口名称
|
GUID
|
USB Raw Device/USB设备
|
{a5dcbf10-6530-11d2-901f-00c04fb951ed}
|
Disk Device/磁盘设备
|
{53f56307-b6bf-11d0-94f2-00a0c91efb8b}
|
Network Card/网卡
|
{ad498944-762f-11d0-8dcb-00c04fc3358c}
|
Human Interface Device (HID)/人机界面设备
|
{4d1e55b2-f16f-11cf-88cb-001111000030}
|
Palm/手持设备
|
{784126bf-4190-11d4-b5c2-00c04f687a67}
|
DEV_BROADCAST_DEVICEINTERFACE的解码
如下是修改处理捕获对应事件的函数:
Collapse
LRESULT CHWDetectDlg::OnMyDeviceChange(WPARAM wParam, LPARAM lParam)
{
....
....
if ( DBT_DEVICEARRIVAL == wParam || DBT_DEVICEREMOVECOMPLETE == wParam )
{
PDEV_BROADCAST_HDR pHdr = (PDEV_BROADCAST_HDR)lParam;
switch( pHdr->dbch_devicetype )
{
case DBT_DEVTYP_DEVICEINTERFACE:
PDEV_BROADCAST_DEVICEINTERFACE pDevInf = (PDEV_BROADCAST_DEVICEINTERFACE)pHdr;
UpdateDevice(pDevInf, wParam);
break;
....
....
}
从MSDN中,我们知道
Collapse
typedef struct _DEV_BROADCAST_DEVICEINTERFACE {
DWORD dbcc_size;
DWORD dbcc_devicetype;
DWORD dbcc_reserved;
GUID dbcc_classguid;
TCHAR dbcc_name[1];
} DEV_BROADCAST_DEVICEINTERFACE *PDEV_BROADCAST_DEVICEINTERFACE;
我们似乎可以通过dbcc_name知道那个设备安装到了当前系统。J,答案是不对,dbcc_name仅仅是操作系统内部使用来做为ID的,其实不易读的,例如下面的这个dbcc_name:
\\?\USB#Vid_04e8&Pid_503b#0002F9A9828E0F06#{a5dcbf10-6530-11d2-901f-00c04fb951ed}
- \\?\USB: USB 意思是这是一个USB设备类
- Vid_04e8&Pid_053b:
Vid
/Pid
是一个厂商ID和产品ID(但这是由设备类指定的,USB设备类使用VID/PID,不同的设备类使用不同的命名约定)
- 002F9A9828E0F06: 不清楚是怎么生成的,是唯一设备ID
- {a5dcbf10-6530-11d2-901f-00c04fb951ed}:设备接口类GUID
现在,我们来解出设备描述信息或是设备别名,有2种办法:
1 直接读注册表, \\HKLM\SYSTEM\CurrentControlSet\Enum\USB\Vid_04e8&Pid_503b\0002F9A9828E0F06
2 使用 SetupDiXxx 系列API
API:SetupDiXxx()
Windows定义了一组API,让用户通过编程的办法来获取对应的硬件设备信息。例如,我们可以通过dbcc_name来获得设备描述信息或是设备别名。下面是这个办法都具体步骤:
1 首先通过SetupDiGetClassDevs()来获得设备信息集 HDEVINFO,这个操作等同于是一个获取目录句柄的过程。
2 接着使用SetupDiEnumDeviceInfo()来遍历出这个设备信息集内的所有设备,这个操作等同于把目录列表的过程。对于每个遍历出的,我们可以获得SP_DEVINFO_DATA,这个等同于是文件句柄。
3 在上面的枚举过程中,使用SetupDiGetDeviceInstanceId()来读取每个设备的实例ID,这个操作等同于是读文件的属性,一个设备的实例ID类似这个:”USB\Vid_04e8&Pid_503b\0002F9A9828E0F06”,和dbcc_name非常像。
4 如果设备的实例ID等同于dbcc_name,则通过SetupDiGetDeviceRegistryProperty()来获取设备描述信息或是设备别名信息。
程序如下:
Collapse
void CHWDetectDlg::UpdateDevice(PDEV_BROADCAST_DEVICEINTERFACE pDevInf, WPARAM wParam)
{
// dbcc_name:
// \\?\USB#Vid_04e8&Pid_503b#0002F9A9828E0F06#{a5dcbf10-6530-11d2-901f-00c04fb951ed}
// convert to
// USB\Vid_04e8&Pid_503b\0002F9A9828E0F06
ASSERT(lstrlen(pDevInf->dbcc_name) > 4);
CString szDevId = pDevInf->dbcc_name+4;
int idx = szDevId.ReverseFind(_T('#'));
ASSERT( -1 != idx );
szDevId.Truncate(idx);
szDevId.Replace(_T('#'), _T('\\'));
szDevId.MakeUpper();
CString szClass;
idx = szDevId.Find(_T('\\'));
ASSERT(-1 != idx );
szClass = szDevId.Left(idx);
// if we are adding device, we only need present devices
// otherwise, we need all devices
DWORD dwFlag = DBT_DEVICEARRIVAL != wParam
? DIGCF_ALLCLASSES : (DIGCF_ALLCLASSES | DIGCF_PRESENT);
HDEVINFO hDevInfo = SetupDiGetClassDevs(NULL, szClass, NULL, dwFlag);
if( INVALID_HANDLE_VALUE == hDevInfo )
{
AfxMessageBox(CString("SetupDiGetClassDevs(): ")
+ _com_error(GetLastError()).ErrorMessage(), MB_ICONEXCLAMATION);
return;
}
SP_DEVINFO_DATA* pspDevInfoData =
(SP_DEVINFO_DATA*)HeapAlloc(GetProcessHeap(), 0, sizeof(SP_DEVINFO_DATA));
pspDevInfoData->cbSize = sizeof(SP_DEVINFO_DATA);
for(int i=0; SetupDiEnumDeviceInfo(hDevInfo,i,pspDevInfoData); i++)
{
DWORD DataT ;
DWORD nSize=0 ;
TCHAR buf[MAX_PATH];
if ( !SetupDiGetDeviceInstanceId(hDevInfo, pspDevInfoData, buf, sizeof(buf), &nSize) )
{
AfxMessageBox(CString("SetupDiGetDeviceInstanceId(): ")
+ _com_error(GetLastError()).ErrorMessage(), MB_ICONEXCLAMATION);
break;
}
if ( szDevId == buf )
{
// device found
if ( SetupDiGetDeviceRegistryProperty(hDevInfo, pspDevInfoData,
SPDRP_FRIENDLYNAME, &DataT, (PBYTE)buf, sizeof(buf), &nSize) ) {
// do nothing
} else if ( SetupDiGetDeviceRegistryProperty(hDevInfo, pspDevInfoData,
SPDRP_DEVICEDESC, &DataT, (PBYTE)buf, sizeof(buf), &nSize) ) {
// do nothing
} else {
lstrcpy(buf, _T("Unknown"));
}
// update UI
// .....
// .....
break;
}
}
if ( pspDevInfoData ) HeapFree(GetProcessHeap(), 0, pspDevInfoData);
SetupDiDestroyDeviceInfoList(hDevInfo);
}
禁用设备
假使你有一个正确的HDEVINFO和SP_DEVINFO_DATA(实际上,我们保持dbcc_name座位树节点的tag,当右键单击某一个节点的时候,可以通过调用SetupDiGetClassDevs和SetupDiEnumDeviceInfo来获得所需东西),按照如下的步骤即可禁用一个设备:
1 给SP_PROPCHANGE_PARAMS结构体赋上正确的值
2 把上面赋完值的SP_PROPCHANGE_PARAMS作为参数传入到SetupDiSetClassInstallParams()
3 调用SetupDiCallClassInstaller(),传递参数DIF_PROPEFRTYCHANGE
实际上,DIF也是按位做与运算后兼容的,你也可以去传递不同的DIF参数来调用SetupDiSetClassInstallParams()。 更多信息,请参考MSDN”Handling DIF Codes”
Collapse
SP_PROPCHANGE_PARAMS spPropChangeParams ;
spPropChangeParams.ClassInstallHeader.cbSize = sizeof(SP_CLASSINSTALL_HEADER);
spPropChangeParams.ClassInstallHeader.InstallFunction = DIF_PROPERTYCHANGE ;
spPropChangeParams.Scope = DICS_FLAG_GLOBAL ;
spPropChangeParams.HwProfile = 0; // current hardware profile
spPropChangeParams.StateChange = DICS_DISABLE
if( !SetupDiSetClassInstallParams(hDevInfo, &spDevInfoData,
// note we pass spPropChangeParams as SP_CLASSINSTALL_HEADER
// but set the size as sizeof(SP_PROPCHANGE_PARAMS)
(SP_CLASSINSTALL_HEADER*)&spPropChangeParams, sizeof(SP_PROPCHANGE_PARAMS)) )
{
// handle error
}
else if(!SetupDiCallClassInstaller(DIF_PROPERTYCHANGE, hDevInfo, &spDevInfoData))
{
// handle error
}
else
{
// ok, show disable success dialog
// note, after that, the OS will post DBT_DEVICEREMOVECOMPLETE for the disabled device
}
附录:
我使用这个程序,已经多次测试了USB的无线网卡的,插入、拔出测试。
局限性:
1 明显的,必须先运行该程序,才能检测硬件设备。例如:设备在操作系统启动前就已经连接,或者在这个程序运行前的连接都不会被检测。但这个问题,可以通过保存当前系统配置到远程计算机上,等启动完这个程序后再坚持不同的配置来解决
2 我们可以禁用设备,换而言之这也是我们所有能做到。我们不能访问设备底层控制。 我认为可以通过重新用基于内核的过滤驱动来实现,则可以解决这个问题。
原文地址:
http://blog.csdn.net/tongxuechen1982/archive/2008/05/26/2483123.aspx用C#做串口通讯很方便,因为dotfx2.0已经集成了Serial Port控件,此控件使用上比MSComm控件更简单,当然它也有一个小bug(RecievedBytesThreshold设置有时候不好使),但是这个问题很好解决,而做串口通讯最最基本的问题就是如何获得目标机器的要与之通讯的特定设备的特定com号,有许多软件在处理这个问题的时候都是要求用户手动去选择对应的串口号,这多少有些不人性化,这篇文章就是介绍如何自动获得目标机器对应特定串口设备的串口号,从而达到软件与目标设备的自动通讯。
方法基本上是我前一篇文章的一个应用,代码如下(注意自己添加引用的命名空间)
/**//**//**//// <summary>
/// 通过vid,pid获得串口设备号
/// </summary>
/// <param name="vid">vid</param>
/// <param name="pid">pid</param>
/// <returns>串口号</returns> public static string GetPortNameFormVidPid(string vid, string pid)
{
Guid myGUID = Guid.Empty;
string enumerator = "USB";
try
{
IntPtr hDevInfo = HardWareLib.SetupDiGetClassDevs(ref myGUID, enumerator, IntPtr.Zero, HardWareLib.DIGCF_ALLCLASSES | HardWareLib.DIGCF_PRESENT);
if (hDevInfo.ToInt32() == HardWareLib.INVALID_HANDLE_VALUE)
{
throw new Exception("没有该类设备");
}
HardWareLib.SP_DEVINFO_DATA deviceInfoData;//想避免在api中使用ref,就把structure映射成类
deviceInfoData = new HardWareLib.SP_DEVINFO_DATA();
deviceInfoData.cbSize = 28;//如果要使用SP_DEVINFO_DATA,一定要给该项赋值28=16+4+4+4
deviceInfoData.devInst = 0;
deviceInfoData.classGuid = System.Guid.Empty;
deviceInfoData.reserved = 0;
UInt32 i;
StringBuilder property = new StringBuilder(HardWareLib.MAX_DEV_LEN);
for (i = 0; HardWareLib.SetupDiEnumDeviceInfo(hDevInfo, i, deviceInfoData); i++)
{
// Console.Write(deviceInfoData.classGuid.ToString());
// HardWareOperation.SetupDiGetDeviceInstanceId(hDevInfo, deviceInfoData, porperty, (uint)porperty.Capacity, 0);
HardWareLib.SetupDiGetDeviceRegistryProperty(hDevInfo, deviceInfoData,
(uint)HardWareLib.SPDRP_.SPDRP_CLASS,
0, property, (uint)property.Capacity, IntPtr.Zero);
if (property.ToString().ToLower() != "ports") continue;//首先看看是不是串口设备(有些USB设备不是串口设备)
HardWareLib.SetupDiGetDeviceRegistryProperty(hDevInfo, deviceInfoData,
(uint)HardWareLib.SPDRP_.SPDRP_HARDWAREID,
0, property, (uint)property.Capacity, IntPtr.Zero);
if (!(property.ToString().ToLower().Contains(vid.ToLower())
&& property.ToString().ToLower().Contains(pid.ToLower()))) continue;//找到对应于vid&pid的设备
HardWareLib.SetupDiGetDeviceRegistryProperty(hDevInfo, deviceInfoData,
(uint)HardWareLib.SPDRP_.SPDRP_FRIENDLYNAME,
0, property, (uint)property.Capacity, IntPtr.Zero);
break;
}
HardWareLib.SetupDiDestroyDeviceInfoList(hDevInfo);//记得用完释放相关内存
string friendlyName = property.ToString();
string pattern=@"(COM[1-9][0-9]?)$";//friendlyName一般形式为以(COMn)结尾,n为1-99
if (Regex.IsMatch(friendlyName, pattern, RegexOptions.IgnoreCase))
{
string portName=Regex.Match(friendlyName, pattern, RegexOptions.IgnoreCase).Value;
return portName.Trim(new char[] { '(', ')' });
}
return null;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
return null;
}
}
HardWareLib.cs(也就是前一篇文章的一个类)
HardWareLib.cs(也就是前一篇文章的一个类) using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
namespace ForGeneralUse
{
public class HardWareLib
{
/**//**//**//// <summary>
/// 设备的各项属性,注意有些属性是不通用的,例如SPDRP_FRIENDLYNAME只适用于端口设备
/// </summary>
public enum SPDRP_
{
SPDRP_DEVICEDESC = (0x00000000), // DeviceDesc (R/W)
SPDRP_HARDWAREID = (0x00000001), // HardwareID (R/W)
SPDRP_SERVICE = (0x00000004), // Service (R/W)
SPDRP_CLASS = (0x00000007), // Class (R--tied to ClassGUID)
SPDRP_CLASSGUID = (0x00000008), // ClassGUID (R/W)
SPDRP_DRIVER = (0x00000009), // Driver (R/W)
SPDRP_CONFIGFLAGS = (0x0000000A), // ConfigFlags (R/W)
SPDRP_MFG = (0x0000000B), // Mfg (R/W)
SPDRP_FRIENDLYNAME = (0x0000000C), // FriendlyName (R/W)
SPDRP_PHYSICAL_DEVICE_OBJECT_NAME = (0x0000000E), // PhysicalDeviceObjectName (R)
SPDRP_CAPABILITIES = (0x0000000F), // Capabilities (R)
SPDRP_REMOVAL_POLICY_HW_DEFAULT = (0x00000020), // Hardware Removal Policy (R)
SPDRP_INSTALL_STATE = (0x00000022), // Device Install State (R)
}
public const int DIGCF_ALLCLASSES = (0x00000004);
public const int DIGCF_DEVICEINTERFACE = 0x00000010;
public const int DIGCF_PRESENT = (0x00000002);
public const int INVALID_HANDLE_VALUE = -1;
public const int MAX_DEV_LEN = 1000;
/**//**//**//// <summary>
/// 获取一个指定类别或全部类别的所有已安装设备的信息
/// </summary>
/// <param name="gClass">该类别对应的guid</param>
/// <param name="iEnumerator">类别名称(在HKLMSYSTEMCurrentControlSetEnum内获取)</param>
/// <param name="hParent">应用程序定义的窗口句柄</param>
/// <param name="nFlags">获取的模式</param>
/// <returns>设备信息集合的句柄</returns>
[DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern IntPtr SetupDiGetClassDevs(ref Guid gClass, String enumerator, IntPtr hParent, UInt32 nFlags);
/**//**//**//// <summary>
/// 获得该设备的设备范例ID
/// </summary>
/// <param name="DeviceInfoSet">设备信息集合</param>
/// <param name="DeviceInfoData">表示该设备</param>
/// <param name="DeviceInstanceId">设备范例ID(输出)</param>
/// <param name="DeviceInstanceIdSize">该ID所占大小(字节)</param>
/// <param name="RequiredSize">需要多少字节</param>
/// <returns>是否成功</returns>
[DllImport("setupapi.dll", SetLastError = true)]
public static extern bool SetupDiGetDeviceInstanceId(IntPtr DeviceInfoSet,
HardWareLib.SP_DEVINFO_DATA DeviceInfoData,
StringBuilder DeviceInstanceId,
UInt32 DeviceInstanceIdSize,
UInt32 RequiredSize);
/**//**//**//// <summary>
/// 枚举指定设备信息集合的成员,并将数据放在SP_DEVINFO_DATA中
/// </summary>
/// <param name="lpInfoSet">设备信息集合句柄</param>
/// <param name="dwIndex">元素索引</param>
/// <param name="devInfoData">表示一个设备(作为输出)</param>
/// <returns>是否成功</returns>
[DllImport("setupapi.dll", SetLastError = true)]
public static extern bool SetupDiEnumDeviceInfo(IntPtr lpInfoSet, UInt32 dwIndex, SP_DEVINFO_DATA devInfoData);
/**//**//**//// <summary>
/// 获取指定设备的属性
/// </summary>
/// <param name="lpInfoSet">设备信息集合</param>
/// <param name="DeviceInfoData">表示该设备</param>
/// <param name="Property">表示要获取哪项属性</param>
/// <param name="PropertyRegDataType">注册类型</param>
/// <param name="PropertyBuffer">属性(输出)</param>
/// <param name="PropertyBufferSize">存储属性的字节大小</param>
/// <param name="RequiredSize">需要的字节大小</param>
/// <returns>是否成功</returns>
[DllImport("setupapi.dll", SetLastError = true)]
public static extern bool SetupDiGetDeviceRegistryProperty(IntPtr lpInfoSet,
SP_DEVINFO_DATA DeviceInfoData,
UInt32 Property,
UInt32 PropertyRegDataType,
StringBuilder PropertyBuffer,
UInt32 PropertyBufferSize,
IntPtr RequiredSize);
/**//**//**//// <summary>
/// 销毁一个设备信息集合,并且释放所有关联的内存
/// </summary>
/// <param name="lpInfoSet">设备信息集合</param>
/// <returns></returns>
[DllImport("setupapi.dll", SetLastError = true)]
public static extern bool SetupDiDestroyDeviceInfoList(IntPtr lpInfoSet);
/**//**//**//// <summary>
/// 设备信息数据
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public class SP_DEVINFO_DATA
{
public int cbSize;//本结构的大小(字节表示)
public Guid classGuid;//本结构所表示的设备的GUID
public int devInst;//设备句柄
public ulong reserved;//没用
};
}
}
posted on 2010-09-13 21:27
漂漂 阅读(11256)
评论(0) 编辑 收藏 引用 所属分类:
深入vc++ 、
c#开发