PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Tastaturabfrage in C++



hirnfrei
23.01.2016, 00:13
Mahlzeit!

Jetzt mal was wahrscheinlich banales!

Das Problem hatte ich bislang nicht, da ich normalerweise wenn dann eher mit GUIs arbeite und es da "von selbst" funktioniert und die Arduinos machen das ja auch von Hause aus.

Ich möchte das meine Anwendung im Terminal läuft, also keine GUI verwendet. Das ist soweit auch kein Problem. Jedoch soll das Programm durchgehend laufen. Genau wie beim Arduino die loop() Schleife. Das Programm soll also in dieser Schleife vor sich hin laufen und eventuell, je nachdem was das Programm mal an "Reizen" bekommt, auch auf diese reagieren können. Es soll aber auch reagieren wenn ich etwas eingebe.

Wie realisiere ich das, ohne das die gedrückte Taste dann auch dumm in den Terminal geschrieben wird?

Mxt
23.01.2016, 07:59
Hallo,

so ganz banal ist das nicht. Ich denke die Standard C++ Eingabemittel wie cin oder getline() sind schon so gemacht, dass man auch sieht, was man schreibt. Das ist mir in den meisten Fällen auch recht so. Dinge die durchgehenend laufen, packt man normalerweise in einen std::thread, das Hauptprogramm kann dann ruhig blockierend z.B. in einem getline stecken und auf Eingabe waren.

Tastaturabfrage ohne dass das Zeichen erscheint, geht wahrscheinlich nur betriebssystemabhängig. Eventuell bieten auch Bibliotheken wie Open Frameworks solche Anwendungsarten, da kenne ich mich aber nicht aus.

Peter(TOO)
23.01.2016, 08:36
Hallo,

Wie realisiere ich das, ohne das die gedrückte Taste dann auch dumm in den Terminal geschrieben wird?

Mit kbhit() kannst du abfragen ob ein Zeichen eingegeben worden ist. Dann kannst du dieses Zeichen z.B. mit getch() einlesen.

getch() wartet bis ein Zeichen vorhanden ist und liefert es dann zurück.

MfG Peter(TOO)

Mxt
23.01.2016, 08:48
Ich war nicht wirklich sicher, ob es sich um Linux handelt. Unter Windows sind die als veraltet markiert (nicht Unicode fähig).

https://msdn.microsoft.com/de-de/library/ms235446.aspx

HaWe
23.01.2016, 09:09
für welche Programmierplattform?
auf Windows-Plattformen gibt es (normalerweise ?) für kbhit() und getch() <conio.h>, das gibt es aber nicht auf Linux-Systemen. (Für Linux (Raspbian/Debian) benutze ich einen selbstgestrickten Ersatz, der halbwegs genauso funktioniert - wenn dich das interessiert...? )

i_make_it
23.01.2016, 09:48
Zuerst sollte das OS und der C++ Dialekt bekannt sein um da was sagen zu können.
Wenn man ganz in die Hardware rein geht liest man den Tastaturpuffer direkt aus.
Etwas weiter oben ist das auslesen der Tastatur Scancodes mit den Keystroke Messages.
Bei Windows wäre das WM_KEYDOWN aus der Win32.dll.
Man greift also direkt auf Betriebssystemfunktionen zu und nutzt keine darauf aufbauenden Befehle der Programmiersprache.
Wenn man sich damit näher befassen will, sollte man sich Infos zur API Programmierung des jeweiligen OS ansehen.

Für Linux schau mal hier:
http://forums.codeguru.com/showthread.php?468159-ReadConsoleInput-virtual-and-control-keys

Für Windows:
https://msdn.microsoft.com/de-de/library/windows/desktop/ms646280%28v=vs.85%29.aspx

Scancodes:
https://msdn.microsoft.com/en-us/library/aa299374%28v=vs.60%29.aspx

HaWe
23.01.2016, 09:51
den Code-Guru Code habe ich auf Raspbian Jessie probiert, funktioniert aber nicht.
Meiner aber schon. ;)
Aber, wie du ganz richtig schriebst, lass erst den TO mal antworten, welches OS er benutzt.
Ohne ihn dann doch vorher wieder mit Links zu überschütten. ;)

Mxt
23.01.2016, 10:00
Zuerst sollte das OS und der C++ Dialekt bekannt sein um da was sagen zu können.

Ja, deswegen bin ich da auch vorsichtig.



Für Windows:
https://msdn.microsoft.com/de-de/library/windows/desktop/ms646280%28v=vs.85%29.aspx

Oh oh, vorsicht. Nicht einfach nur suchen und verlinken.

Eine WM_.... ist eine Window Message.

Posted to the window with the keyboard focus ...
das geht nur in GUI-Anwendungen mit einer Messageloop. Er hat aber ein Konsolenfenster (wenn er auf Windows ist).

Ein anderes Problem unter Windows wäre, getch (bzw. _getch) liefern ein char, der Standardzeichentyp bei (halbwegs) aktuellen Windows Entwicklungsumgebungen ist aber wchar_t (UTF-16), das kann dann Probleme bei der Weiterverarbeitung machen.

hirnfrei
23.01.2016, 10:54
Ich bin manchmal echt dämlich!

Ich arbeite derzeit auf 64 Bit Gentoo Linux. Laufen soll das letzten Endes aber auf einem Raspi.

Sind ja schon interessante Ideen dabei. Das mit Threat klingt interessant.

Danke schonmal.

Mxt
23.01.2016, 11:30
Das mit Threat klingt interessant.


Ich hab mal auf die schnelle was zusammengeschrieben, ist jetzt aber mit Visual Studio 2015:


// WorkerThread.cpp : Definiert den Einstiegspunkt für die Konsolenanwendung.
//

#include "stdafx.h"

std::mutex g_mutex;

void console_out(const std::string& text)
{
std::lock_guard<std::mutex> lock(g_mutex);

std::cout << text << std::endl;
}

class Worker
{
private:
bool m_Run;
int m_Counter;

public:

Worker() : m_Run(true), m_Counter(0)
{}

void Stop()
{
m_Run = false;
}

void Reset()
{
m_Counter = 0;
}

void operator()()
{
while (m_Run) // Hier ist die "Loop" !
{
console_out(std::to_string(m_Counter++));

std::this_thread::sleep_for(std::chrono::milliseco nds(1000));
}
}
};

int main()
{
Worker worker;

std::thread t(std::ref(worker));

for (;;)
{
std::string input;
std::getline(std::cin, input);

if (input == "exit")
{
worker.Stop();
break;
}
else if (input == "reset")
{
worker.Reset();
}
else
{
console_out("unknown command");
}
}

if (t.joinable())
{
t.join();
}

return 0;
}



mit



// stdafx.h: Includedatei für Standardsystem-Includedateien
// oder häufig verwendete projektspezifische Includedateien,
// die nur in unregelmäßigen Abständen geändert werden.
//

#pragma once

#include "targetver.h"

#include <tchar.h>

// TODO: Hier auf zusätzliche Header, die das Programm erfordert, verweisen.
#include <string>
#include <mutex>
#include <thread>
#include <iostream>

HaWe
23.01.2016, 12:36
gerade auf dem Raspi (Raspbian Jessie) hab ich einiges (!!!) an source codes durchgetestet, bis was funktionierendes dabei war.
Letztlich funktioniert hat dieses hier:


/*
* rpiconio.h
* mimics kbhit(), getch()
*/

#ifndef RPICONIO_H
#define RPICONIO_H

#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <termio.h>
#include <unistd.h>

bool kbhit(void) {
struct termios original;
tcgetattr(STDIN_FILENO, &original);
struct termios term;
memcpy(&term, &original, sizeof(term));
term.c_lflag &= ~ICANON;
tcsetattr(STDIN_FILENO, TCSANOW, &term);
int characters_buffered = 0;
ioctl(STDIN_FILENO, FIONREAD, &characters_buffered);
tcsetattr(STDIN_FILENO, TCSANOW, &original);
bool pressed = (characters_buffered != 0);
return pressed;
}

void echoOff(void) {
struct termios term;
tcgetattr(STDIN_FILENO, &term);
term.c_lflag &= ~ECHO;
tcsetattr(STDIN_FILENO, TCSANOW, &term);
}

void echoOn(void) {
struct termios term;
tcgetattr(STDIN_FILENO, &term);
term.c_lflag |= ECHO;
tcsetattr(STDIN_FILENO, TCSANOW, &term);
}


#endif

Testcode:


#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <wiringPi.h>

#include "rpiconio.h"

int main(void)
{
long i=0;
echoOff();
int c = '\0';
while (c != 'q') {
if (kbhit())
{
c = getchar();
printf("(hier ggf. auskommentieren:) got key \'%c\'\n", c);
if(c==27) { printf("\nuser break\n"); return 1; }
}
printf("%ld\n", ++i);
delay(100);
}
echoOn();
return 0;
}

hirnfrei
23.01.2016, 19:15
Das mit Thread scheint ja ganz gut zu funktionieren und gefällt mir prinzipiell auch ganz gut.

Nur kann man von dem einen Thread auch etwas in einen anderen schreiben?

Mxt
24.01.2016, 08:17
Ja, im Prinzip schon.

Dabei muss man zwei Dinge beachten. Einmal müssen die Daten natürlich sichtbar sein. Globale Variablen sind ja bei größeren Programmen eher verpöhnt. Man kann aber z.B. sowas machen


class Dings
{
// irgendwelche gemeinsamen Daten, die auch noch andere verwenden
};

class Worker
{
// ...

explicit Worker(Dings ding) // ob man hier kopiert oder mit Referenz oder (Smart-)Pointer arbeitet hängt von der Anwendung ab
// Das explicit sagt: Ein Worker kriegt ein Ding. Ein Ding ist aber kein Worker.
// Gegenbeispiel: Bruchzahl(int i) Ein int ist auch eine Bruchzahl
// (Das ist nur bei Konstruktoren mit einem Parameter relevant)
// ...


int main()
// ...

Dings zeug; // Wird auch noch von anderen benutzt
Worker worker(zeug);

std::thread t(std::ref(worker)); // std::thread legt eine Kopie seiner Parameter an,
// das std::ref packt worker in einen reference_wrapper
// dadurch arbeitet der Thread auf dem selben Objekt wie main


Dann ist da noch das Problem, dass mehrere Prozessorkerne auf die selben Daten lesend oder schreibend zugreifen wollen.
Bei bool oder int ist das kein Problem
https://de.wikipedia.org/wiki/Cache-Koh%C3%A4renz

Schon bei einem string würde es knallen. Deshalb auch diese Funktion zur Consolenausgabe mit dem lock_guard.

Das alles zu erklären, würde etwas lang werden. Hier mal was es zu threads im Standard gibt
http://en.cppreference.com/w/cpp/thread
außerdem gibt es noch atomare Werte, die nur am Stück geändert werden können
http://en.cppreference.com/w/cpp/atomic

hirnfrei
24.01.2016, 17:25
Danke. Bisher funktioniert das ganz gut muss ich sagen!

HaWe
24.01.2016, 17:44
wenn was "mal ne Zeitlang" gutgeht, heißt das nicht, dass es immer so sein wird. Meist gehen solche Sachen genau dann schief, wenn sie den größtmöglichen Schaden anrichten (acc to Murphy's Law (2) ).
Ganz persönlich finde ich: auf Multi-User-Multi-Tasking-OS in mehreren Tasks auf dieselben Ressourcen zuzugreifen ohne Mutexe zu benutzen - DAS finde ICH ziemlich "hirnfrei" ;)

Mxt
25.01.2016, 07:08
ohne Mutexe zu benutzen
Was immer diese Antwort bedeuten mag. Ein std::mutex ist ein solcher.

Bei int oder bool kann man noch volatile davorschreiben, muss man aber bei den meisten Compilern nicht.

Atomic ist in der Tat lock free programming und basiert auf memory order. Wenn man nicht gerade Hochfrequenz Börsenhandel (mittlerweile eine der größten C++ Anwendungen) betreibt, sollte man da besser als Anfänger von Abstand nehmen.

hirnfrei
25.01.2016, 18:48
Ich sprach auch nicht davon das es "eine Zeit lang" funktioniert, sondern das es bisher funktioniert. Ich also alles, was das Programm bisher kann, auch schon mehrfach getestet habe und es funktioniert. Wie es sich verhält wenn das Programm wächst, bzw. mehr Daten anfallen wird sich zeigen. Aber auch da habe ich vor auf der sicheren Seite zu bleiben.