C++ Interfaces
Posted by Mark | Posted in Artikel, C++, WinAPI, Windows | Posted on 07-04-2009
Tags: c++, interface, interfaces, win32, WinAPI, windows
2
Was ist ein Interface?
Interfaces sind Beschreibungen für das Aussehen der tatsächlichen Implementierung der Klassen, im Gegensatz zu abstrakten Klassen werden bei Interfaces keine
Funktionen implementiert (bis auf Destruktoren, dazu aber später mehr).
Wozu dient das ganze?
Interfaces werden dazu benutzt, eine gemeinsame Schnittstelle und ein wohldefiniertes Konstrukt der Klassenstrukturen anzubieten. So wird gewährleistet, dass spätere
Implementierungen problemlos ausgetauscht werden können, durch Plugins oder einfach neuere Versionen der alten Klassen.
Was sind die Besonderheiten?
Das besondere an Interfaces ist, dass die Implementierungen nur ein einziges mal Speicher für die Funktionen anfordern müssen, was bei Objekten wie z.B.
Gegner/Items/Partikel einen enormen Speichervorteil darstellen kann, je nachdem wie viele Methoden man nun benutzt.
Noch dazu wird die Benutzung der GUIDs enorm vereinfacht. Die Übersichtlichkeit wird erhöht und für den Klienten bleiben die tatsächlichen Implementierungen und die
dazugehörigen Verwirrungen verborgen, da der Endklient nur die Interfaces benutzen soll.
Eine wichtige Besonderheit von Interfaces ist übrigens auch, dass virtuelle Destruktoren schon im Interface implementiert werden müssen, da ansonsten eine
Fehlermeldung die weitere Benutzung unterbindet (wieso das so ist, kann jeder selbst versuchen heruaszufinden). Virtuelle Destruktoren haben den Vorteil, wenn
kontinuierlich genutzt, dass auch Ableitungen den Destruktor der unterliegenden Klassen automatisch aufrufen wenn sie zerstört werden.
Hilfsfunktionen/Klassen:
Man kann sich das Verwenden der Interfaces enorm vereinfachen und damit auch die Benutzung der Implementierungen. Dazu bietet zumindest der VC Compiler nützliche
Schlüsselwörter an, welche man in Kombination mit geschickten Code wunderbar weiter verwenden kann.
Dazu gehört z.B. das Casten in eine bestimmte andere Klasse ohne die Verwendung von Strings, Flags oder anderen Identifizierungsmitteln innerhalb der Klasse selbst
durch z.B. Attribute.
Anwendungsbeispiel:
Hier werde ich nun ein kleines Beispiel vorzeigen für die Benutzung von Interfaces. Als erstes ein sehr simples Interface:
- struct __declspec(novtable) __declspec(uuid("{EDFDE891-F04D-4061-8CCC-F95959C16354}")) iBaseObject
- {
- // virtueller Destructor wird implementiert!
- virtual ~iBaseObject {}
- // alles andere nicht
- virtual void* SearchInterface(const GUID& InterfaceGuid) = 0;
- };
Wie man gut sehen kann, habe ich bei der Struktur von iBaseObject 2 Schlüsselwörter benutzt; zum einen __declspec(novtable) was bedeutet, dass für
diese Struktur allein kein VTable erstellt werden soll, was ja auch ziemlich unsinnig wäre, da diese Struktur so nie verwendet wird (es kann ja auch nichts), zwingend
nötig ist dies allerdings nicht.
Zum anderen habe ich __declspec(uuid(“{EDFDE891-F04D-4061-8CCC-F95959C16354}”)) verwendet, womit ich diesem Interface eine eindeutige/einmalige Kennzeichnung
(GUID = Globally Unique Identifier) verpasse. Mit dieser GUID lässt sich eine spätere Klasse testen ob sie etwas mit dem Interface gemeinsam hat, dazu später mehr.
iBaseObject wird später die für alle weiteren Interfaces das zugrunde liegende Standardinterface bilden. In diesem Tutorial werde ich es verwenden und auch
gleich erklären weshalb.
Nun zeige ich eine Beispiel BaseEntity Interfacebeschreibung:
- struct __declspec(novtable) __declspec(uuid("{18ED9EBE-E346-4762-BA22-A3EE7EC352AD}")) iBaseEntity
- : public iBaseObject
- {
- // virtueller Destructor wird implementiert!
- virtual ~iBaseEntity {}
- // nuetzliche Methoden
- virtual std::string getName() const = 0;
- virtual void setPosition(const Vector3& Position) = 0
- virtual Vector3 getPosition() const = 0
- ...
- };
Zu beachten ist hier, dass iBaseEntity eine neue GUID verpasst bekommen hat. Erstellen lassen sich GUIDs unter VisualStudio übrigens sehr einfach über den
Menüpunkt “Extras->GUID erstellen…” einfach auf “Copy” beim GUID Generator klicken und im Code einsetzen. Schön einfach das ganze.
Nun werde ich nochmal auf die Bedeutung der SearchInterface Methode etwas erläutern, dazu ein Anwendungsbeispiel.
Angenommen wir haben noch weitere Interfaces:
- struct __declspec(novtable) __declspec(uuid("{9626799C-FF45-4517-AB36-B4162D71A087}")) iPlayerEntity
- : public iBaseObject
- {
- // virtueller Destructor wird implementiert!
- virtual ~iPlayerEntity {}
- // nuetzliche Methoden
- ...
- };
- struct __declspec(novtable) __declspec(uuid("{9626799C-FF45-4517-AB36-B4162D71A087}")) iItemEntity
- : public iBaseObject
- {
- // virtueller Destructor wird implementiert!
- virtual ~iItemEntity {}
- // nuetzliche Methoden
- ...
- };
und deren Implementierung:
- class cBaseEntity
- : public iBaseEntity // und unser interface als Schablone benutzen
- {
- public:
- ...
- virtual void* SearchInterface(const GUID& InterfaceGuid);
- ...
- };
- class cPlayerEntity
- : public cBaseEntity // Basisimplementierung benutzen \o/
- , public iPlayerEntity // und unser interface als Schablone benutzen
- {
- public:
- ...
- virtual void* SearchInterface(const GUID& InterfaceGuid);
- ...
- };
- class cItemEntity
- : public cBaseEntity // Basisimplementierung benutzen \o/
- , public iItemEntity // und unser interface als Schablone benutzen
- {
- public:
- ...
- virtual void* SearchInterface(const GUID& InterfaceGuid);
- ...
- };
SearchInterface wird benötigt, um die Klasse wenn möglich in ein gewünschtes Interface zu bringen, was wir dann dazu benutzen können um heraus zu finden,
welchem Typ die Klasse angehört:
- void* cBaseEntity::SearchInterface(const GUID& InterfaceGuid)
- {
- if (InterfaceGuid == __uuidof(iBaseEntity))
- return static_cast<iBaseEntity>(this);
- // nichts liegt hier drunter
- return NULL;
- }
- void* cPlayerEntity::SearchInterface(const GUID& InterfaceGuid)
- {
- if (InterfaceGuid == __uuidof(iPlayerEntity))
- return static_cast<iPlayerEntity>(this);
- // Basis abfragen
- return cBaseEntity::SearchInterface(InterfaceGuid);
- }
- void* cItemEntity::SearchInterface(const GUID& InterfaceGuid)
- {
- if (InterfaceGuid == __uuidof(iItemEntity))
- return static_cast<iItemEntity>(this);
- // Basis abfragen
- return cBaseEntity::SearchInterface(InterfaceGuid);
- }
Wenn man dies dann im Code benutzt braucht man nicht auf gewöhnliche Weise einen Flag abfragen der erst umständlich definiert und gesetzt werden muss, sondern braucht
nur überprüfen, ob der Rückgabewert von SearchInterface ungleich oder gleich NULL ist:
- iBaseEntity* entity = EntityCreator->CreateHealthpack();
- // position setzen
- entity->setPosition(Vector3(10, 5, 0));
- // heilstaerke setzen
- iHealthEntity* healthpack = static_cast<iHealthEntity>(entity->SearchInterface(__uuidof(iHealthEntity)));
- if (!healthpack)
- Error("entity ist kein HealthPack!");
- healthpack->setHealrate(100.0f);
Da das ganze recht unschön aussieht, gibt es dagegen auch gute Möglichkeiten, dieses zu vereinfachen durch eine simple Template-Funktion:
- template TargetType* interface_cast(iBaseObject* sourceObject)
- {
- if (!sourceObject)
- return NULL;
- return reinterpret_cast<TargetType>( sourceObject->SearchInterface(__uuidof(TargetType)) );
- };
Wodurch das Beispiel von oben nun so aussehen könnte:
- iBaseEntity* entity = EntityCreator->CreateHealthpack();
- // position setzen
- entity->setPosition(Vector3(10, 5, 0));
- // heilstaerke setzen
- iHealthEntity* healthpack = interface_cast<iHealthEntity>(entity);
- if (!healthpack)
- Error("entity ist kein HealthPack!");
- healthpack->setHealrate(100.0f);
mfg Mark
PS: Sollte GUID ein unbekannter Typbezeichner sein, so nutzt einfach das hier:



[quote]Das besondere an Interfaces ist, dass die Implementierungen nur ein einziges mal Speicher für die Funktionen anfordern müssen[/quote]
Verstehe ich nicht.
[quote]Noch dazu wird die Benutzung der GUIDs enorm vereinfacht.[/quote]
Was hat das mit C++ Interfaces zu tun?
[quote]der Endklient[/quote]
Aha, gehts hier um Client/Server-Anwendungen?
[code]__declspec(novtable) __declspec(uuid( ...[/code]
Das ist kein C++.
Sorry, ich kann nicht erkennen, wozu dieser Artikel gut sein soll. Ich weiss nicht, in welcher Situation ich dieses Wissen anwenden kann. Oder in welchem übergeordneten Kontext es einzuordnen ist.
Hallo
Interfaces
Vorteile: -Man kann den Code verbergen so daß ein reverse-engineering notwendig ist um in zu bekommen.
-Ist sehr praktisch für jede Art von Schnittstelle z.B. unterschiedliche Librarys lösen das gleiche Problem und können mit einem Interface schnell ausgewechselt werden.
Bekannte Anwendung: MS Direktx funktioniert nur mit solchen Interfaces