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!
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
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.
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?
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; |
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.
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
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!
Thomas S. schrieb: > Ich benutze unter Delphi die Jedi Componenten, dort ist HID auch drinne Danke auch Dir!
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
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.
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!
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...
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.
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.
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.
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!
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.
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 :)
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
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
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
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.
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.
Backspace schrieb: > Auch interessant aber warum sollte ich dann den Umweg über C++ machen? Was daran siehst du als Umweg?
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.
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
Vielen Dank, dann war die erste Intention doch nicht so verkehrt. Da freue ich mich doch schon auf meine ersten Versuche!
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.