Hallo,
ich möchte eine Treiberarchitektur entwickeln, bei der eine generische
(hardwareunabhängige) Basisklasse den Großteil der Funktionalität
implementiert und einer hardwarespezifische Klasse ein paar wenige
Funktionen beisteuert.
Die generische Klasse soll im Wesentlichen nur ein paar Funktionsaufrufe
auf die spezialisierte tätigen, wie auch die spezialisierte ein paar
Funktionen der generischen nutzen wird.
Die Programmiersprache ist C++, der Compiler G++ und die Architekturen
verschiedene.
Mein erster Gedanke war nun, die generische Klasse durch Vererbung von
der hardwarespezifischen erweitern zu lassen. Dazu müsste ich ein paar
virtuelle Methoden deklarieren, die dann von der hardwarespezifischen zu
erweitern sind, etwa so:
1
classDriver{
2
// ...
3
voiddoSomething(){
4
// ...
5
doSpecific();
6
// ...
7
}
8
virtualvoiddoSpecific()=0;
9
// ...
10
};
11
12
classATMega32Driver:publicDriver{
13
// ...
14
virtualvoiddoSpecific(){
15
// ...
16
}
17
};
Mit virtuellen Methoden möchte ich aber architekturbedingt sparsam
umgehen, also kam mir als zweiter Gedanke, Templates einzusetzen.
1
classATMega32Driver{
2
voiddoSpecific(){
3
// ...
4
}
5
};
6
7
8
template<typenameHardwareType>
9
classDriver{
10
HardwareTypedevice;
11
12
voiddoSomething(){
13
// ...
14
device.doSpecific();
15
// ...
16
}
17
};
18
19
Driver<ATMega32Driver>driver;
Dann würden die vtables wegfallen und man kann trotzdem noch genau
vorgeben, welche Methoden im spezifischen Treiber enthalten sein sollen.
Nun habe ich in der spezialisierten Klasse außerdem noch ein struct
Config. Das Layout von Config muss ich in der Klasse Driver nicht
kennen, denn dort verwende ich nur Pointer und greife nicht auf dessen
Member zu.
Allerdings kompiliert das Programm nicht, wenn ich in der Deklaration
von Driver auf Config zugreife, etwa in der Form:
1
template<typenameHardwareType>
2
classDriver{
3
HardwareTypedevice;
4
HardwareType::Config*config;
5
staticHardwareType::Config*currentConfig;
6
7
voiddoSomething(){
8
// ...
9
device.setup(config);
10
device.doSpecific();
11
// ...
12
}
13
};
Der Compiler wird meckern: "type 'HardwareType' is not derived from type
'Driver<HardwareType>'" und eben auf die Zeilen mit
HardwareType::Config verweisen.
Meine Frage lautet nun allgemein, ob es eine elegante Lösung gibt, den
hardwarespezifischen Teil vom hardwareunabhängigen zu trennen.
Vielleicht kennt jemand einen guten Artikel zu der Thematik. Ich habe
zum Beispiel diesen (http://www.hackcraft.net/cpp/templateInheritance)
gefunden, aber der ist noch ein wenig dürftig und die Quellcodebeispiele
nicht gerade hübsch.
Im Speziellen würde mich interessieren, warum ich bei der oben genannten
Templateidee zwar Methodenaufrufe auf HardwareType tätigen kann, aber
nicht keine Pointer auf dessen Membertypen (also HardwareType::Config*).
Vielen Dank für's Durchlesen.
Hallo RW,
ich setzte mich seit ca. einem Jahr mit einem ähnlichen Thema
auseinander. Im Rahmen einer Studienarbeit habe ich versuche einen
kompletten 8bit AVR µC als Klasse zu abstrahieren. Dabei besitzt diese
Klasse Unterobjekte, welche die Hardwareeinheiten darstellen u.s.w.
Ich hatte dabei einige Probleme. Beispielsweise entstehen auf diese Art
und Weise leicht Objekte mit tausenden Unterobjekten. Leider verbraucht
jedes Objekt in C++ mindestens ein Byte an Speicher, auch dann wenn es
keine Attribute sondern nur Methoden enthält. Ich habe alle diese
Probleme umschiffen können und für den AtXMega128A1 eine (noch nicht
vollständige) Klasse erstellt. Allerdings steht der Aufwand inzwischen
in keinem Verhältnis zum Nutzen und die Programmierung ist nicht
unbedingt wesentlich einfacher geworden dadurch - nur anders. Ich bin
also etwas unzufrieden mit meiner Lösung.
Vielleicht kannst du mal kurz erläutern, was dein genaues Ziel ist. Wenn
dein Ziel ähnlich meinem Ziel ist, könnte man diesbezüglich Erfahrungen
austauschen. Vielleicht habe ich deine Absichten jedoch einfach nur
Fehlinterpretiert und dann möchte ich dich mit meinen "Problemen"
natürlich nicht nerven.
Bis dann,
netb
Hallo,
nein, das ist nicht mein Ziel. Ich schreibe gerade einen SPI-Treiber für
ein kleines Betriebssystem, das auf mehreren Plattformen läuft (Reflex,
http://idun.informatik.tu-cottbus.de/reflex/). Das ganze System ist eine
einzige Klasse, welche aus vielen anderen zusammengesteckt ist. Es ist
eigentlich ziemlich effizient, aber bedarf einiger Einarbeitungszeit.
Möglicherweise handelt es sich dabei um das, was du vorhast. Aber Reflex
war eine Doktorarbeit ;-)
Bei meiner oben beschriebenen Architektur will ich einerseits
vorschreiben, welche Funktionen man in der hardwarespezifischen
Kindklasse des Treibers zu implementieren hat und von der Basisklasse
aus darauf zugreifen, andererseits mag ich nicht gleich alles mit
virtuellen Methodenaufrufen erschlagen. Vorhin bin ich auf das
"Curiously Recurring Template Pattern" gestoßen, was meinen zweiten
Ansatz von oben schön beschreibt. Es geht um statische Polymorphie in
C++ mit Hilfe von Templates. Es ist unglaublich, was alles für
abgefahrene Sachen in C++ möglich sind, ich bin ganz begeistert.
Beim Disassemblieren der Dateichen habe ich gemerkt, welchen Overhead
virtuelle Methoden erzeugen können, wenn man nicht aufpasst. Darum ist
C++ wahrscheinlich auch als ineffzient im eingebetteten Bereich
verschrien.
Ok, das ist natürlich schon etwas anderes, meine Idee war halt wirklich
nur die reine Abstraktion der Hardware in Form von Objekten. Ich bin
hellhörig geworden, als ich deine Klassenkonstruktion mit dem Template
gesehen habe, was mich etwas an meine Lösungen dazu erinnert hat.
Auf virtuelle Methoden verzichte ich auch größtenteils, da diese beim
AVR nicht nur Code-Overhead, sondern auch eine
Datenspeicherverschwendung verursachen. Mit statischer Polymorphie habe
ich mich damals nicht beschäftigt, aber das Curiously Recurring Template
Pattern sieht in der Tat vielversprechend aus.
Was mich an Templates stört: Ein Template zieht ein anderes Template
hinter sich her. Bsp:
Die Klasse A benutzt einen Zeiger auf die Treiberklasse Driver um
darüber was zu machen. Allerdings muss A dann die genaue Ausprägung von
Driver wissen, da sie ansonsten keinen Zeiger darauf erzeugen kann.
Somit muss A auch ein Templateklasse sein.
Dann kommt noch hinzu, dass wenn du mehrere Ausprägungen von Driver hast
für jede Ausprägung noch einmal alle Methoden von Driver angelegt
werden. Bei größeren Methoden und vielen Ausprägungen wächst damit der
Programmspeicherverbrauch sprunghaft an.
Das ist in der Tat ein Problem auf das ich auch gestoßen bin. Ich
umschiffe es damit, dass der Datenfluss in meinem System über
Ereigniskanalobjekte läuft, die unabhängig vom konkreten Treiber sind.
Das heißt, der Rest des Systems kennt nur diese Ereigniskanalobjekte und
ruft nicht direkt Funktionen des Treibers auf.
Für generische Methoden die trotzdem noch von anderen Klassen benötigt
werden, welche aber die konkrete Ausprägung meines Treibers nicht kennen
sollen (also den Templateparameter), habe ich noch eine Mutterklasse vor
die templatetisierte Klasse gesetzt. Allerdings dürfen diese Methoden
nur im Kontext dieser Mutterklasse bleiben, getter und setter
beispielsweise.
In meinem Falls funktioniert es jetzt prima, ist aber nicht gerade
besonders hübsch.
Wenn jemand noch Links zu guten Artikeln über Treiberarchitekturen hat,
immer her damit. Ich habe heute bereits extrem viel gelernt :)
Ansonsten hat sich die Frage von oben erledigt.
@netb: Viel Erfolg noch bei deiner Riesenklasse ;-)
Könntest du das Konzept der Ereignisknoten kurz anreißen, damit ich mir
ein Bild davon machen kann? Auch die Sache mit den Mutterklassen
interessiert mich. Ich habe viel hin und her überlegt wie man die Sache
mit der Templateausprägung lösen kann.
Dabei verfolge ich zwei Konzepte: Mit dem gettype-Operator kann man beim
GCC den Typen eines Objektes zur Kompilierzeit ermitteln. Beispiel:
Ich habe eine Hardwareklasse für die 1. USART Einheit am Port E des
XMegas. Auf das Objekt dieser Klasse kann über das Objekt "uC"
zugegriffen werden. Also:
1
uC.USARTE1...
Dann habe ich eine Treiberklasse RS232, welche als Template den Typen
der Hardwareklasse benötigt. Um die Treiberklasse mit der Hardwareklasse
zu verbinden schreibe ich nun:
1
RS232<gettype(uC.USARTE1)>com1;
Wenn die Ausprägung absolut gar nicht bekannt ist auch nicht per gettype
ermittelt werden kann, dann benutze ich spezielle Klassen als "Mantel"
und nur einer virtuellen Methode.
Basisklasse:
1
classBaseDriver
2
{
3
virtualvoidaccess(inttype,intparam)=0;
4
5
voiddoSomething(intparam)
6
{
7
access(0,param);
8
}
9
10
};
Davon abgeleitete Templateklasse:
1
template<typenameHWCLASS>
2
classDriver
3
{
4
typenameHWCLASShwObject;
5
6
virtuelvoidaccess(inttype,intparam)
7
{
8
switch(type)
9
{
10
case0:
11
doSomething(param);
12
break;
13
14
...
15
16
default:
17
break;
18
}
19
}
20
21
voiddoSomething(intparam)
22
{
23
hwObject.doAnything(param);
24
}
25
26
};
Nun lege ich irgendwo den Treiber an:
1
Driver<gettype(uc.USARTE1)>driver;
An einer anderen Stelle bekomme ich einen Pointer des Objektes. Die
Programmteile, die mit pDriver anstatt Driver arbeiten haben keinen
blassen Schimmer mehr welcher Ausprägung die Klasse von Driver war.
1
BaseDriver*pDriver=&driver;
Und kann nun über diesen Basispointer auf die Methoden der abgeleiteten
Klasse zugreifen (Umweg über die virtuelle Methode).
1
pDriver.doSomething(1);
(Syntax ist nicht geprüft, die Beispiele dienen nur zur Verdeutlichung.)
Das ist zwar ein sehr komplizierter Umweg aber in manch einer Situation
die einzige Lösung, die mir eingefallen ist, um die Ausprägung der
Templates nicht in jeden Winkel meines Programmes mittragen zu müssen.
Ich hoffe, dass ich hier nicht zu sehr vom eigentlichen Thema abweiche,
wenn doch können wir das per Mail behandeln, in einen anderen Thread
packen oder die Diskussion einfach einstellen.
Ich wünsche dir auch viel Erfolg bei deinem Treiber für Reflex.