参考文档
https://www.cnblogs.com/catzhou/archive/2018/06/08/9156863.html
C#怎么获取已知USB设备驱动信息(请看内容) (microsoft.com)
遍历Windows设备树的几种方法_飞鹤的程序员人生-CSDN博客
目录
1. 新建类SetupAPI.cs,引用SetupAPI.dll中的操作
1.2. API函数SetupDiEnumDeviceInfo
1.3 API函数SetupDiGetDeviceInstanceId
1.4 API函数SetupDiGetDeviceRegistryProperty
2. 新建类USB2SerialPort,继承System.IO.Ports.SerialPort
2.2.1 生成VID和PID字符串,后面会用这个字符串过滤COM口
2.2.2 调用SetupDiGetClassDevs获取全部类别的所有已安装设备的信息
2.2.3 通过SetupDiEnumDeviceInfo遍历所有设备,找到COM端口号
2.2.1 前面与GetCom一样,到遍历所有设备,这里需要遍历2遍,第一遍是遍历到Class为“USB”,去获取设备的父系设备。
2.2.2 第二遍遍历和GetCom类似,先找到对应VID和PID的COM口,然后读取各个信息
1. 新建类SetupAPI.cs,引用SetupAPI.dll中的操作
1.1 API函数SetupDiGetClassDevs
[DllImport("SetupAPI.dll")]
public static extern IntPtr SetupDiGetClassDevs(
ref Guid ClassGuid,
UInt32 Enumerator,
IntPtr hwndParent,
UInt32 Flags
);
最后一个参数Flags的定义:
public const UInt32 DIGCF_DEFAULT = 0x00000001; // only valid with DIGCF_DEVICEINTERFACE
public const UInt32 DIGCF_PRESENT = 0x00000002;
public const UInt32 DIGCF_ALLCLASSES = 0x00000004;
public const UInt32 DIGCF_PROFILE = 0x00000008;
public const UInt32 DIGCF_DEVICEINTERFACE = 0x00000010;
调用例程:
// 获取全部类别的所有已安装设备的信息
UInt32 dwFlag = (SetupAPI.DIGCF_ALLCLASSES | SetupAPI.DIGCF_PRESENT);
Guid usbGuid = Guid.Empty;
hDevInfo = SetupAPI.SetupDiGetClassDevs(ref usbGuid, 0, IntPtr.Zero, dwFlag);
1.2. API函数SetupDiEnumDeviceInfo
[StructLayout(LayoutKind.Sequential, Pack = 4, CharSet = CharSet.Auto)]
public struct SP_DEVINFO_DATA
{
public int cbSize;
public Guid ClassGuid;
public IntPtr DevInst;
public IntPtr Reserved;
}
[DllImport("SetupAPI.dll")]
public static extern Boolean SetupDiEnumDeviceInfo(
IntPtr DeviceInfoSet,
UInt32 MemberIndex,
ref SP_DEVINFO_DATA DeviceInfoData
);
注意这里的SP_DEVINFO_DATA结构需要按照上面的方式声明,否则调用SetupDiGetClassDevs会返回False。SetupDiEnumDeviceInfo是获取DeviceInfoData信息。
1.3 API函数SetupDiGetDeviceInstanceId
[DllImport("SetupAPI.dll")]
public static extern Boolean SetupDiGetDeviceInstanceId(
IntPtr DeviceInfoSet,
ref SP_DEVINFO_DATA DeviceInfoData,
byte[] DeviceInstanceId,
UInt32 DeviceInstanceIdSize,
ref UInt32 RequiredSize
);
调用例程:
// 获取ID
if (!SetupAPI.SetupDiGetDeviceInstanceId(hDevInfo, ref sDevInfoData, PropertyBuffer, (UInt32)PropertyBuffer.Length, ref nSize))
{
continue;
}
strTemp = System.Text.Encoding.Default.GetString(PropertyBuffer);
1.4 API函数SetupDiGetDeviceRegistryProperty
[DllImport("SetupAPI.dll", CharSet = CharSet.Ansi)]
public static extern Boolean SetupDiGetDeviceRegistryProperty(
IntPtr DeviceInfoSet,
ref SP_DEVINFO_DATA DeviceInfoData,
UInt32 Property,
ref UInt32 PropertyRegDataType,
byte[] PropertyBuffer,
UInt32 PropertyBufferSize,
ref UInt32 RequiredSize
);
调用例程:
//读取Hardware ID
byte[] PropertyBuffer = new byte[260];
UInt32 PropertyRegDataType = 0;
SetupAPI.SetupDiGetDeviceRegistryProperty(
hDevInfo,
ref sDevInfoData,
SetupAPI.SPDRP_HARDWAREID,
ref PropertyRegDataType, PropertyBuffer,
(UInt32)PropertyBuffer.Length,
ref nSize);
strTemp = System.Text.Encoding.Default.GetString(PropertyBuffer);
2. 新建类USB2SerialPort,继承System.IO.Ports.SerialPort
public class USB2SerialPort:System.IO.Ports.SerialPort
{
}
2.1 API函数GetCom
public static eUSB2SerialPortStatus GetCom(
UInt16 vid,
UInt16 pid,
byte[] port
)
作用:找到对应VID和PID的串口COM编号。
参数:vid和pid分别是USB转串口芯片的VID和PID,为0时表示该过滤条件为空;port是返回的COM编号数组。
2.2.1 生成VID和PID字符串,后面会用这个字符串过滤COM口
string strVidPid; //= string.Format("VID_{0:X4}&PID_{1:X4}", vid, pid);
string strVid, strPid;
if (vid == 0)
strVid = "";
else
strVid = string.Format("VID_{0:X4}", vid);
if (pid == 0)
strPid = "";
else
strPid = string.Format("PID_{0:X4}", pid);
strVidPid = strVid + "&" + strPid;
strVidPid.ToUpper();
2.2.2 调用SetupDiGetClassDevs获取全部类别的所有已安装设备的信息
IntPtr hDevInfo = SetupAPI.INVALID_HANDLE_VALUE;
UInt32 dwFlag = (SetupAPI.DIGCF_ALLCLASSES | SetupAPI.DIGCF_PRESENT);
Guid usbGuid = Guid.Empty;
hDevInfo = SetupAPI.SetupDiGetClassDevs(ref usbGuid, 0, IntPtr.Zero, dwFlag);
if (SetupAPI.INVALID_HANDLE_VALUE == hDevInfo)
return eUSB2SerialPortStatus.NO_DEVICE;
2.2.3 通过SetupDiEnumDeviceInfo遍历所有设备,找到COM端口号
sDevInfoData.cbSize = Marshal.SizeOf(new SetupAPI.SP_DEVINFO_DATA());
sDevInfoData.ClassGuid = Guid.Empty;
sDevInfoData.DevInst = IntPtr.Zero;
sDevInfoData.Reserved = IntPtr.Zero;
for (UInt32 i = 0; SetupAPI.SetupDiEnumDeviceInfo(hDevInfo, i, ref sDevInfoData); i++)
{
}
2.2.3.1 通过SetupDiGetDeviceRegistryProperty读取设备的Class,这里数组PropertyBuffer只定义了4096个字节,更好的方式是调用2次SetupDiGetDeviceRegistryProperty获取实际数据的长度。
byte[] PropertyBuffer = new byte[4096];
UInt32 PropertyRegDataType = 0;
Array.Clear(PropertyBuffer, 0, PropertyBuffer.Length);
SetupAPI.SetupDiGetDeviceRegistryProperty
(
hDevInfo,
ref sDevInfoData,
SetupAPI.SPDRP_CLASS,
ref PropertyRegDataType, PropertyBuffer,
(UInt32)PropertyBuffer.Length,
ref nSize);
if(nSize > 0)
strTemp = System.Text.Encoding.Default.GetString(PropertyBuffer, 0, (int)nSize - 1
);
2.2.3.2找到Class为“PORTS”的设备
if(strTemp.ToUpper() == "PORTS")
{
}
2.2.3.3读取该设备的友好名称,找到设备的COM端口号
nSize = 0;
Array.Clear(PropertyBuffer, 0, PropertyBuffer.Length);
SetupAPI.SetupDiGetDeviceRegistryProperty(hDevInfo, ref sDevInfoData,
SetupAPI.SPDRP_FRIENDLYNAME,
ref PropertyRegDataType, PropertyBuffer,
(UInt32)PropertyBuffer.Length,
ref nSize);
// "XXX Virtual Com Port (COM?)"
if (nSize > 0)
strTemp = System.Text.Encoding.Default.GetString(PropertyBuffer, 0, (int)nSize - 1);
strTemp = strTemp.ToUpper();
int nStart = -1;
int nEnd = -1;
// 寻找串口信息
nStart = strTemp.IndexOf("(COM");
nEnd = strTemp.IndexOf(")");
if (nStart > -1 && nEnd > -1)
{
strTemp = strTemp.Substring(nStart + 4, nEnd - nStart - 4);
comPort = byte.Parse(strTemp);
}
else
continue;
2.2.3.4 读取该设备的Hardware ID,判断VID和PID是否一致,如果一致则将COM编号保存到port数组中。
Array.Clear(PropertyBuffer, 0, PropertyBuffer.Length);
SetupAPI.SetupDiGetDeviceRegistryProperty(
hDevInfo,
ref sDevInfoData,
SetupAPI.SPDRP_HARDWAREID,
ref PropertyRegDataType, PropertyBuffer,
(UInt32)PropertyBuffer.Length,
ref nSize);
if (nSize > 0)
strTemp = System.Text.Encoding.Default.GetString(PropertyBuffer, 0, (int)nSize - 1);
// 根据设备信息寻找VID PID一致的设备
strTemp.ToUpper();
if (strTemp.IndexOf(strVidPid) == -1)
{
continue;
}
port[n] = comPort;
n++;
2.2.4 遍历完成后释放设备
SetupAPI.SetupDiDestroyDeviceInfoList(hDevInfo);
2.2 API函数GetInfo
public static eUSB2SerialPortStatus GetInfo(
UInt16 vid,
UInt16 pid,
ref stUsb2UartDevice[] pstDev
)
获取USB转串口设备的详细信息,包括供应商,序列号,描述符等。
参数:vid和pid分别是USB转串口芯片的VID和PID,为0时表示该过滤条件为空;pstDev是返回的详细信息,其定义如下:
public struct stUsb2UartDevice
{
public byte port;
public byte usbIF;
public string ParentSN;
public string Manufacturer;
public string DeviceDsc;
public string SerialNumber;
};
其中port表示该设备的COM编号;usbIF是设备是第几个Interface;ParentSN是设备父系的序列号;Manufacturer是供应商;DeviceDsc是设备的描述符;SerialNumber是设备的序列号。
2.2.1 前面与GetCom一样,到遍历所有设备,这里需要遍历2遍,第一遍是遍历到Class为“USB”,去获取设备的父系设备。
SetupAPI.SP_DEVINFO_DATA sDevInfoData = new SetupAPI.SP_DEVINFO_DATA();
sDevInfoData.cbSize = Marshal.SizeOf(new SetupAPI.SP_DEVINFO_DATA());
sDevInfoData.ClassGuid = Guid.Empty;
sDevInfoData.DevInst = IntPtr.Zero;
sDevInfoData.Reserved = IntPtr.Zero;
for (UInt32 i = 0; SetupAPI.SetupDiEnumDeviceInfo(hDevInfo, i, ref sDevInfoData); i++)
{
}
2.2.1.1 通过SetupDiGetDeviceRegistryProperty获取设备的Class,判读是否是“USB”设备
//读取Class
byte[] PropertyBuffer = new byte[1024];
UInt32 PropertyRegDataType = 0;
Array.Clear(PropertyBuffer, 0, PropertyBuffer.Length);
SetupAPI.SetupDiGetDeviceRegistryProperty
(
hDevInfo,
ref sDevInfoData,
SetupAPI.SPDRP_CLASS,
ref PropertyRegDataType, PropertyBuffer,
(UInt32)PropertyBuffer.Length,
ref nSize);
if (nSize > 0)
strTemp = System.Text.Encoding.Default.GetString(PropertyBuffer, 0, (int)nSize - 1);
if (strTemp.ToUpper() == "USB")
{
}
2.2.1.2 通过SetupDiGetDeviceInstanceId读入ID,这个ID包括VID、PID和SerialNumber,判断是否VID和PID一致
string szID = "";
nSize = 0;
Array.Clear(PropertyBuffer, 0, PropertyBuffer.Length);
Array.Clear(PropertyBuffer, 0, PropertyBuffer.Length);
SetupAPI.SetupDiGetDeviceInstanceId(hDevInfo, ref sDevInfoData, PropertyBuffer, (UInt32)PropertyBuffer.Length, ref nSize);
if (nSize > 0)
szID = System.Text.Encoding.Default.GetString(PropertyBuffer, 0, (int)nSize - 1);
szID.ToUpper();
if (szID.IndexOf(strVidPid) != -1)
{
}
2.2.1.3 VID和PID一致后读入设备的Location Path,保存在一个ArrayList中,等查找到Port时再看是否为父系。
nSize = 0;
string szPath = "";
Array.Clear(PropertyBuffer, 0, PropertyBuffer.Length);
SetupAPI.SetupDiGetDeviceRegistryProperty(hDevInfo, ref sDevInfoData,
SetupAPI.SPDRP_LOCATION_PATHS,
ref PropertyRegDataType, PropertyBuffer,
(UInt32)PropertyBuffer.Length,
ref nSize);
if (nSize > 0)
szPath = System.Text.Encoding.Default.GetString(PropertyBuffer, 0, (int)nSize - 1);
usbParent.Path = szPath.Substring(0, szPath.IndexOf('\0'));
usbParent.SN = szID.Substring(szID.LastIndexOf(('\\')) + 1, szID.Length - szID.LastIndexOf(('\\')) - 1);
ParentList.Add(usbParent);
2.2.2 第二遍遍历和GetCom类似,先找到对应VID和PID的COM口,然后读取各个信息
2.2.2.1 读取USB设备的SerialNumber
Array.Clear(PropertyBuffer, 0, PropertyBuffer.Length);
if (!SetupAPI.SetupDiGetDeviceInstanceId(hDevInfo, ref sDevInfoData, PropertyBuffer, (UInt32)PropertyBuffer.Length, ref nSize))
{
continue;
}
if (nSize > 0)
strTemp = System.Text.Encoding.Default.GetString(PropertyBuffer, 0, (int)nSize - 1);
pstDev[n].SerialNumber = strTemp.Substring(strTemp.LastIndexOf(('\\')) + 1, strTemp.Length - strTemp.LastIndexOf(('\\')) - 1);
2.2.2.2 读取USB设备的Manufacturer
nSize = 0;
Array.Clear(PropertyBuffer, 0, PropertyBuffer.Length);
SetupAPI.SetupDiGetDeviceRegistryProperty(hDevInfo, ref sDevInfoData,
SetupAPI.SPDRP_MFG,
ref PropertyRegDataType, PropertyBuffer,
(UInt32)PropertyBuffer.Length,
ref nSize);
if (nSize > 0)
pstDev[n].Manufacturer = System.Text.Encoding.Default.GetString(PropertyBuffer, 0, (int)nSize - 1);
2.2.2.3 读取USB设备的DeviceDsc
nSize = 0;
Array.Clear(PropertyBuffer, 0, PropertyBuffer.Length);
SetupAPI.SetupDiGetDeviceRegistryProperty(hDevInfo, ref sDevInfoData,
SetupAPI.SPDRP_DEVICEDESC,
ref PropertyRegDataType, PropertyBuffer,
(UInt32)PropertyBuffer.Length,
ref nSize);
if (nSize > 0)
pstDev[n].DeviceDsc = System.Text.Encoding.Default.GetString(PropertyBuffer, 0, (int)nSize - 1);
2.2.2.3 读取USB设备的Location Path,然后判断这个Path是否在前面找到父系路径内(父系路径最长那个是最终的结果,所以要遍历所有的父系设备)
nSize = 0;
Array.Clear(PropertyBuffer, 0, PropertyBuffer.Length);
SetupAPI.SetupDiGetDeviceRegistryProperty(hDevInfo, ref sDevInfoData,
SetupAPI.SPDRP_LOCATION_PATHS,
ref PropertyRegDataType, PropertyBuffer,
(UInt32)PropertyBuffer.Length,
ref nSize);
if (nSize > 0)
strTemp = System.Text.Encoding.Default.GetString(PropertyBuffer, 0, (int)nSize - 1);
strTemp = strTemp.Substring(0, strTemp.IndexOf('\0'));
int parantLen = 0;
for(int j = 0; j < ParentList.Count; j++)
{
stUSBParent parent = (stUSBParent)ParentList[j];
if (strTemp.IndexOf(parent.Path) != -1 && parent.Path.Length > parantLen)
{
pstDev[n].ParentSN = parent.SN;
parantLen = parent.Path.Length;
}
}
2.2.2.4 通过关键字USBMI找到接口编号(似乎这个关键字并不是所有的USB转串口设备都有)
nStart = strTemp.IndexOf("#USBMI(");
nEnd = strTemp.LastIndexOf(")");
if (nStart > -1 && nEnd > -1)
{
strTemp = strTemp.Substring(nStart + 7, nEnd - nStart - 7);
pstDev[n].usbIF = byte.Parse(strTemp);
}
另外通过读取SPDRP_SECURITY_SDS可以得到一个值,和USBMI的编号一致(不确定是否就是接口编号)。
nSize = 0;
Array.Clear(PropertyBuffer, 0, PropertyBuffer.Length);
SetupAPI.SetupDiGetDeviceRegistryProperty(hDevInfo, ref sDevInfoData,
SetupAPI.SPDRP_SECURITY_SDS,
ref PropertyRegDataType, PropertyBuffer,
(UInt32)PropertyBuffer.Length,
ref nSize);
if (nSize > 0)
strTemp = System.Text.Encoding.Default.GetString(PropertyBuffer, 0, (int)nSize - 1);
获取父系的方式不是通用的,对于FTDI的FT4232H来说,它的路径都在USB类里面,以下面的打印信息为例:
Parent Path: PCIROOT(0)#PCI(1400)#USBROOT(0)#USB(4)#USB(4)#USB(2)#USBMI(2)
Parent Path: PCIROOT(0)#PCI(1400)#USBROOT(0)#USB(4)#USB(4)#USB(2)
Parent Path: PCIROOT(0)#PCI(1400)#USBROOT(0)#USB(4)#USB(4)#USB(2)#USBMI(3)
Parent Path: PCIROOT(0)#PCI(1400)#USBROOT(0)#USB(4)#USB(4)#USB(2)#USBMI(0)
Parent Path: PCIROOT(0)#PCI(1400)#USBROOT(0)#USB(4)#USB(4)#USB(2)#USBMI(1)
得到的父系路径已经包括了子系路径,而PORTS类里面得到的路径是
LocationPath: FTDIBUS\VID_0403+PID_6011+FT9EF1UB\0000
但是以Path为方法还是可以得到设备的父子系关系,针对不同品牌的芯片方案可能需要调整自己的代码。