如何在 Windows 中获取 COM 端口的友好名称?
Posted
技术标签:
【中文标题】如何在 Windows 中获取 COM 端口的友好名称?【英文标题】:How do I get the friendly name of a COM port in Windows? 【发布时间】:2008-11-20 11:12:46 【问题描述】:我有一个通过 USB 连接的 GSM 调制解调器。调制解调器创建 2 个串行端口。第一个自动连接到调制解调器,第二个在设备管理器中显示为“HUAWEI Mobile Connect - 3G PC UI Interface (COM6)”
第二个端口用于从调制解调器获取重要信息,例如信号质量;发送和接收短信;以及许多其他功能。
我正在编写一个应用程序,它将封装第二个端口提供的一些功能。我需要的是一种确定哪个 COM 端口是备用端口的可靠方法。迭代端口并检查对“ATE0”的响应是不够的。调制解调器的端口通常是编号较小的端口,当拨号连接不活动时,它将与第二个端口一样响应“ATE0”。
我想做的是迭代端口并检查它们的友好名称,如设备管理器中所示。这样我就可以将我的应用程序中的端口链接到设备管理器中标有“HUAWEI Mobile Connect - 3G PC UI Interface (COM6)”的端口。我还没有找到任何可以让我以编程方式获取该名称的信息。
【问题讨论】:
【参考方案1】:很久以前,我为客户端编写了一个实用程序来执行此操作,但用于 GPS 而不是调制解调器。
我刚刚看过它,可能有帮助的部分是:
GUID guid = GUID_DEVCLASS_PORTS;
SP_DEVICE_INTERFACE_DATA interfaceData;
ZeroMemory(&interfaceData, sizeof(interfaceData));
interfaceData.cbSize = sizeof(interfaceData);
SP_DEVINFO_DATA devInfoData;
ZeroMemory(&devInfoData, sizeof(devInfoData));
devInfoData.cbSize = sizeof(devInfoData);
if(SetupDiEnumDeviceInfo(
hDeviceInfo, // Our device tree
nDevice, // The member to look for
&devInfoData
))
DWORD regDataType;
BYTE hardwareId[300];
if(SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_HARDWAREID, ®DataType, hardwareId, sizeof(hardwareId), NULL))
...
(您在循环中调用该位并增加 nDevice)
然后
BYTE friendlyName[300];
if(SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_FRIENDLYNAME, NULL, friendlyName, sizeof(friendlyName), NULL))
strFriendlyNames += (LPCTSTR)friendlyName;
strFriendlyNames += '\n';
查找设备的名称。
希望这会帮助您朝着正确的方向前进。
【讨论】:
这看起来可能会奏效。我现在正在写一个测试。谢谢一百万:)【参考方案2】:确定串行端口设备是您想要的设备后(通过查看其友好名称、检查其父设备等),获取端口名称的正确方法可能是:
调用SetupDiOpenDevRegKey(hDevInfo, devInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ)
将HKEY
获取到所谓的设备密钥
在此注册表项中查询 REG_SZ
值“PortName”
别忘了关闭HKEY
:)
但是,这可能需要在 C# 中进行如此多的互操作,这甚至都不好笑,所以如果你坚持字符串解析解决方案,我不会怪你。
【讨论】:
互操作很少。这对我来说似乎是正确的答案。在 C# 中工作就好了。【参考方案3】:Will Dean 发布的信息最有帮助。这是最终对我有用的代码。 PInvoke 类中的所有内容都是从 http://www.pinvoke.net 逐字提取的。我确实必须在这里或那里更改数据类型以使其工作(例如使用枚举而不是 uint 时),但应该很容易弄清楚。
internal static string GetComPortByDescription(string Description)
string Result = string.Empty;
Guid guid = PInvoke.GUID_DEVCLASS_PORTS;
uint nDevice = 0;
uint nBytes = 300;
byte[] retval = new byte[nBytes];
uint RequiredSize = 0;
uint PropertyRegDataType = 0;
PInvoke.SP_DEVINFO_DATA devInfoData = new PInvoke.SP_DEVINFO_DATA();
devInfoData.cbSize = Marshal.SizeOf(typeof(PInvoke.SP_DEVINFO_DATA));
IntPtr hDeviceInfo = PInvoke.SetupDiGetClassDevs(
ref guid,
null,
IntPtr.Zero,
PInvoke.DIGCF.DIGCF_PRESENT);
while (PInvoke.SetupDiEnumDeviceInfo(hDeviceInfo, nDevice++, ref devInfoData))
if (PInvoke.SetupDiGetDeviceRegistryProperty(
hDeviceInfo,
ref devInfoData,
PInvoke.SPDRP.SPDRP_FRIENDLYNAME,
out PropertyRegDataType,
retval,
nBytes,
out RequiredSize))
if (System.Text.Encoding.Unicode.GetString(retval).Substring(0, Description.Length).ToLower() ==
Description.ToLower())
string tmpstring = System.Text.Encoding.Unicode.GetString(retval);
Result = tmpstring.Substring(tmpstring.IndexOf("COM"),tmpstring.IndexOf(')') - tmpstring.IndexOf("COM"));
// if retval == description
// if (PInvoke.SetupDiGetDeviceRegistryProperty( ... SPDRP_FRIENDLYNAME ...
// while (PInvoke.SetupDiEnumDeviceInfo(hDeviceInfo, nDevice++, ref devInfoData))
PInvoke.SetupDiDestroyDeviceInfoList(hDeviceInfo);
return Result;
我认为Result = tmpstring.Substring(tmpstring.IndexOf("COM"),tmpstring.IndexOf(')') - tmpstring.IndexOf("COM"));
行有点笨拙,不胜感激有关如何清理它的建议。
感谢您对此事的帮助 Will,没有您,我仍然会在 google 上搜索。
【讨论】:
【参考方案4】:基于@Will Dean 回答的 C++ 版本。
#include <windows.h>
#include <initguid.h>
#include <devguid.h>
#include <setupapi.h>
void enumerateSerialPortsFriendlyNames()
SP_DEVINFO_DATA devInfoData = ;
devInfoData.cbSize = sizeof(devInfoData);
// get the tree containing the info for the ports
HDEVINFO hDeviceInfo = SetupDiGetClassDevs(&GUID_DEVCLASS_PORTS,
0,
nullptr,
DIGCF_PRESENT
);
if (hDeviceInfo == INVALID_HANDLE_VALUE)
return;
// iterate over all the devices in the tree
int nDevice = 0;
while (SetupDiEnumDeviceInfo(hDeviceInfo, // Our device tree
nDevice++, // The member to look for
&devInfoData))
DWORD regDataType;
DWORD reqSize = 0;
// find the size required to hold the device info
SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_HARDWAREID, nullptr, nullptr, 0, &reqSize);
BYTE* hardwareId = new BYTE[(reqSize > 1) ? reqSize : 1];
// now store it in a buffer
if (SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_HARDWAREID, ®DataType, hardwareId, sizeof(hardwareId) * reqSize, nullptr))
// find the size required to hold the friendly name
reqSize = 0;
SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_FRIENDLYNAME, nullptr, nullptr, 0, &reqSize);
BYTE* friendlyName = new BYTE[(reqSize > 1) ? reqSize : 1];
// now store it in a buffer
if (!SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_FRIENDLYNAME, nullptr, friendlyName, sizeof(friendlyName) * reqSize, nullptr))
// device does not have this property set
memset(friendlyName, 0, reqSize > 1 ? reqSize : 1);
// use friendlyName here
delete[] friendlyName;
delete[] hardwareId;
【讨论】:
“BYTE hardwareId[reqSize > 1 ? reqSize : 1];”--> “错误 C2131:表达式未计算为常数”。要解决它,请使用 --> BYTE* hardwareId = new BYTE[(reqSize > 1) ?请求大小:1]; @KamranBigdely 我做了编辑谢谢。代码依赖 VLA,在 Visual Studio 中不可用。 "sizeof(hardwareId)"--> 应该是 --> sizeof(hardwareId) * reqSize"。另外 "sizeof(friendlyName) * reqSize"--> 应该是 "sizeof(friendlyName) * reqSize"(这些更改应适用于 Visual Studio C++ 编译器)。【参考方案5】:很高兴它成功了。
你可以试试:
Regex.Match(tmpstring, @"COM\s\d+").ToString()
为你的字符串匹配。
作为 .NET 风格点,我会添加一个“使用 System.Text”,并且我不会以大写字母开头的局部变量名称,如果我觉得自己很善良,我可能会将 SetupDiDestroyDeviceInfoList 放在 finally 子句。
【讨论】:
正则表达式看起来更优雅。但是我必须更改一件事:“COM\s?\d+” COM 和数字之间并不总是有空格,因此它需要匹配 0 或更多。再次感谢。【参考方案6】:我为串行端口控制构建了一个库。它可以在注册表中搜索友好名称。这是链接。
https://github.com/kcwongjoe/serial_port
std::vector<SerialPortInfo> comPorts = SerialPort::getSerialPortList();
std::cout << comPorts[0].friendlyName << std::endl;
【讨论】:
【参考方案7】:使用LiGenChen发布的方法。 ComPortSetupAPISetupDiClassGuids 方法给出了最好的时间和友好的名称。
【讨论】:
如果您使用链接中的更多相关信息修改您的答案将会很有帮助【参考方案8】:基于这里的答案组合是一个获取 COM 号、VID / PID 和友好名称等的解决方案。
这里是一些获取连接设备列表的示例代码。
public static class SerialPortUtils
private static Guid GUID_DEVCLASS_PORTS = new Guid(0x4d36e978u, 0xe325, 0x11ce, 0xbf, 0xc1, 0x08, 0x00, 0x2b, 0xe1, 0x03, 0x18);
private unsafe static bool GetPortRegistryProperty(HDEVINFO classHandle, SP_DEVINFO_DATA* deviceInfo, uint spdrp, out string result)
DWORD size;
SetupAPI.SetupDiGetDeviceRegistryPropertyW(classHandle, deviceInfo, spdrp, null, null, 0, &size);
if (size == 0)
result = null;
return false;
var resultBuffer = new byte[(int)size];
fixed (byte* resultBufferPtr = resultBuffer)
if (SetupAPI.SetupDiGetDeviceRegistryPropertyW(classHandle, deviceInfo, spdrp, null, resultBufferPtr, size, null))
result = Encoding.Unicode.GetString(resultBufferPtr, (int)size - sizeof(char));
return true;
else
result = null;
return false;
public unsafe static List<SerialPortDeviceDesc> GetSerialPortDevices()
var results = new List<SerialPortDeviceDesc>();
// get present ports handle
var classHandle = SetupAPI.SetupDiGetClassDevsW(ref GUID_DEVCLASS_PORTS, null, IntPtr.Zero, SetupAPI.DIGCF_PRESENT);
if (classHandle == Common.INVALID_HANDLE_VALUE || classHandle == HDEVINFO.Zero) throw new Exception("SetupDiGetClassDevsW failed");
// enumerate all ports
var deviceInfo = new SP_DEVINFO_DATA();
uint deviceInfoSize = (uint)Marshal.SizeOf<SP_DEVINFO_DATA>();
deviceInfo.cbSize = deviceInfoSize;
uint index = 0;
while (SetupAPI.SetupDiEnumDeviceInfo(classHandle, index, &deviceInfo))
// get port name
string portName;
HKEY regKey = SetupAPI.SetupDiOpenDevRegKey(classHandle, &deviceInfo, SetupAPI.DICS_FLAG_GLOBAL, 0, SetupAPI.DIREG_DEV, WinNT.KEY_READ);
if (regKey == Common.INVALID_HANDLE_VALUE || regKey == IntPtr.Zero) continue;
using (var regHandle = new SafeRegistryHandle(regKey, true))
using (var key = RegistryKey.FromHandle(regHandle))
portName = key.GetValue("PortName") as string;
if (string.IsNullOrEmpty(portName)) continue;
// get registry values
if (!GetPortRegistryProperty(classHandle, &deviceInfo, SetupAPI.SPDRP_FRIENDLYNAME, out string friendlyName)) continue;
if (!GetPortRegistryProperty(classHandle, &deviceInfo, SetupAPI.SPDRP_HARDWAREID, out string hardwareID)) continue;
// add device
results.Add(new SerialPortDeviceDesc(friendlyName, portName, hardwareID));
// setup for next device
++index;
deviceInfo = new SP_DEVINFO_DATA();
deviceInfo.cbSize = deviceInfoSize;
// finish
SetupAPI.SetupDiDestroyDeviceInfoList(classHandle);
return results;
这里是 SerialPortDeviceDesc 类
public enum SerialPortType
Unknown,
COM
public class SerialPortDeviceDesc
public readonly string friendlyName, portName, hardwareID;
public readonly string vid, pid;
public readonly int portNumber = -1;
public readonly SerialPortType portType = SerialPortType.Unknown;
public SerialPortDeviceDesc(string friendlyName, string portName, string hardwareID)
this.friendlyName = friendlyName;
this.portName = portName;
this.hardwareID = hardwareID;
if (portName.StartsWith("COM") && int.TryParse(portName.Substring("COM".Length), out portNumber))
portType = SerialPortType.COM;
else
portNumber = -1;
var rx = Regex.Match(hardwareID, @"VID_(\w*)&PID_(\w*)", RegexOptions.IgnoreCase);
if (rx.Success)
vid = rx.Groups[1].Value;
pid = rx.Groups[2].Value;
【讨论】:
以上是关于如何在 Windows 中获取 COM 端口的友好名称?的主要内容,如果未能解决你的问题,请参考以下文章
如何设置 htaccess 以从用户友好的 URL 获取参数以与 JS 一起使用
使用PowerShell从com端口获取Linux设备IP地址