如何在 Windows 中获取 COM 端口的友好名称?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/304986/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How do I get the friendly name of a COM port in Windows?
提问by RichieACC
I have a GSM modem connected via USB. The modem creates 2 serial ports. The first is automatically attached to the modem, the second shows in Device Manager as "HUAWEI Mobile Connect - 3G PC UI Interface (COM6)"
我有一个通过 USB 连接的 GSM 调制解调器。调制解调器创建 2 个串行端口。第一个自动连接到调制解调器,第二个在设备管理器中显示为“HUAWEI Mobile Connect - 3G PC UI Interface (COM6)”
The second port is used to get vital information from the modem, such as signal quality; to send and receive text messages; and a whole host of other functions.
第二个端口用于从调制解调器获取重要信息,例如信号质量;发送和接收短信;以及一系列其他功能。
I am writing an application that will wrap up some of the features provided by the second port. What I need is a sure fire method of identifying which COM port is the spare one. Iterating the ports and checking a response to "ATE0" is not sufficient. The modem's port is usually the lower numbered one, and when a dial up connection is not active, it will respond to "ATE0" the same as the second port.
我正在编写一个应用程序,它将包含第二个端口提供的一些功能。我需要的是一种确定哪个 COM 端口是备用端口的可靠方法。迭代端口并检查对“ATE0”的响应是不够的。调制解调器的端口通常是编号较低的端口,当拨号连接未激活时,它将响应“ATE0”与第二个端口相同。
What I was thinking of doing is iterating the ports and checking their friendly name, as it shows in Device Manager. That way I can link the port in my application to the port labelled "HUAWEI Mobile Connect - 3G PC UI Interface (COM6)" in Device Manager. I've just not found any information yet that will allow me to get that name programmatically.
我想做的是迭代端口并检查它们的友好名称,如设备管理器中所示。这样我就可以将应用程序中的端口链接到设备管理器中标记为“HUAWEI Mobile Connect - 3G PC UI Interface (COM6)”的端口。我还没有找到任何可以让我以编程方式获得该名称的信息。
采纳答案by RichieACC
The information posted by Will Deanwas most helpful. This is the code that eventually worked for me. Everything in the PInvoke class was taken verbatim from http://www.pinvoke.net. I did have to change a data type here or there to make it work (like when using an enum instead of a uint) but it should be easy to figure out.
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;
}
I think the line Result = tmpstring.Substring(tmpstring.IndexOf("COM"),tmpstring.IndexOf(')') - tmpstring.IndexOf("COM"));
is a little clumsy, suggestions on how to clean it up would be appreciated.
我认为这条线Result = tmpstring.Substring(tmpstring.IndexOf("COM"),tmpstring.IndexOf(')') - tmpstring.IndexOf("COM"));
有点笨拙,关于如何清理它的建议将不胜感激。
Thanks for your help with this matter Will, without you, I'd still be searching google.
感谢您对此事的帮助 Will,没有您,我仍然会在 google 上搜索。
回答by Will Dean
A long time ago I wrote a utility for a client to do just this, but for a GPS rather than a modem.
很久以前,我为客户端编写了一个实用程序来执行此操作,但用于 GPS 而不是调制解调器。
I have just looked at it, and bits that jump-out as being possibly helpful are:
我刚刚看过它,跳出可能有用的部分是:
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))
{
...
(You call this bit in a loop with incrementing nDevice)
(您在循环中调用此位并递增 nDevice)
and then
进而
BYTE friendlyName[300];
if(SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_FRIENDLYNAME, NULL, friendlyName, sizeof(friendlyName), NULL))
{
strFriendlyNames += (LPCTSTR)friendlyName;
strFriendlyNames += '\n';
}
which finds the name of the device.
它找到设备的名称。
Hopefully that will help you in the right direction.
希望这会帮助您朝着正确的方向前进。
回答by Ilya
After you determine a Serial Port device is the one you want (by looking at its Friendly Name, by checking its parent device etc.), the proper way to get the port's name would probably be:
在您确定某个串行端口设备是您想要的设备后(通过查看其友好名称、检查其父设备等),获取端口名称的正确方法可能是:
- invoke
SetupDiOpenDevRegKey(hDevInfo, devInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ)
to get theHKEY
to the so-called device key - query this registry key for the
REG_SZ
value "PortName" - don't forget to close the
HKEY
:)
- 调用
SetupDiOpenDevRegKey(hDevInfo, devInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ)
以获取HKEY
所谓的设备密钥 - 查询此注册表项的
REG_SZ
值“PortName” - 不要忘记关闭
HKEY
:)
However, this might require so much interop in C# it's not even funny, so I don't blame you if you keep to the string parsing solution.
但是,这可能需要在 C# 中进行如此多的互操作,这甚至不好笑,因此如果您继续使用字符串解析解决方案,我不会责怪您。
回答by Leherenn
The C++ version based on @Will Dean answer.
基于@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;
}
}
回答by Will Dean
Glad it worked.
很高兴它起作用了。
You could try:
你可以试试:
Regex.Match(tmpstring, @"COM\s\d+").ToString()
Regex.Match(tmpstring, @"COM\s\d+").ToString()
for your string matching.
为您的字符串匹配。
As .NET style points, I'd add a "using System.Text", and I wouldn't start local variable names with capitals, and if I was feeling really virtuous, I would probably put the SetupDiDestroyDeviceInfoList in a finally{} clause.
作为 .NET 风格的要点,我会添加一个“使用 System.Text”,并且我不会以大写字母开头局部变量名称,如果我觉得非常有道德,我可能会将 SetupDiDestroyDeviceInfoList 放在 finally{} 子句中.