IODevice

Aus DGL Wiki
Wechseln zu: Navigation, Suche

Allgmein

Jede High-level Software benötigt Ein- und Ausgabe Möglichkeiten, um mit dem User zu kommunizieren. Solche Geräte nennt man in der Fachsprache IO devices und unterliegen den Unterschiedlichsten Eigenschaften. Das ansprechen von solchen Devices wird über eine Treiber realisiert, das Ansprechen des Treibers wird oft über eine Bibliothek vereinfacht. Die Realisierung solcher Bibliotheken wird über 2 verschiedene Arten ermöglicht, blocking und none blocking. Windows und Linux verarbeiten Tastatur und Maus Interrupts und generieren daraus die Fensterbotschaften, wie z.B. Fenster-aktiviert, Fenster-verschoben, Taste-gedrückt, Maustaste-gedrückt und Maus-bewegt. Diese Botschaften werden in den Speicherbereich, des Hauptprozesses, hinterlegt und können dann abgerufen werden. Diese Unterschiedlichen Umsetzungen benötigen auch unterschiedliche Herangehensweisen bei der Klassen konzipieren.

Blocking mode

Blocking bedeutet zu Deutsch blockierend und wenn man mit dem Gerät kommunizieren will, dann wird der ganze Programmablauf angehalten. Neben den blocking devices gibt es noch none blocking devices, welche mit einem device kommunizieren können, ohne dabei das ganze Programm zum stehen zu bringen. Wieso aber gibt es ein blocking mode und wann tritt dieser auf, bzw. wann nicht ? Folgendes Beispiel sollte dies sehr klar zeigen. Eine Applikation soll ein Paket über die Netzwerkkarte an ein anderen PC senden. Dafür wird der Treiber den OSI-Stack runter arbeiten und unverzüglich die Daten senden. Wenn das Paket über UDP Protokoll versendet wurde, dann hat der Sendebefehl nicht blockiert und der Programmfluss ging weiter. War das Paket über TCP over IP gesendet, dann ist aufgrund der Protokollgeschaffenheit, eine Bestätigung des Empfangs notwendig. Dieser Empfang bedeutet, dass solange gewartet wird, bis die Netzwerkkarte meldet, dass diese angekommen ist oder auch nicht. Hierbei ist ein blockierender Befehl entstanden, da das Programm nicht weiter arbeiten kann, bis die Antwort da ist. Diese Antwort wird im Treiber über ein Interrupt realisiert, welcher der CPU sagt, ich habe hier was, du kannst weiter arbeiten. Durch ein Prozess-Scheduler werden die anderen Prozesse nicht beeinfluss und laufen weiter aber unser Prozess wird angehalten, bis dies geschehen ist. Ungünstig ist es, wenn das zu sendende Paket Logisch erreichbar ist aber nicht Physikalisch, denn dann wird solange gewartet, bis die Definierte Wartezeit rum ist. Also das lesen blockiert unseren Prozess, da wir auf Daten warten. Um nicht die ganze Applikation zum Stillstand zu bringen, gibt es 2 Möglichkeiten zur Umgehung. Die erste Möglichkeit wäre das Daten polling, dabei wurde vom Treiber ein Speicherbereich reserviert, in dem Daten abgelegt werden und man auf diese zugreifen kann. Dies kann im Fall von Netzwerkkommunikation aber zu Datenverlust führen, da ein Altes Paket, welches wir wollten von ein neuen Überschieben werden kann. Bei Devices, welche wenigen Änderungen unterliegen wäre diese Art Sinnvoll, da unser Programmfluss nicht unterbrochen wird. Diese Art von Datenaustausch findet man z.B. in der Messageverarbeitung von Windows und Linux Fenstern. Eine bessere Leistung erhält man aber durch ein Thread, dieser erzeugt ein weiteren Prozess und ordnet diesen unserem Programmprozess unter. Wenn dieser Unterprozess nun den Lesebefehl ausführt, dann wird nur dieser blockiert und der Hauptprozess kann weiter arbeiten. So kann jedes Netzwerk-Paket verarbeitet werden und das Programm muss nicht auf ein Netzwerkpaket warten, um ein folgendes Maus-,Tastatur- oder Fensterevent zu verarbeiten.

Device Manager

Der Device Manager, verwaltet Ressourcen, in diesem Fall einzelne Geräte. Dem entsprechend lohnt es sich für diesen Manager das Singleton und Facotory Muster an zu wenden. Eine Mögliche Umsetzung sieht wie folgt aus.

DeviceManagerUML.png

m_DeviceList ist eine Liste von bereits erstellten Devices und wird von den Create,Destroy und der Update Methode benötigt. m_DeviceIDs ist eine Liste von ID und Klassenkonstruktor, wobei der Klassenkonstruktor je nach Sprache unterschiedlich implementiert werden muss. Bei Objekt Pascal kann man die Klasse class verwenden, indem man dann von dieser die Create Methode aufruft. Wenn man C++ benutzt dann kann man den DeviceManager als Template Implementieren und dann für Class einfach T einsetzen und im Template T als ClassType definieren. So würde dann "DeviceManager<MyDevice>::RegisterDevice(MyDeviceID);" verwenden, um ein Device zu registrieren oder man benutzt ein Callback. Dann würde Class durch z.B. "Device* (*Callback)()" ersetzt werden und schreibt entweder für jedes Device eine extra Funktion oder erstellt ein Template "template <typename T> T* CreateCallback(){ return new T; }" und nutzt dies durch "DeviceManager::RegisterDevice(MyDeviceID,CreateCallback<MyDevice>);". CreateDevice, DestroyDevice und Update können durch die m_DeviceList ob das Device existiert und entsprechend reagieren. Update ruft von jedem eingetragendem Device die Update Methode auf. Sollte es Notwendig sein, dass man ein Tutorial oder Präsentation mit der Software realisieren muss, dann kann man im DeviceManager noch eine SendMessage Methode einbauen, welche dann die GeräteID, ein Datenpointer und eventuell ein Namen enthält. Der Name wäre denn der eindeutige Devicename, wenn man eine von mehreren Geräten des gleichen Typs ansprechen möchte. Dem gefundendem Zielgerät würde man dann den Datenpointer übergeben und dieser kann dann die Entsprechende Reaktion realisieren, z.B. ein OnKeyDown Event auslösen.

Devices

Das einzelne Device sollte push und polling ermöglichen, dies bedeutet, dass ein device Callbacks wie z.B. OnKeyDown oder OnResize und Methoden wie IsKeyDown oder IsMouseButtonDown. Ein Callback ruft eine externe Funktion oder Methode auf, wenn ein Event auftritt und die polling Methoden ermöglichen das aufrufen von einem Aktuellen Status. Es macht durchaus Sinn eine Update Funktion ein zu führe, damit der Hauptprozess von sich aus ein Update der Devices triggern kann. Wenn die Device-Klasse direkt an ein device angeschlossen ist, dann wird dieses sehr Wahrscheinlich über ein Thread implementiert, wenn die Antwortzeit länger dauern kann. Dies kann z.B. bei Netzwerk der Fall sein. Ist die Antwortzeit recht gering, wie z.B. bei Tastatur, Maus, Joystick oder Gamepad, dann werden diese in der Regel über dem Hauptprozess aktualisiert, statt eines weiteren Unterprozesses. Ein Threadbasierte Device-Klasse würde dann z.B. die Update Methode leer lassen und somit garnicht auf den Aufruf reagieren. Die Implementierung sollte also das Observer Muster benutzen, um Fremde Klassen zu benachrichtigen und das Proxy Muster, um ein Einheitliche Basis an Methoden nach Aussen zu bieten. Eine Mögliche Umsetzung sieht wie folgt aus.

DeviceUML.png

Device ist eine abstrakte Basisklasse, wovon weitere Deviceklassen abgeleitet werden können. GenericKeyboard ist eine Beispiel-Klasse, welche ein Allgemeines Keyboard steuern soll. Jedes Device hat eine ID, welches den Typ des Device angibt, z.B. GenericKeyboard oder WiiMote, der Name enthält den Devicenamen, z.B. mice0 oder mice1 und Description eine Beschreibung, z.B. "Microsoft-3Button Mouse" oder "Realtek Ethernet card". So kann man mehrere Instanzen eines Devicetyps haben und diese trotzdem unterscheiden. Das Observer Muster, besagt, dass man sich bei einem Objekt registrieren kann und Information erhält, wenn dieses etwas bestimmtes getan hat. Dies wird realisiert, indem die GenericKeyboard klasse 2 Listen mit bestimmten Callback erhalten hat und Register sowie Unregister Funktionen für diese. Sollten nun mehrere Objekte abhängig von Tastatureingaben sein, dann ist dies kein Problem. Der 2. Konstruktor von GenericKeyboard ist notwendig, wenn man z.B. das Device aus einem Plugin lädt und somit der überschneidung von doppelt belegten IDs aus dem Weg gehen will. So kann man dann einfach 0 als AID übergeben und erhält dann eine High-ID aus einen fest Definierten Bereich z.B. 0000FFFF-FFFFFFFF. Ein weiterer Vorteil ist die Kombination von ID,Name und Description, so kann man z.B. Shortcutbelegungen für bestimmte Geräte festlegen und speichern. Wenn man ein Spiel entwickelt, wo 2 WiiMotes benötigt werden, dann kann die Tastenbelegung für jede WiiMote einzeln fest gelegt werden.