Forum: PC-Programmierung Serialports in Win32 App finden


von Olli Z. (z80freak)


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
}

von Peter D. (fenstergucker)


Lesenswert?

Ich lese dafür die Einträge aus der Registry ein:

"HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM"

Darunter findest du alle aktuell vorhandenen Ports.

Peter

von Sigi (Gast)


Lesenswert?

Versuchs mal mit den Std. Win32-Funktionen
- FindFirstFile
- FindNextFile,
Wildcards werden akzeptiert.

Bei COMs müsste evtl. noch der Anfang des Filenamemusters
entsprechend angepasst werden.

von Olli Z. (z80freak)


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
}

: Bearbeitet durch User
von Peter D. (fenstergucker)


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

von Olli Z. (z80freak)


Angehängte Dateien:

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
                ...

: Bearbeitet durch User
von Peter D. (fenstergucker)


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

von Joachim B. (jar)


Angehängte Dateien:

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/

: Bearbeitet durch User
von Frank K. (fchk)


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.

: Bearbeitet durch User
von Hans-Georg L. (h-g-l)


Lesenswert?

Die Funktion gibt es bereits fix und fertig in windows ... -> winbase.h
https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getcommports

von Olli Z. (z80freak)


Lesenswert?

Hans-Georg L. schrieb:
> Die Funktion gibt es bereits fix und fertig in windows ... -> winbase.h
> 
https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getcommports

Da meckert Codeblocks das es die Funktion nicht kennt (not declared). 
<winbase.h> ist drin.

: Bearbeitet durch User
von Knecht Ruprecht (Gast)


Lesenswert?

Olli Z. schrieb:
> Da meckert Codeblocks das es die Funktion nicht kennt (not declared).
> <winbase.h> ist drin.

Versuche mal

#include <windows.h>

Und OneCore.lib linken.

von Olli Z. (z80freak)


Lesenswert?

Knecht Ruprecht schrieb:
> Olli Z. schrieb:
>> Da meckert Codeblocks das es die Funktion nicht kennt (not declared).
>> <winbase.h> ist drin.
>
> Versuche mal
>
> #include <windows.h>
>
> Und OneCore.lib linken.

Ja, hatte ich gemacht, klappt trotzdem nicht:
1
#include <windows.h>
2
#include <winbase.h>
3
#pragma comment (lib, "OneCore.lib")

von Knecht Ruprecht (Gast)


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).

von loeti2 (Gast)


Lesenswert?

GetCommPorts ist ab Windows 10, vielleicht mal _WIN32_WINNT prüfen.

von Wolfgang H. (drahtverhau)


Lesenswert?


von Knecht Ruprecht (Gast)


Lesenswert?

@Wolfgang
Das ist für .NET, glaube ich.

von Olli Z. (z80freak)


Angehängte Dateien:

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
                    ....

: Bearbeitet durch User
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.