65 votes

Comment puis-je obtenir une liste des ports série disponibles dans Win32 ?

J'ai un ancien code qui fournit une liste des ports COM disponibles sur le PC en appelant la fonction EnumPorts() puis en filtrant les noms de port qui commencent par "COM".

À des fins de test, il serait très utile de pouvoir utiliser ce code avec quelque chose comme com0com qui fournit des paires de ports COM virtuels bouclés ensemble comme un null-modem.

Cependant, les ports com0com ne sont pas trouvés par la fonction EnumPorts() (même sans filtrer pour "COM"). HyperTerminal et SysInternals PortMon peuvent tous deux les voir, donc je suis sûr qu'il est installé correctement.

Existe-t-il donc une autre fonction Win32 qui fournit une liste définitive des ports série disponibles ?

100voto

GrahamS Points 3315

El EnumSerialPorts v1.20 suggéré par Nick D utilise neuf différentes méthodes pour lister les ports série ! Nous ne manquons certainement pas de choix, même si les résultats semblent varier.

Pour épargner à d'autres la peine de le faire, je vais les énumérer ici et indiquer leur succès dans la recherche de la com0com sur mon PC (XP Pro SP2) :

  1. CreateFile("COM" + 1->255) comme suggéré par Wael Dalloul
    ✔ Trouvé les ports com0com, ça a pris 234ms.

  2. QueryDosDevice()
    ✔ Trouvé les ports com0com, ça a pris 0ms.

  3. GetDefaultCommConfig("COM" + 1->255)
    ✔ Trouvé les ports com0com, ça a pris 235ms.

  4. "SetupAPI1" utilisant des appels à SETUPAPI.DLL
    ✔ A trouvé des ports com0com, a également signalé des "noms amis", a pris 15ms.

  5. "SetupAPI2" utilisant des appels à SETUPAPI.DLL
    ✘ N'a pas trouvé de ports com0com, a signalé "noms amis", a pris 32ms.

  6. EnumPorts()
    ✘ A signalé quelques ports non-COM, n'a pas trouvé de ports com0com, a pris 15ms.

  7. Utilisation des appels WMI
    ✔ A trouvé des ports com0com, a également signalé des "noms amis", a pris 47ms.

  8. Base de données COM utilisant des appels à MSPORTS.DLL
    ✔/✘ A signalé des ports non-COM, a trouvé des ports com0com, a pris 16ms.

  9. Interroger la clé de registre HKEY_LOCAL_MACHINE. \HARDWARE\DEVICEMAP\SERIALCOMM
    ✔ Trouvé les ports com0com, ça a pris 0ms. C'est apparemment ce que SysInternals PortMon utilise.

Sur la base de ces résultats, je pense que la méthode WMI est probablement celle qui répond le mieux à mes besoins, car elle est relativement rapide et, en prime, elle donne également des noms sympathiques (par exemple, "Port de communication (COM1)", "com0com - émulateur de port série").

1 votes

@GrahamS : excellente réponse - QueryDosDevice() a très bien fonctionné lors de la recherche de périphériques FTDI USB<->Port série là où d'autres méthodes avaient échoué.

0 votes

Heureux que cela ait aidé Jon, bien que @Nick Dandoulakis mérite vraiment la plupart du crédit pour m'avoir dirigé vers EnumSerialPorts en premier lieu.

1 votes

Vous devez faire précéder \\.\ pour accéder à COM > 9 car ils ne sont pas réservés dans l'espace de noms NT et ne sont accessibles que dans l'espace de noms du périphérique -- docs.microsoft.com/ru-ru/Windows/desktop/FileIO/

14voto

Nick Dandoulakis Points 26809

Il semble que ce ne soit pas une tâche simple.

Regarde ça : EnumSerialPorts v1.20

0 votes

Merci Nick, j'ai développé un peu ta réponse ci-dessous. Si personne ne vient avec quelque chose de plus précis, j'accepterai ta réponse.

3 votes

Attention : "Si vous voulez distribuer le code source avec votre application, alors vous n'êtes autorisé à distribuer que les versions publiées par l'auteur." Cela le rend inutile pour tout ce qui est open source - vous ne pouvez même pas corriger les bugs à moins que l'auteur original ne le fasse pour vous. Je suggère à tous ceux qui cherchent un code série de trouver quelque chose sous une licence moins fermée.

6voto

Wael Dalloul Points 7995

Vous pouvez faire une boucle par exemple de 1 à 50 et essayer d'ouvrir chaque port. Si le port est disponible, l'ouverture fonctionnera. Si le port est utilisé, vous obtiendrez une erreur de partage. Si le port n'est pas installé, vous obtiendrez une erreur de fichier non trouvé.

pour ouvrir le port, utilisez CreateFile API :

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

puis vérifiez le résultat.

8 votes

Il convient de mentionner que si vous essayez d'accéder aux ports COM > 9 avec CreateFile, vous obtiendrez toujours ERROR_FILE_NOT_FOUND, même si le port existe. Pour éviter ce comportement, le nom du port doit être passé en tant que \\.\COMx (en remplaçant x par le numéro du port que nous voulons tester). Lien : support.microsoft.com/kb/115831

3voto

Ville-Valtteri Points 3536

Dans mon cas, j'ai besoin à la fois des noms complets et des adresses des ports COM. J'ai des ports série physiques, des ports série USB et des ports série virtuels com0com.

Comme le suggère la réponse acceptée, j'utilise des appels WMI. SELECT * FROM Win32_PnPEntity trouve tous les périphériques. Il retourne les périphériques physiques comme ceci, et l'adresse peut être analysée à partir de Caption :

Serial Port for Barcode Scanner (COM13)

Cependant, pour les ports com0com Caption est comme ceci (pas d'adresse) :

com0com - serial port emulator

SELECT * FROM Win32_SerialPort renvoie les adresses ( DeviceID ), ainsi que les noms complets ( Name ). Cependant, il ne trouve que les ports série physiques et les ports com0com, pas les ports série USB.

Donc au final, j'ai besoin de deux appels WMI : SELECT * FROM Win32_SerialPort (l'adresse est DeviceID ) et SELECT * FROM Win32_PnPEntity WHERE Name LIKE '%(COM%' (l'adresse peut être analysée à partir de Caption ). J'ai réduit la liste des Win32_PnPEntity car il ne doit trouver que les dispositifs qui n'ont pas été trouvés lors du premier appel.

Ce code C++ peut être utilisé pour trouver tous les ports série :

// 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;
}

1voto

Gaiger Chen Points 116

J'ai réorganisé la fille de PJ. EnumSerialPorts comme des formes plus portables et individuelles, c'est plus utile.

Pour une meilleure compatibilité, j'utilise C, au lieu de C++.

Si vous en avez besoin ou si vous êtes intéressé, veuillez vous rendre sur le site suivant le poste dans mon blogger.

6 votes

"Pour une meilleure compatibilité, j'utilise C, au lieu de C++." Désolé, mais c'est stupide. Nous ne sommes pas en 1998 et il n'y a aucune raison d'utiliser le C dans Windows.

7 votes

@GlennMaynard "il n'y a aucune raison d'utiliser le C sous Windows". Bien sûr qu'il y en a ! Si vous avez une bibliothèque ou une application déjà écrite en C, ou si vous avez une application multiplateforme qui fonctionne également sur une plateforme où le C est la seule option, etc. Et bien d'autres raisons encore.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X