Forum: PC-Programmierung Empfehlung Programmiersprache mit USB-HID-Routinen für Windows


von Backspace (Gast)


Lesenswert?

Moin Leute,

ich suche eine Programmiersprache, welche bereits Komponenten für die 
USB-HID-Kommunikation unter Windows beinhaltet und eine einfache 
GUI-Schnittstelle bietet.
Nach Möglichkeit soll es keine Interpreter-Sprache sein, schon gar nicht 
JAVA!
Am liebesten wäre mir eine finale .exe-Datei ohne Installationsmonster.

Kann hier jemand eine solche Sprache empfehlen, mit der er ohne 
Schwierigkeiten eine Windows-GUI-Anwendung mit USB-HID-Kommunikation 
programmiert hat?

Danke!

von Bernd K. (prof7bit)


Lesenswert?

Backspace schrieb:
> für die
> USB-HID-Kommunikation unter Windows beinhaltet

Das Gerät wird wie eine Datei geöffnet und die normalen 
open/read/write/close Funktionen können verwendet werden. Das geht in 
jeder Programmiersprache.

Das umständliche Part ist eigentlich nur den Dateinamen für ein 
gegebenes Gerät zu finden bzw. vorhandene Geräte aufzulisten, dafür 
gibts aber API-Funktionen (Stichwort SetupAPI) und Beipielcode im Netz.

: Bearbeitet durch User
von testfall (Gast)


Lesenswert?

C#? Das ist C in etwas billiger mit framework und gratis IDE vom 
Fensterbauer.

Sonst halt Borland Delphi. Aber das hängt stark an den Fähigkeiten mit 
PASCAL.

Sonst halt C. Aber das hängt stark am Umgang mit den GUI Dingern.

Sonst halt Fortran. Aber das hängt stark an Fortran.

Interessant wäre, was du schon kannst.

von Backspace (Gast)


Lesenswert?

Bernd K. schrieb:
> Das Gerät wird wie eine Datei geöffnet und die normalen
> read/write-Funktionen können verwendet werden. Das geht in jeder
> Programmiersprache.

Ja, das ist mir schon klar, das geht sogar mit JAVA.
Mit jeder Sprache ist ja auch eine GUI möglich.

Aber das beantwortet überhaupt nicht meine Frage zu einer Empfehlung 
nach den oben genannten Kriterien!

Also bevor die Dikussion sich hier am Thema vorbei entwickelt, 
wiederhole ich am besten nochmal den Abschlussatz aus meinem 
Eingangspost, wobei der Text davor gerne auch gelesen werden darf:

Kann hier jemand eine solche Sprache empfehlen, mit der er ohne
Schwierigkeiten eine Windows-GUI-Anwendung mit USB-HID-Kommunikation
programmiert hat?

von Bernd K. (prof7bit)


Lesenswert?

Hier sind ein paar Pascal Codeschnipsel von mir:
1
procedure TLazHid.Enumerate(Vid: Word; Pid: Word; List: TStringList);
2
var
3
  DevInfo: HDEVINFO;
4
  DevIntfData: TSPDeviceInterfaceData;
5
  DevIntfDetailData: PSPDeviceInterfaceDetailDataA;
6
  MemberIdx: DWORD;
7
  Size: DWORD = 0;
8
  DevData: TSPDevInfoData;
9
  Err: DWORD;
10
  Res: LongBool;
11
  Path: String;
12
  Search: String;
13
begin
14
  Search := LowerCase(Format('vid_%04x&pid_%04x', [Vid, Pid]));
15
16
  DevInfo := SetupDiGetClassDevsA(@USB_CLASS, nil, 0, DIGCF_DEVICEINTERFACE or DIGCF_PRESENT);
17
18
  if DevInfo <> HDEVINFO(INVALID_HANDLE_VALUE) then begin
19
    DevIntfData.cbSize := SizeOf(DevIntfData);
20
    MemberIdx := 0;
21
22
    repeat
23
      SetupDiEnumDeviceInterfaces(DevInfo, nil, USB_CLASS, MemberIdx, DevIntfData);
24
      Err := GetLastError;
25
      if Err <> 0 then
26
        break;
27
28
      // Get the required buffer size. Call SetupDiGetDeviceInterfaceDetail with
29
      // a NULL DevIntfDetailData pointer, a DevIntfDetailDataSize
30
      // of zero, and a valid RequiredSize variable. In response to such a call,
31
      // this function returns the required buffer size at dwSize.
32
      DevData.cbSize := SizeOf(DevData);
33
      DevIntfData.cbSize := SizeOf(DevIntfData);
34
      SetupDiGetDeviceInterfaceDetailA(DevInfo, @DevIntfData, nil, 0, Size, @DevData);
35
36
      // Allocate memory for the DeviceInterfaceDetail struct
37
      // and call it again.
38
      DevIntfDetailData := GetMem(Size);
39
40
      // there seems to be a strange bug in windows, some versions of windows
41
      // want the size of the pointer to the structure, some of them want the
42
      // size of the struct. Try it both ways, one of them will succeed.
43
      DevIntfDetailData^.cbSize := SizeOf(PSPDeviceInterfaceDetailDataA);
44
      Res := SetupDiGetDeviceInterfaceDetailA(DevInfo, @DevIntfData, DevIntfDetailData, Size, Size, @DevData);
45
      if not res then begin
46
        DevIntfDetailData^.cbSize := SizeOf(TSPDeviceInterfaceDetailDataA);
47
        Res := SetupDiGetDeviceInterfaceDetailA(DevInfo, @DevIntfData, DevIntfDetailData, Size, Size, @DevData);
48
      end;
49
50
      if Res then begin
51
        Path := PChar(@DevIntfDetailData^.DevicePath[0]);
52
        if Pos(Search, Path) > 0 then begin
53
          List.Append(Path);
54
        end;
55
      end;
56
57
      Freemem(DevIntfDetailData);
58
      Inc(MemberIdx);
59
    until False;
60
  end;
61
end;
62
63
procedure TLazHid.Open(Path: String);
64
var
65
  H: THANDLE;
66
begin
67
  H := CreateFileA(PChar(Path), GENERIC_READ or GENERIC_WRITE,
68
    FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0);
69
  if H <> INVALID_HANDLE_VALUE then begin
70
    FDevice := H;
71
    StartThreads;
72
  end;
73
end;
74
75
procedure TLazHid.Close;
76
var
77
  P: ^LongInt;
78
  D: Cardinal;
79
begin
80
  P := @FDevice;
81
  D := InterLockedExchange(P^, 0);
82
  if D <> 0 then begin
83
    CancelIoEx(D, nil);
84
    CloseHandle(D);
85
  end;
86
end;
87
88
procedure TLazHid.DeviceWriteReport(Data: TBytes);
89
var
90
  Len: Integer;
91
  SendBuf: array [0..64] of Byte; // 65 byte, not 64!
92
begin
93
  Len := Length(Data);
94
  if Len > 64 then
95
    Len := 64;
96
  Move(Data[0], {%H-}SendBuf[1], Len);
97
  SendBuf[0] := 0; // report ID
98
99
  while not DriverReadWriteMutex.TryEnter do
100
    CancelIoEx(FDevice, nil);
101
  FileWrite(FDevice, SendBuf, SizeOf(SendBuf));
102
  DriverReadWriteMutex.Release;
103
end;
104
105
function TLazHid.DeviceReadReport: TBytes;
106
var
107
  Buf: array[0..64] of Byte; // 65 bytes, not 64!
108
  Len: Integer;
109
  Err: DWORD;
110
begin
111
  // Windows will prepend a report ID to the Data,
112
  // so instead of 64 Byte we must provide a buffer
113
  // of 65 byte to the FileRead function.
114
115
  DriverReadWriteMutex.Acquire;
116
  Len := FileRead(FDevice, Buf, SizeOf(Buf));
117
  DriverReadWriteMutex.Release;
118
  Err := GetLastError;
119
  if Len < 0 then begin
120
    SetLength(Result, 0);
121
    if Err <> ERROR_OPERATION_ABORTED then begin
122
      Close;
123
    end;
124
  end
125
  else begin
126
    // ignore the report ID and use last
127
    // 64 bytes of buffer as the actual
128
    // report data
129
    Dec(Len);
130
    SetLength(Result, Len);
131
    Move(Buf[1], Result[0], Len);
132
  end;
133
end;

von Backspace (Gast)


Lesenswert?

testfall schrieb:
> C#? Das ist C in etwas billiger mit framework und gratis IDE vom
> Fensterbauer.
>
> Sonst halt Borland Delphi. Aber das hängt stark an den Fähigkeiten mit
> PASCAL.
>
> Sonst halt C. Aber das hängt stark am Umgang mit den GUI Dingern.
>
> Sonst halt Fortran. Aber das hängt stark an Fortran.
>
> Interessant wäre, was du schon kannst.

Danke testfall!
Ich habe Erfahrung in C nur auf µC-Ebene.
Sonst Java und vor allem Pascal. Aber gibt es in Lazarus oder Delphi 
eine einfache USB-HID-Komponente? Ich will ja das Rad nicht neu erfinden 
und zum Wrapper-König mutieren, sondern nur möglichst schnell ein 
simples Ergebnis bekommen.
In Delphi wäre zumindest die Generierung einer single-exe Date gegeben 
aber USB-technisch kenne ich da nichts.
Ich setze mich auch gerne mit neuen Sprachen auseinander.

von Thomas S. (thschl)


Lesenswert?

Backspace schrieb:
> Aber gibt es in Lazarus oder Delphi
> eine einfache USB-HID-Komponente? Ich will ja das Rad nicht neu erfinden
> und zum Wrapper-König mutieren, sondern nur möglichst schnell ein
> simples Ergebnis bekommen.

Ich benutze unter Delphi die Jedi Componenten, dort ist HID auch drinne

von Backspace (Gast)


Lesenswert?

Bernd K. schrieb:
> Hier sind ein paar Pascal Codeschnipsel von mir:

Danke Bernd!
Dann nützt du offensichtlich eine Jedi-Komponente.
Das schaue ich mir an!

von Backspace (Gast)


Lesenswert?

Thomas S. schrieb:
> Ich benutze unter Delphi die Jedi Componenten, dort ist HID auch drinne

Danke auch Dir!

von Bernd K. (prof7bit)


Angehängte Dateien:

Lesenswert?

Backspace schrieb:
> Bernd K. schrieb:
>> Hier sind ein paar Pascal Codeschnipsel von mir:
>
> Danke Bernd!
> Dann nützt du offensichtlich eine Jedi-Komponente.
> Das schaue ich mir an!

Ich hab mir die paar API-Funktionen die nötig sind aus dem Jedi-API 
extrahiert in ne eigene Unit, siehe Anhang (vier Funktionen der 
SetupAPI.dll und ein kleiner Stall voll zugehöriger Typdeklarationen).

Dieser Zinnober wird nur benötigt um die Geräte aufzuzählen und das 
Gerät das einen interessiert rauszufiltern (meine Enumerate-methode), 
sobald Du den Dateinamen hast gehts ganz normal mit FileOpen, FileRead 
und FileWrite weiter.

Nur eins muß man beachten: Der gererische Windows HID-Treiber hat einen 
Bug: Wenn ein Thread blockierend in der FileRead hängt darf ein anderer 
Thread nicht FileWrite aufrufen. Deshalb der Tanz mit dem Mutex und 
CancelIoEx.

: Bearbeitet durch User
von Backspace (Gast)


Lesenswert?

Bernd K. schrieb:
> Ich hab mir die paar API-Funktionen die nötig sind aus dem Jedi-API
> extrahiert in ne eigene Unit, siehe Anhang (vier Funktionen der
> SetupAPI.dll und ein kleiner Stall voll zugehöriger Typdeklarationen).

Das ist interessant!
Offenbar bist Du da sehr versiert. Das hilft mir in jedem Fall.
Vermutlich werde ich mich dann doch wieder mit Delphi beschäftigen.

von Backspace (Gast)


Lesenswert?

Bernd K. schrieb:
> Dieser Zinnober wird nur benötigt um die Geräte aufzuzählen und das
> Gerät das einen interessiert rauszufiltern (meine Enumerate-methode),
> sobald Du den Dateinamen hast gehts ganz normal mit FileOpen, FileRead
> und FileWrite weiter.
>
> Nur eins muß man beachten: Der gererische Windows HID-Treiber hat einen
> Bug: Wenn ein Thread blockierend in der FileRead hängt darf ein anderer
> Thread nicht FileWrite aufrufen. Deshalb der Tanz mit dem Mutex und
> CancelIoEx.

O.k. habe ich im Prinzip verstanden.
Coole Infos und mein Respekt!
Vielen Dank Bernd!

von c-hater (Gast)


Lesenswert?

Backspace schrieb:

> Kann hier jemand eine solche Sprache empfehlen, mit der er ohne
> Schwierigkeiten eine Windows-GUI-Anwendung mit USB-HID-Kommunikation
> programmiert hat?

VB.net
C#
Delphi/OP
Lazarus/FP
C
C++

In allen diesen Sprachen habe ich das schon getan. Ich befürchte nur: 
diese Auskunft hilft dir kein bissel weiter, denn das Kernproblem ist 
ganz offensichtlich nicht die Programmiersprache...

von Backspace (Gast)


Lesenswert?

c-hater schrieb:
> Ich befürchte nur:
> diese Auskunft hilft dir kein bissel weiter, denn das Kernproblem ist
> ganz offensichtlich nicht die Programmiersprache...

Du hast Recht. Deine Aussage hilft mir null, da diese mit "geht mit 
allen Programmiersprachen" gleichzusetzen ist und offenbar nur der 
eigenen Beiweihräucherung dient.
Ich habe das bislang mit Java gemacht und will aus mehreren Gründen 
wechseln.
Dir zu Liebe würde ich ja eine C-Sprache nehmen - aber befasse mich nun 
doch nochmal mit Delphi oder auch Lazarus, nachdem es auch sinnvolle 
Beiträge hier gab.

von Arc N. (arc)


Lesenswert?

Backspace schrieb:
> Dir zu Liebe würde ich ja eine C-Sprache nehmen - aber befasse mich nun
> doch nochmal mit Delphi oder auch Lazarus, nachdem es auch sinnvolle
> Beiträge hier gab.

C++Builder in der Community Edition. C++ inkl. aller Libs (fast, ab der 
10.3 unterstützen einige der mitgelieferten Compiler C++17 andere aber 
nur C++11) und alle Delphi-Komponenten lassen sich verwenden.

von Backspace (Gast)


Lesenswert?

Arc N. schrieb:
> C++Builder in der Community Edition. C++ inkl. aller Libs (fast, ab der
> 10.3 unterstützen einige der mitgelieferten Compiler C++17 andere aber
> nur C++11) und alle Delphi-Komponenten lassen sich verwenden.

Auch interessant aber warum sollte ich dann den Umweg über C++ machen?
Gerade für C und Konsorten hätte ich gedacht, dass es nur so wimmelt von 
USB-Komponenten.
Ich werde am WE mal den Weg von Bernd K. mit Lazarus ausprobieren.

von Backspace (Gast)


Lesenswert?

Bernd K. schrieb:
> Hier sind ein paar Pascal Codeschnipsel von mir:

Sorry, ich habe da nochmal eine dumme Frage:

Ich habe Deine Fragmente mal so in Lazarus eingesetzt und natürlich 
fehlt da noch das ein oder andere.

Woher z.B. hast Du die Konstantenwerte wie z.B. "USB_CLASS".

Finde ich diese in den Jedi-Komponenten?
Dankeschön vorab für eine kurze Erleuchtung!

von Bernd K. (prof7bit)


Lesenswert?

Backspace schrieb:
> Bernd K. schrieb:
>> Hier sind ein paar Pascal Codeschnipsel von mir:
>
> Sorry, ich habe da nochmal eine dumme Frage:
>
> Ich habe Deine Fragmente mal so in Lazarus eingesetzt und natürlich
> fehlt da noch das ein oder andere.
>
> Woher z.B. hast Du die Konstantenwerte wie z.B. "USB_CLASS".
>
> Finde ich diese in den Jedi-Komponenten?
> Dankeschön vorab für eine kurze Erleuchtung!
1
const
2
  USB_CLASS: TGUID = '{4D1E55B2-F16F-11CF-88CB-001111000030}';

Das ist die Klasse für HID-Geräte.

Sorry, die Code-Schnipsel sind aus nem größeren Projekt, die Komponente 
aus der ich die Methoden gepostet habe abstrahiert ein proprietäres 
HID-Gerät mit allerlei Spezialfunktionen, deshalb wollte ich nur das 
Zeug posten das allgemeingültig ist und nicht die ganze Unit. Wenn noch 
mehr fehlt, sage bitte Bescheid, ich reiche es nach.

von Backspace (Gast)


Lesenswert?

Bernd K. schrieb:
> Wenn noch
> mehr fehlt, sage bitte Bescheid, ich reiche es nach.

Genial Meister!

Wenn ich dann so unverschämt sein darf, es fehlen mir noch die 
Definitionen für:

"FDevice"
"StartThreads"
"DriverReadWriteMutex"

Danke :)

von M. K. (kichi)


Lesenswert?


von Bernd K. (prof7bit)


Lesenswert?

Backspace schrieb:
> Bernd K. schrieb:
>> Wenn noch
>> mehr fehlt, sage bitte Bescheid, ich reiche es nach.
>
> Genial Meister!
>
> Wenn ich dann so unverschämt sein darf, es fehlen mir noch die
> Definitionen für:
>
> "FDevice"
> "StartThreads"
> "DriverReadWriteMutex"
>
> Danke :)
1
uses
2
  syncobjs;

1
  FDevice: THandle;
2
  DriverReadWriteMutex: TCriticalSection;

1
constructor TLazHid.Create(AOwner: TComponent);
2
begin
3
  inherited Create(AOwner);
4
  DriverReadWriteMutex := TCriticalSection.Create;
5
6
  // noch mehr

1
destructor TLazHid.Destroy;
2
begin
3
  // einiges andere
4
5
  DriverReadWriteMutex.Free;
6
  inherited Destroy;
7
end;

Und StartThreads ist nur eine Methode die ein paar Threads startet die 
den anderen Klimbim steuern, Sachen die sich mit den speziellen 
Vorgängen bei diesem einen Gerät beschäftigen die ich nicht gepostet 
habe.

Letztendlich starte ich da unter anderem einen Thread der nichts weiter 
macht als dann in einer Schleife die meiste Zeit lang in der schon 
geposteten DeviceReadReport Methode blockierend auf eingehende 
HID-Reports zu warten (und die dann zu sortieren und in verschiedene 
Queues zu stopfen) so lange bis das Handle durch irgendwas wieder 
geschlossen wurde. Und noch ein paar andere Threads die andere ganz 
spezielle Dinge tun die nur mit der speziellen Funktion dieses Gerätes 
zu tun haben.

: Bearbeitet durch User
von Backspace (Gast)


Lesenswert?

DANKE!!!

von Bernd K. (prof7bit)


Lesenswert?

Und was die 64 oder 65 Byte in DeviceReadReport angeht:

Das spezielle Gerät das ich da habe sendet und empfängt HID-Reports der 
Länge 64, das ist so in dessen Device-Descriptor festgelegt, das ist die 
Maximallänge die bei HID in einem Rutsch transferiert werden kann und 
bei dem Gerät nutze ich die voll aus.

Die FileRead() Funktion des generischen Windows HID Treibers wird NUR 
dann funktionieren wenn der übergebene Buffer genau die Länge des 
Reports plus 1 hat, man kann im Gegensatz zu Linux (libhidapi) immer nur 
den ganzen Report in einem Rutsch auslesen und es ist immer die 
Report-ID als erstes Byte vorangestellt und wen der Puffer kleiner (oder 
größer) ist kommt kommentarlos gar nichts! Da hab ich ne Weile dran 
geknabbert denn das hab ich in keiner Dokumentation gefunden (oder nicht 
richtig gesucht).

Wenn also Dein Gerät kleinere Reports verschickt und nichts kommt dann 
probier da auch mal nen entsprechend kleineren Empfangspuffer zu 
übergeben.

: Bearbeitet durch User
von Bernd K. (prof7bit)


Lesenswert?

Wenn Du übrigens ne Lösung für Linux brauchst hab ich ein 
objektorientiertes Pascal-Binding für libhidapi geschrieben:

https://github.com/prof7bit/HIDAPI.pas
Lizenz: LGPL mit static linking exception

(Dazu muss das Paket libhidapi-dev installiert sein sonst meckert der 
Linker. Auf der Zielmaschine brauchst Du nur libhidapi-libusb0 und 
natürlich eine passende udev-Regel damit der Nutzer Berechtigung für das 
Gerät bekommt)

: Bearbeitet durch User
von Backspace (Gast)


Lesenswert?

Vielen Dank Bernd!

Ich nutze ohnehin die volle Länge von 64 Byte, von daher passt das 
natürlich optimal. Auf die 65 wäre ich da natürlich nicht gekommen...

Auf Deine github-Seite bin ich auch schon gestossen, da habe ich 
offenbar den Vollprofi erwischt!
So tief möchte ich gar nicht einsteigen (auch wenn es sicherlich sehr 
lehrreich wäre).

Leider bin ich nur in der WindowsWelt zu Hause - aber sag niemals nie.

Deine Codefragmente compilieren zumindest jetzt fehlerfrei.
Ich hoffe, mich noch heute oder am WE damit beschäftigen zu können.


Die Vorgehensweise wird vermutlich sein:

1. Enumerate(Vid: Word; Pid: Word; List: TStringList);

dann bekomme ich wahrscheinlich eine Liste mit allen Devices und kann da 
die zugehörige ID meines Devices auslesen.

Mit diesem (meinem) extrahierten String rufe ich dann
2. Open(Path: String);

und sollte dann meine Reports lesen und schreiben können.

von Backspace (Gast)


Lesenswert?

Beim Lesen ist mir gleich aufgefallen, dass ich einen Käse geschrieben 
habe.

Ich rufe die Enummerierung natürlich mit meiner VID und PID auf.
Was der letzte Parameter, die Stringliste bedeutet erschließt sich mir 
noch nicht, kommt aber sicher noch.

Also wird die Reihenfolge irgendwie anders sein - aber da komme ich 
sicher drauf.

von vn nn (Gast)


Lesenswert?

Backspace schrieb:
> Auch interessant aber warum sollte ich dann den Umweg über C++ machen?

Was daran siehst du als Umweg?

von Backspace (Gast)


Lesenswert?

Naja, wenn ich Delphi Komponenten verwenden will und Delphi nutzen kann, 
warum dann einen Umweg über C++ nehmen?

> C++Builder in der Community Edition. C++ inkl. aller Libs (fast, ab der
> 10.3 unterstützen einige der mitgelieferten Compiler C++17 andere aber
> nur C++11) und alle Delphi-Komponenten lassen sich verwenden.

von Bernd K. (prof7bit)


Lesenswert?

Backspace schrieb:
> Was der letzte Parameter, die Stringliste bedeutet erschließt sich mir
> noch nicht, kommt aber sicher noch.

Du erzeugst erst eine leere TStringList Instanz und übergibst sie der 
Enumerate() Methode. Wenn die Methode dann zurückkehrt beinhaltet diese 
Liste (hoffentlich) mindestens ein Element welches der Pfadname ist den 
Du direkt für die Open() methode verwendest. Wenn sie mehr als ein 
Element enthält waren mehrere der Geräte mit VID und PID angeschlossen 
ud Du könntest sie alle einzeln öffnen oder nur das erste oder was auch 
immer. Man könnte die Enumerate-Methode theoretisch auch noch aufbohren 
so daß man nach der Seriennummer filtern kann oder evtl auch so daß sie 
alle Seriennummern passender Geräte zurückliefert aber das war bei 
meiner Anwendung nicht nötig. Sobald Du den Pfadnamen hast kannst Du die 
Stringliste wieder zerstören wenn Du sie nicht mehr anderweitig 
brauchst.

: Bearbeitet durch User
von Backspace (Gast)


Lesenswert?

Vielen Dank, dann war die erste Intention doch nicht so verkehrt.

Da freue ich mich doch schon auf meine ersten Versuche!

von Backspace (Gast)


Lesenswert?

Ich wollte nur nochmal sagen DANKE Bernd !!!

Dank Deiner genial simplen vorgekauten Lösung konnte ich in Lazarus nach 
kurzer Zeit schon Reports senden und empfangen.
Wirklich Wahnsinn, wie einfach das dank Deiner Hilfe ging!

Die Hauptschwierigkeit war dann nur, das geschundene Hirn wieder vom 
Java-Slang auf Pascal umzubiegen.

Jetzt muss ich mich noch ein wenig in der setupAPI nach den Methoden 
umschauen, um die Seriennummer, den manufacturer und product string und 
was mich sonst noch so interessiert, zu extrahieren.

Ich trinke heute Abend ein Bier auf Dich!

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.