Serialports in Win32 App finden

OP #7349218
Lesenswert?

Ich versuche mich gerade an Windows-Apps in C/C++ (Code::Blocks IDE und MinGW Compiler). Dort möchte ich beim Aufruf eines Menüpunktes eine Liste der zu diesem Zeitpunkt im OS verfügbaren Serial-Ports ermitteln aus denen ich dann Untermenüs zur Auswahl dynamisch erstellen kann.

Die komme ich am besten an eine solche Liste? Die Geräte mit denen ich aktuell teste haben alle ein "COM..." im Portbezeichner, auch wenn es sich dabei teilweise um virtuelle COM-Ports (USB) handelt.

Mein erster (brute-force) Versuch über eine Loop von 1-255 mit der API-Funktion GetDefaultCommConfig() hat zwar teilweise funktioniert, dauert aber sehr lange (ca. 1 Minute).

1
BOOL COM_exists(int port)
2
{
3
    char buffer[7];
4
    COMMCONFIG CommConfig;
5
    DWORD size;
6
    snprintf( buffer, sizeof buffer, "COM%d", port);
7
    size = sizeof CommConfig;
8
    return (GetDefaultCommConfig(buffer, &CommConfig, &size) || size > sizeof CommConfig);
9
}
10

11
for (int i=1; i<256; i++)
12
{
13
    if (COM_exists(i)) {
14
        // HURRA
15
    }
16
}
OP #7349458
Lesenswert?

Peter D. schrieb:

Ich lese dafür die Einträge aus der Registry ein: "HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM"

Klingt spannend. Alle Beispiele die ich so gefunden habe nutzen die OpenFile funktionen. Kannst Du einen Beispielcode geben wie man über die Registry iteriert?

Ich denke ich muss zuerst den Schlüssel öffnen. Dazu gibt es in der Win32 API im "Winreg.h" schon ein halbes Dutzend Funktionen. Ich könnte mir diese vorstellen:

1
LSTATUS RegOpenKeyExA(
2
  [in]           HKEY   hKey,
3
  [in, optional] LPCSTR lpSubKey,
4
  [in]           DWORD  ulOptions,
5
  [in]           REGSAM samDesired,
6
  [out]          PHKEY  phkResult
7
);
1
HKEY hk;
2
long h;
3
h = RegOpenKeyExA(HKEY_LOCAL_MACHINE, TEXT("HARDWARE\\DEVICEMAP\\SERIALCOMM"), 0, KEY_QUERY_VALUE, &hk);
4
if (h == ERROR_SUCCESS) {
5
    printf("REG FOUND\n");
6
}
#7349504
Lesenswert?

Hallo Olli,

hier ist mein PureBasic-Code. Die Funktionen mit Unterstrich am Ende sind die Win-API-Funktionen.

Peter

1
#sKeySerialComm = "HARDWARE\DEVICEMAP\SERIALCOMM"
2
          
3
iResult = RegOpenKeyEx_(#HKEY_LOCAL_MACHINE, #sKeySerialComm, 0, #KEY_QUERY_VALUE, @ihKey)
4
If Not (iResult = #ERROR_SUCCESS)
5
  DebugView::Error("Es konnte der Registry-Key nicht geöffnet werden: " + #sKeySerialComm)
6
Else
7
  slpName = Space(1024)
8
  
9
  Repeat
10
    ilpcbName = 1024
11
    iResult = RegEnumValue_(ihKey, c, @slpName, @ilpcbName, 0, 0, 0, 0)
12
    If  Not (iResult = #ERROR_SUCCESS)
13
      ; Kein Fehler, es ist dann kein Eintrag mehr vorhanden zum Einlesen.
14
      Break
15
    Else
16
      sValue = Left(slpName, ilpcbName)
17
      If sValue = ""
18
        Break
19
      EndIf
20
      ilpcbName = 1024
21
      ilType = 0
22
      iResult = RegQueryValueEx_(ihKey, sValue, 0, @ilType, @slpName, @ilpcbName)
23
      If Not (iResult = #ERROR_SUCCESS)
24
        DebugView::Error("Der Wert von dem Eintrag >" + sValue + "< konnte nicht eingelesen werden.")
25
      Else
26
        AddGadgetItem(\iPortComboboxNumber, -1, slpName)
27
        \iInstrumentSerialCount + 1
28
      EndIf
29
      c + 1
30
    EndIf
31
  ForEver
32
  RegCloseKey_(ihKey)
33
EndIf
OP #7349979
Lesenswert?

Mit diesem Ansatz erhalte ich zumindest den ersten COM-Port aus der Registry:

1
            HKEY hk;
2
            long h;
3
            h = RegOpenKeyExA(HKEY_LOCAL_MACHINE, TEXT("HARDWARE\\DEVICEMAP\\SERIALCOMM"), 0, KEY_QUERY_VALUE, &hk);
4
            if (h == ERROR_SUCCESS)
5
            {
6
                long rc;
7
                LPTSTR lpValueName = new TCHAR[50];
8
                DWORD dwValueSize = 50;
9
                DWORD dwIndex = 0;
10
                LPBYTE lpValue = new BYTE[50];
11
                DWORD lpValueSize = 50;
12
                while ((rc = RegEnumValueA(hk, dwIndex++, lpValueName, &dwValueSize, NULL, NULL, lpValue, &lpValueSize)) == ERROR_SUCCESS)
13
                {
14
                    printf("%s = %s\n", lpValueName, lpValue);
15
                }
16
            } else {
17
                printf("ERROR - Can't open registry\n");
18
            }

Es müsste aber noch ein weiterer kommen, eigentlich.

Füge ich einen Query davor ein, zeigt dieser auch 2 Einträge an:

1
            ...
2
            if (h == ERROR_SUCCESS)
3
            {
4
                DWORD cSubKeys;
5
                DWORD cValues;
6
                cSubKeys = 0;
7
                cValues = 0;
8
                RegQueryInfoKeyA(hk, NULL, NULL, NULL, &cSubKeys, NULL, NULL, &cValues, NULL, NULL, NULL, NULL);
9
                if (cValues) {
10
                    printf( "Number of values found: %d\n", cValues);
11
                }
12
                ...
Angehängte Dateien:
#7350025
Lesenswert?

Hallo Olli,

bei der while-Schleife setzt du die Werte nicht erneut. lpvalueSize ist zuerst 50, wird dann mit der Länge des ersten Ports 'COM5' ersetzt, also 4. Beim zweiten Aufruf übergibst du die 4, und solltest einen Fehler empfangen, da der Buffer jetzt zu klein ist für den zweiten Port 'COM12', der benötigt für 5 Zeichen Platz. Ist aber nur eine Vermutung. Du solltest den Rückgabewert prüfen.

Peter

#7350047
Lesenswert?

Olli Z. schrieb:

Dort möchte ich beim Aufruf eines Menüpunktes eine Liste der zu diesem Zeitpunkt im OS verfügbaren Serial-Ports ermitteln aus denen ich dann Untermenüs zur Auswahl dynamisch erstellen kann.

Die komme ich am besten an eine solche Liste? Die Geräte mit denen ich aktuell teste haben alle ein "COM..." im Portbezeichner, auch wenn es sich dabei teilweise um virtuelle COM-Ports (USB) handelt.

ich hatte das mal mit LCC32 gemacht um mein Antennenmessgerät zu finden und die falschen COM: zu ignorieren.

https://lcc-win32.services.net/

Angehängte Dateien:
#7350051
Lesenswert?

Die Suche in der Registry ist kein wirklich empfohlender Ansatz, weil sich das von WIndows-Version zu Windows-Version ändern kann. Normalerweise geht man über die setupapi.dll.

https://nakov.com/blog/2009/05/10/enumerate-all-com-ports-and-find-their-name-and-description-in-c/

In neueren Windows-Versionen ist der cfgmgr32 dazugekommen - der ist auch aus UWP-Programmen erreichbar.

https://learn.microsoft.com/en-us/windows-hardware/drivers/install/porting-from-setupapi-to-cfgmgr32

fchk

PS: Das geht für alle Arten von Geräten.

Gast #7351666
Lesenswert?

winbase.h brauchst du nicht explizit, das wird schon von windows.h inkludiert. Hilft dir jetzt natürlich auch nicht weiter.

Unter Windows habe ich für C++ nur Visual Studio installiert, damit klappt der Aufruf von GetCommPorts so wie beschrieben einwandfrei. Du müsstest nachforschen, warum der Compiler die Deklaration nicht sieht. Wobei ... verwendet MinGW nicht eigene Windows-Header, also nicht die aus dem Windows-SDK? Vielleicht ist GetCommPorts da einach (noch?) nicht drin? Am besten mal nachsehen (nach "GetCommPorts" suchen).

OP #7358634
Lesenswert?

Peter D. schrieb:

bei der while-Schleife setzt du die Werte nicht erneut. lpvalueSize ist

Hallo Peter, danke für den Tipp aber auch wenn ich alle Variablen neu initialisiere bekomme ich immer nur 2 von 3:

1
Number of device found: 3
2
Number of devices found: 3
3
#0: COM5 (len 5) \Device\Serial0 (len 15)
4
#1: COM8 (len 5) \Device\VCP0 (len 12)

Wenn ich nun den mit COM12 belegten Port auf COM2 ändere bekomme ich alle drei:

1
Number of devices found: 3
2
#0: COM5 (len 5) \Device\Serial0 (len 15)
3
#1: COM8 (len 5) \Device\VCP0 (len 12)
4
#2: COM2 (len 5) \Device\Silabser0 (len 17)

Du hast also absolut recht Peter, wenn ich den "rc" mal auswerte:

1
                    rc = RegEnumValueA(hk, dwIndex, lpValueName, &lpcchValueName, NULL, NULL, lpData, &lpcbData);
2
                    if (rc != ERROR_SUCCESS) {
3
                        if (rc == ERROR_MORE_DATA) {
4
                            printf("ERROR - Buffer too small\n");
5
                        } else if (rc == ERROR_NO_MORE_ITEMS) {
6
                            printf("NO MORE ITEMS\n");
7
                        } else {
8
                            printf("ERROR %d\n", rc);
9
                        }
10
                        break;
11
                    }

bekomme ich "ERROR - Buffer too small" angezeigt. Wenn ich die Funktion oben vor dem Aufruf die Bufferlängen "zurücksetze", klappt es:

1
                    lpcchValueName = 50;
2
                    lpcbData = 50;
3
                    rc = RegEnumValueA(hk, dwIndex, lpValueName, &lpcchValueName, NULL, NULL, lpData, &lpcbData);
4
                    ....
Angehängte Dateien:

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren