如何获取 Win32 中可用串行端口的列表?

发布于 2024-08-03 23:54:50 字数 492 浏览 6 评论 0原文

我有一些遗留代码,通过调用 EnumPorts() 函数,然后过滤以“COM”开头的端口名称。

出于测试目的,如果我可以将此代码与 com0com 之类的东西一起使用,它会提供虚拟对COM 端口作为零调制解调器循环在一起。

但是,EnumPorts() 函数找不到 com0com 端口(即使没有过滤“COM”)。超级终端和 SysInternals PortMon 都可以看到它们,所以我确信它安装正确。

那么还有其他一些 Win32 函数可以提供可用串行端口的明确列表吗?

I have some legacy code that provides a list of the available COM ports on the PC by calling the EnumPorts() function and then filtering for the port names that start with "COM".

For testing purposes it would be very useful if I could use this code with something like com0com, which provides pairs of virtual COM ports looped together as a null-modem.

However the com0com ports are not found by the EnumPorts() function (even without filtering for "COM"). HyperTerminal and SysInternals PortMon can both see them, so I'm sure it is installed correctly.

So is there some other Win32 function that provides a definitive list of available serial ports?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

奢望 2024-08-10 23:54:50

Nick D 建议的 EnumSerialPorts v1.20 使用九种不同的方法来列出串行端口!我们当然不缺少选择,尽管结果似乎有所不同。

为了省去其他人的麻烦,我将在这里列出它们,并表明它们在我的 PC (XP Pro SP2) 上成功找到 com0com 端口:

  1. CreateFile("COM" + 1 ->255) 根据 Wael Dalloul 的建议
    ✔ 找到 com0com 端口,花费了 234 毫秒。

  2. QueryDosDevice()
    ✔ 找到 com0com 端口,花费了 0 毫秒。

  3. GetDefaultCommConfig("COM" + 1->255)
    ✔ 找到 com0com 端口,耗时 235 毫秒。

  4. “SetupAPI1”使用对 SETUPAPI.DLL 的调用
    ✔ 找到 com0com 端口,还报告“友好名称”,花费了 15 毫秒。

  5. “SetupAPI2”使用对 SETUPAPI.DLL 的调用
    ✘ 未找到 com0com 端口,报告“友好名称”,耗时 32 毫秒。

  6. EnumPorts()
    ✘ 报告一些非COM端口,没有找到com0com端口,花了15ms。

  7. 使用 WMI 调用
    ✔ 找到 com0com 端口,还报告“友好名称”,花费了 47 毫秒。

  8. 使用 MSPORTS.DLL 调用的 COM 数据库
    ✔/✘ 报告了一些非 COM 端口,找到了 com0com 端口,花费了 16 毫秒。

  9. 迭代注册表项 HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM
    ✔ 找到 com0com 端口,花费了 0 毫秒。这显然是 SysInternals PortMon 使用的。

基于这些结果,我认为 WMI 方法可能最适合我的要求,因为它相对较快,而且它还提供了友好的名称(例如“通信端口 (COM1)”、“com0com - 串行端口模拟器”)。

The EnumSerialPorts v1.20 suggested by Nick D uses nine different methods to list the serial ports! We're certainly not short on choice, though the results seem to vary.

To save others the trouble, I'll list them here and indicate their success in finding the com0com ports on my PC (XP Pro SP2):

  1. CreateFile("COM" + 1->255) as suggested by Wael Dalloul
    ✔ Found com0com ports, took 234ms.

  2. QueryDosDevice()
    ✔ Found com0com ports, took 0ms.

  3. GetDefaultCommConfig("COM" + 1->255)
    ✔ Found com0com ports, took 235ms.

  4. "SetupAPI1" using calls to SETUPAPI.DLL
    ✔ Found com0com ports, also reported "friendly names", took 15ms.

  5. "SetupAPI2" using calls to SETUPAPI.DLL
    ✘ Did not find com0com ports, reported "friendly names", took 32ms.

  6. EnumPorts()
    ✘ Reported some non-COM ports, did not find com0com ports, took 15ms.

  7. Using WMI calls
    ✔ Found com0com ports, also reported "friendly names", took 47ms.

  8. COM Database using calls to MSPORTS.DLL
    ✔/✘ Reported some non-COM ports, found com0com ports, took 16ms.

  9. Iterate over registry key HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM
    ✔ Found com0com ports, took 0ms. This is apparently what SysInternals PortMon uses.

Based on those results I think the WMI method probably suits my requirements best as it is relatively fast and as a bonus it also gives the friendly names (e.g. "Communications Port (COM1)", "com0com - serial port emulator").

撩起发的微风 2024-08-10 23:54:50

看来这不是一个简单的任务。

看看这个:EnumSerialPorts v1.20

It appears that it's not a simple task.

Check out this: EnumSerialPorts v1.20

燃情 2024-08-10 23:54:50

例如,您可以从 1 到 50 进行循环,并尝试打开每个端口。如果端口可用,则打开即可。如果端口正在使用中,您将收到共享错误。如果未安装该端口,您将收到文件未找到错误。

使用CreateFile API 打开端口:

HANDLE Port = CreateFile(
                  "\\\\.\\COM1",
                  GENERIC_READ | GENERIC_WRITE,
                  0,
                  NULL,
                  OPEN_EXISTING,
                  FILE_ATTRIBUTE_NORMAL,
                  NULL);

然后检查结果。

you can make loop for example from 1 to 50 and try to open each port. If the port is available, the open will work. If the port is in use, you'll get a sharing error. If the port is not installed, you'll get a file not found error.

to open the port use CreateFile API:

HANDLE Port = CreateFile(
                  "\\\\.\\COM1",
                  GENERIC_READ | GENERIC_WRITE,
                  0,
                  NULL,
                  OPEN_EXISTING,
                  FILE_ATTRIBUTE_NORMAL,
                  NULL);

then check the result.

风尘浪孓 2024-08-10 23:54:50

现在可在 Windows 中使用,GetCommPorts 可以直接返回通讯端口列表

获取包含格式正确的 COM 端口的数组。

该函数获取COM端口号
HKLM\Hardware\DeviceMap\SERIALCOMM 注册表项,然后写入它们
到调用者提供的数组。如果数组太小,函数
获得必要的尺寸。

您需要添加此代码才能正确链接该函数

#pragma comment (lib, "OneCore.lib")

It's available now in Windows, GetCommPorts can directly return a list of comm ports

Gets an array that contains the well-formed COM ports.

This function obtains the COM port numbers from the
HKLM\Hardware\DeviceMap\SERIALCOMM registry key and then writes them
to a caller-supplied array. If the array is too small, the function
gets the necessary size.

you will need to add this code in order to link the function correctly

#pragma comment (lib, "OneCore.lib")
九公里浅绿 2024-08-10 23:54:50

就我而言,我需要全名和 COM 端口地址。我有物理串口、USB串口和com0com虚拟串口。

正如已接受的答案所示,我使用 WMI 调用。 SELECT * FROM Win32_PnPEntity 查找所有设备。它返回这样的物理设备,并且可以从 Caption 解析地址:

Serial Port for Barcode Scanner (COM13)

但是,对于 com0com 端口 Caption 如下(无地址):

com0com - serial port emulator

SELECT * FROM Win32_SerialPort 返回地址 (DeviceID) 以及全名 (Name)。但是,它只找到物理串行端口和com0com端口,而不是USB串行端口。

所以最后,我需要两个 WMI 调用:SELECT * FROM Win32_SerialPort(地址为 DeviceID)和 SELECT * FROM Win32_PnPEntity WHERE Name LIKE '%(COM% ' (地址可以从 Caption 解析)。我已经缩小了 Win32_PnPEntity 调用的范围,因为它只需要查找第一次未找到的设备。 此 C++代码

可用于查找所有串行端口:

// Return list of serial ports as (number, name)
std::map<int, std::wstring> enumerateSerialPorts()
{
    std::map<int, std::wstring> result;

    HRESULT hres;

    hres = CoInitializeEx(0, COINIT_APARTMENTTHREADED);
    if (SUCCEEDED(hres) || hres == RPC_E_CHANGED_MODE) {
        hres =  CoInitializeSecurity(
            NULL,
            -1,                          // COM authentication
            NULL,                        // Authentication services
            NULL,                        // Reserved
            RPC_C_AUTHN_LEVEL_DEFAULT,   // Default authentication
            RPC_C_IMP_LEVEL_IMPERSONATE, // Default Impersonation
            NULL,                        // Authentication info
            EOAC_NONE,                   // Additional capabilities
            NULL                         // Reserved
            );

        if (SUCCEEDED(hres) || hres == RPC_E_TOO_LATE) {
            IWbemLocator *pLoc = NULL;

            hres = CoCreateInstance(
                CLSID_WbemLocator,
                0,
                CLSCTX_INPROC_SERVER,
                IID_IWbemLocator, (LPVOID *) &pLoc);

            if (SUCCEEDED(hres)) {
                IWbemServices *pSvc = NULL;

                // Connect to the root\cimv2 namespace with
                // the current user and obtain pointer pSvc
                // to make IWbemServices calls.
                hres = pLoc->ConnectServer(
                     bstr_t(L"ROOT\\CIMV2"),  // Object path of WMI namespace
                     NULL,                    // User name. NULL = current user
                     NULL,                    // User password. NULL = current
                     0,                       // Locale. NULL indicates current
                     NULL,                    // Security flags.
                     0,                       // Authority (for example, Kerberos)
                     0,                       // Context object
                     &pSvc                    // pointer to IWbemServices proxy
                     );
                if (SUCCEEDED(hres)) {
                    hres = CoSetProxyBlanket(
                       pSvc,                        // Indicates the proxy to set
                       RPC_C_AUTHN_WINNT,           // RPC_C_AUTHN_xxx
                       RPC_C_AUTHZ_NONE,            // RPC_C_AUTHZ_xxx
                       NULL,                        // Server principal name
                       RPC_C_AUTHN_LEVEL_CALL,      // RPC_C_AUTHN_LEVEL_xxx
                       RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx
                       NULL,                        // client identity
                       EOAC_NONE                    // proxy capabilities
                    );
                    if (SUCCEEDED(hres)) {
                        // Use Win32_PnPEntity to find actual serial ports and USB-SerialPort devices
                        // This is done first, because it also finds some com0com devices, but names are worse
                        IEnumWbemClassObject* pEnumerator = NULL;
                        hres = pSvc->ExecQuery(
                            bstr_t(L"WQL"),
                            bstr_t(L"SELECT Name FROM Win32_PnPEntity WHERE Name LIKE '%(COM%'"),
                            WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
                            NULL,
                            &pEnumerator);

                        if (SUCCEEDED(hres)) {
                            constexpr size_t max_ports = 30;
                            IWbemClassObject *pclsObj[max_ports] = {};
                            ULONG uReturn = 0;

                            do {
                                hres = pEnumerator->Next(WBEM_INFINITE, max_ports, pclsObj, &uReturn);
                                if (SUCCEEDED(hres)) {
                                    for (ULONG jj = 0; jj < uReturn; jj++) {
                                        VARIANT vtProp;
                                        pclsObj[jj]->Get(L"Name", 0, &vtProp, 0, 0);

                                        // Name should be for example "Serial Port for Barcode Scanner (COM13)"
                                        const std::wstring deviceName = vtProp.bstrVal;
                                        const std::wstring prefix = L"(COM";
                                        size_t ind = deviceName.find(prefix);
                                        if (ind != std::wstring::npos) {
                                            std::wstring nbr;
                                            for (size_t i = ind + prefix.length();
                                                i < deviceName.length() && isdigit(deviceName[i]); i++)
                                            {
                                                nbr += deviceName[i];
                                            }
                                            try {
                                                const int portNumber = boost::lexical_cast<int>(nbr);
                                                result[portNumber] = deviceName;
                                            }
                                            catch (...) {}
                                        }
                                        VariantClear(&vtProp);

                                        pclsObj[jj]->Release();
                                    }
                                }
                            } while (hres == WBEM_S_NO_ERROR);
                            pEnumerator->Release();
                        }

                        // Use Win32_SerialPort to find physical ports and com0com virtual ports
                        // This is more reliable, because address doesn't have to be parsed from the name
                        pEnumerator = NULL;
                        hres = pSvc->ExecQuery(
                            bstr_t(L"WQL"),
                            bstr_t(L"SELECT DeviceID, Name FROM Win32_SerialPort"),
                            WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
                            NULL,
                            &pEnumerator);

                        if (SUCCEEDED(hres)) {
                            constexpr size_t max_ports = 30;
                            IWbemClassObject *pclsObj[max_ports] = {};
                            ULONG uReturn = 0;

                            do {
                                hres = pEnumerator->Next(WBEM_INFINITE, max_ports, pclsObj, &uReturn);
                                if (SUCCEEDED(hres)) {
                                    for (ULONG jj = 0; jj < uReturn; jj++) {
                                        VARIANT vtProp1, vtProp2;
                                        pclsObj[jj]->Get(L"DeviceID", 0, &vtProp1, 0, 0);
                                        pclsObj[jj]->Get(L"Name", 0, &vtProp2, 0, 0);

                                        const std::wstring deviceID = vtProp1.bstrVal;
                                        if (deviceID.substr(0, 3) == L"COM") {
                                            const int portNumber = boost::lexical_cast<int>(deviceID.substr(3));
                                            const std::wstring deviceName = vtProp2.bstrVal;
                                            result[portNumber] = deviceName;
                                        }
                                        VariantClear(&vtProp1);
                                        VariantClear(&vtProp2);

                                        pclsObj[jj]->Release();
                                    }
                                }
                            } while (hres == WBEM_S_NO_ERROR);
                            pEnumerator->Release();
                        }
                    }
                    pSvc->Release();
                }
                pLoc->Release();
            }
        }
        CoUninitialize();
    }
    if (FAILED(hres)) {
        std::stringstream ss;
        ss << "Enumerating serial ports failed. Error code: " << int(hres);
        throw std::runtime_error(ss.str());
    }

    return result;
}

In my case, I need both the full names and COM port addresses. I have physical serial ports, USB serial ports, and com0com virtual serial ports.

Like the accepted answer suggests, I use WMI calls. SELECT * FROM Win32_PnPEntity find all devices. It returns physical devices like this, and address can be parsed from Caption:

Serial Port for Barcode Scanner (COM13)

However, for com0com ports Caption is like this (no address):

com0com - serial port emulator

SELECT * FROM Win32_SerialPort returns addresses (DeviceID), as well as full names (Name). However, it only finds physical serial ports and com0com ports, not USB serial ports.

So in the end, I need two WMI calls: SELECT * FROM Win32_SerialPort (address is DeviceID) and SELECT * FROM Win32_PnPEntity WHERE Name LIKE '%(COM%' (address can be parsed from Caption). I have narrowed down the Win32_PnPEntity call, because it only needs to find devices that were not found in the first call.

This C++ code can be used to find all serial ports:

// Return list of serial ports as (number, name)
std::map<int, std::wstring> enumerateSerialPorts()
{
    std::map<int, std::wstring> result;

    HRESULT hres;

    hres = CoInitializeEx(0, COINIT_APARTMENTTHREADED);
    if (SUCCEEDED(hres) || hres == RPC_E_CHANGED_MODE) {
        hres =  CoInitializeSecurity(
            NULL,
            -1,                          // COM authentication
            NULL,                        // Authentication services
            NULL,                        // Reserved
            RPC_C_AUTHN_LEVEL_DEFAULT,   // Default authentication
            RPC_C_IMP_LEVEL_IMPERSONATE, // Default Impersonation
            NULL,                        // Authentication info
            EOAC_NONE,                   // Additional capabilities
            NULL                         // Reserved
            );

        if (SUCCEEDED(hres) || hres == RPC_E_TOO_LATE) {
            IWbemLocator *pLoc = NULL;

            hres = CoCreateInstance(
                CLSID_WbemLocator,
                0,
                CLSCTX_INPROC_SERVER,
                IID_IWbemLocator, (LPVOID *) &pLoc);

            if (SUCCEEDED(hres)) {
                IWbemServices *pSvc = NULL;

                // Connect to the root\cimv2 namespace with
                // the current user and obtain pointer pSvc
                // to make IWbemServices calls.
                hres = pLoc->ConnectServer(
                     bstr_t(L"ROOT\\CIMV2"),  // Object path of WMI namespace
                     NULL,                    // User name. NULL = current user
                     NULL,                    // User password. NULL = current
                     0,                       // Locale. NULL indicates current
                     NULL,                    // Security flags.
                     0,                       // Authority (for example, Kerberos)
                     0,                       // Context object
                     &pSvc                    // pointer to IWbemServices proxy
                     );
                if (SUCCEEDED(hres)) {
                    hres = CoSetProxyBlanket(
                       pSvc,                        // Indicates the proxy to set
                       RPC_C_AUTHN_WINNT,           // RPC_C_AUTHN_xxx
                       RPC_C_AUTHZ_NONE,            // RPC_C_AUTHZ_xxx
                       NULL,                        // Server principal name
                       RPC_C_AUTHN_LEVEL_CALL,      // RPC_C_AUTHN_LEVEL_xxx
                       RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx
                       NULL,                        // client identity
                       EOAC_NONE                    // proxy capabilities
                    );
                    if (SUCCEEDED(hres)) {
                        // Use Win32_PnPEntity to find actual serial ports and USB-SerialPort devices
                        // This is done first, because it also finds some com0com devices, but names are worse
                        IEnumWbemClassObject* pEnumerator = NULL;
                        hres = pSvc->ExecQuery(
                            bstr_t(L"WQL"),
                            bstr_t(L"SELECT Name FROM Win32_PnPEntity WHERE Name LIKE '%(COM%'"),
                            WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
                            NULL,
                            &pEnumerator);

                        if (SUCCEEDED(hres)) {
                            constexpr size_t max_ports = 30;
                            IWbemClassObject *pclsObj[max_ports] = {};
                            ULONG uReturn = 0;

                            do {
                                hres = pEnumerator->Next(WBEM_INFINITE, max_ports, pclsObj, &uReturn);
                                if (SUCCEEDED(hres)) {
                                    for (ULONG jj = 0; jj < uReturn; jj++) {
                                        VARIANT vtProp;
                                        pclsObj[jj]->Get(L"Name", 0, &vtProp, 0, 0);

                                        // Name should be for example "Serial Port for Barcode Scanner (COM13)"
                                        const std::wstring deviceName = vtProp.bstrVal;
                                        const std::wstring prefix = L"(COM";
                                        size_t ind = deviceName.find(prefix);
                                        if (ind != std::wstring::npos) {
                                            std::wstring nbr;
                                            for (size_t i = ind + prefix.length();
                                                i < deviceName.length() && isdigit(deviceName[i]); i++)
                                            {
                                                nbr += deviceName[i];
                                            }
                                            try {
                                                const int portNumber = boost::lexical_cast<int>(nbr);
                                                result[portNumber] = deviceName;
                                            }
                                            catch (...) {}
                                        }
                                        VariantClear(&vtProp);

                                        pclsObj[jj]->Release();
                                    }
                                }
                            } while (hres == WBEM_S_NO_ERROR);
                            pEnumerator->Release();
                        }

                        // Use Win32_SerialPort to find physical ports and com0com virtual ports
                        // This is more reliable, because address doesn't have to be parsed from the name
                        pEnumerator = NULL;
                        hres = pSvc->ExecQuery(
                            bstr_t(L"WQL"),
                            bstr_t(L"SELECT DeviceID, Name FROM Win32_SerialPort"),
                            WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
                            NULL,
                            &pEnumerator);

                        if (SUCCEEDED(hres)) {
                            constexpr size_t max_ports = 30;
                            IWbemClassObject *pclsObj[max_ports] = {};
                            ULONG uReturn = 0;

                            do {
                                hres = pEnumerator->Next(WBEM_INFINITE, max_ports, pclsObj, &uReturn);
                                if (SUCCEEDED(hres)) {
                                    for (ULONG jj = 0; jj < uReturn; jj++) {
                                        VARIANT vtProp1, vtProp2;
                                        pclsObj[jj]->Get(L"DeviceID", 0, &vtProp1, 0, 0);
                                        pclsObj[jj]->Get(L"Name", 0, &vtProp2, 0, 0);

                                        const std::wstring deviceID = vtProp1.bstrVal;
                                        if (deviceID.substr(0, 3) == L"COM") {
                                            const int portNumber = boost::lexical_cast<int>(deviceID.substr(3));
                                            const std::wstring deviceName = vtProp2.bstrVal;
                                            result[portNumber] = deviceName;
                                        }
                                        VariantClear(&vtProp1);
                                        VariantClear(&vtProp2);

                                        pclsObj[jj]->Release();
                                    }
                                }
                            } while (hres == WBEM_S_NO_ERROR);
                            pEnumerator->Release();
                        }
                    }
                    pSvc->Release();
                }
                pLoc->Release();
            }
        }
        CoUninitialize();
    }
    if (FAILED(hres)) {
        std::stringstream ss;
        ss << "Enumerating serial ports failed. Error code: " << int(hres);
        throw std::runtime_error(ss.str());
    }

    return result;
}
甜尕妞 2024-08-10 23:54:50

我将 PJ Naughter 的 EnumSerialPorts 重新组织为更便携和个性化的形式,这更有用。

为了更好的兼容性,我使用 C,而不是 C++。

如果您需要或感兴趣,请访问我的博客中的帖子

I have reorganized PJ Naughter 's EnumSerialPorts as more portable and individual forms, that is more useful.

For better in compatibility, I use C, instead of C++.

If you need or be interested in it, please visit the post in my blogger.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文