Hi,
ich habe vor einer Weile mal mit Java angefangen und bin auf die Vorteile von Interfaces gestossen. Kurz darauf habe ich bemerkt, dass man die auch in Delphi verwenden kann und daher habe ich mein bisheriges Projekt, eine 3D Engine, mit Interfaces ausgestattet.
Ich habe einen Eventmanager, der alle Events (Keydown, Mousebewegungen, Klicks, Exit usw usw usw) registriert und weiterleitet. Für jedes Event verfügt er über eine Liste, in der Interfaces abgelegt sind.
Wenn ich nun zb eine Szene erstellen und das Interface IKeyListener implementiere, dann kann ich diese Szene dank ihres Interfaces in meinem Eventlistener registrieren. Wenn mein Eventlistener nun ein KeyDown event registriert, ruft er automatisch die Keydownmethode der Szene, die im Interface definiert ist, auf. Soviel zur Vorgeschichte. hier erstmal etwas Quellcode, damit das klarer wird:
Die Listenerdefinition:
Code:
type
IKeyListener =interface(IInterface)
['{BB9DB2E4-B01D-445E-9778-5742CD29ED7C}']
procedure OnKeyDown (event : TKeyEvent);
procedure OnKeyUp (event : TKeyEvent);
procedure OnKeyPressed(event : TKeyEvent);
end;
Die Szenendefinition:
Code:
type
TWorldScene =class(TGL_Scene, IKeyListener)
private
points:Arrayof TGLVector3f;
tmp:Arrayof TGLVector3f;
public
constructor Create();override;
procedure Init;override;
procedure Render;override;
procedure Update (aTime:Integer);override;
procedure Release;override;
destructor Destroy();override;
procedure OnKeyDown (event : TKeyEvent);
procedure OnKeyUp (event : TKeyEvent);
procedure OnKeyPressed(event : TKeyEvent);
end;
Wenn ich nun in meinem Hauptprogramm die Szene per:
Code:
EventManager.AddKeyListener(world);
registriere, dann wird fortan bei jedem Tastendruck die entsprechenden Methoden aus der Szene aufgerufen..
Nun das Problem:
Bei der Erstellung der Szene muss ich die Szene in meinem Szenenmanager registrieren. Das heisst das Objekt world wird automatisch doppelt registriert. Einmal in meinem Szenenmanager, weil es von Scene erbt und einmal im Eventmanager, weil es den IKeyListener implementiert.
Wenn ich das Programm allerdings beende und alle Objekte zerstört werden, treten Fehler auf, die aus irgendeinem Grund mit den Interfaces zusammenhängen:
Code:
destructor TGL_SceneManager.Destroy;
var i :Integer;
begin
ReleaseScene();
for i :=0to lScenes.Count-1do
begin
(lScenes.Objects[i]as TGL_Scene).Destroy;
end;
lScenes.Destroy;
end;
end.
In diesem Destructor des ScenenManagers werden alle Szenen, die in der THashedStringList lScenes abgelegt sind, zerstört. In dem Moment, in dem das Programm versucht, die Worldscene zu zerstören, bekomme ich eine EInvalidPointer mit der Meldung "Ungültige Zeigeroperation" und zwar genau in der Zeile begin des Destruktors.
Code:
destructor TWorldScene.Destroy;
begin
inherited Destroy;
end;
Ich bastel hier jetz schon ewig dran rum und weiss einfach nicht warum das nicht geht. Nicht zuletzt könnte das aber daran liegen, dass es bei Interfaces womöglich irgendeine Spezifikation gibt, von der ich nichts weiß. Prinzipiell sind die Quelle, die ich im Internet zu Delphiinterfaces finde, aber ziemlich Bescheiden..
Registriert: Do Dez 05, 2002 10:35 Beiträge: 4234 Wohnort: Dortmund
Also deine Interface Klasse muss von TInterfacedObject abgelitten sein. Und Interfaces haben eine Referenzzählung! Du hast eine Klasse mit einem Interface verbundelst. Wenn du das Interface an 3 Stellen verwendest und diese Stellen dann mit NIL zuweist bzw die Klassen die eine Referenz auf das Interface hat dann freigibst, dann wird automatisch die Instanz deiner Klasse gelöscht. Wenn du in der Zwischenzeit deine Klassen die ein Interface anbieten auf herkömmliche Weise freigibst, dann zeigt dein Interface ins nichts. Wenn das per Zufall noch eine gültigen Referenzzähler hat der irgendwann 0 ist, dann wird versucht eine nicht mehr existierende Instanz zu löschen. Ich vermute mal so etwas wird bei dir passiert sein.
Code:
var
Blah: IBlah;
begin
Blah := TBlah.Create;
Blah.Blub;
Blah :=nil;
end;
So sieht zu mindest der klassische Weg aus. Ich weiß jetzt nicht ob es sinnvolle Wege gibt sowohl die Klasseninstanz als auch das Interface parallel benutzen zu können. Bzw benutzen kann man sie ja aber du würdest nicht mitbekommen, wenn die Klasse durch die Referenzzählung gelöscht würde. Bzw die Interfaces würden nicht merken, wenn die Instanz der Klasse manuell gelöscht würde.
Also ich habe, um versehentliche Freigabe eines nichtmehr existenten Objektes/Interface vorzubeugen, mal alle Destroys auf Free umgestellt. IM Grunde müsste dieses Problem ja nun umgangen werden. Funktionieren tuts aber dennoch nicht ^^
Registriert: Do Dez 05, 2002 10:35 Beiträge: 4234 Wohnort: Dortmund
"Das glaube ich nicht Tim." Denn das Free überprüft ob die Variable der Instanz ungleich nil ist. Das sind aber nur Pointervariablen. Du kannst aber mehr als einen Pointer auf deine Instanz haben. Kleines Beispiel um das zu verdeutlichen.
Code:
var
A, B: TList;
begin
A := TList.Create;
B := A;
FreeAndNil(A);
A.Free;// funktioniert problemlos, da es vom Free ignoriert wird
B.Free;// zugriffsverletzung
end;
A ist nach FreeAndNil nil. Wenn du A dann noch mal freigeben wolltest würde nichts passieren, da A bereits nil ist. Aber was passiert bei B.Free? B ist nach wie vor gesetzt und zeigt auf den Speicherbereich der vormals A zugewiesen war. Im Falle von Free merkt der Delphispeichermanager aber, dass der Speicher nicht mehr existiert und schmeißt eine Zugriffsverletzung. Du könntest aber zu B "problemlos" Einträge hinzufügen, löschen etc. Knallen würde vermutlich es erst dann, wenn jemand anders den freien Speicher für sich beanspruchen würde. Wenn man anstelle des FreeAndNil 2 Mal A.Free aufrufen würde würde es beim zweiten Mal auch knallen, da nach dem ersten A.Free A auch noch auf den Speicher zeigt.
Genau das Gleiche passiert bei deinen Interfaces! Du gibst die Instanzen der Klasse zwar frei allerdings hast du immer noch einen Pointer auf dessen Speicher (Variablen auf die Interfaces. vergleichbar mit B). Nur, dass du dort den Destruktor nicht selber aufrufst sondern dieser automatisiert von dem Interface aufgerufen wird, wenn dieses nicht mehr benutzt wird. Wenn die Interface nicht gerade eine Referenz auf einander haben (A hat referenz auf B und B eine auf A), dann wird der Destruktor in jedem Fall irgendwann aufgerufen werden. Der hat aber nach wie vor einen eigene Instanzvariable.
Wenn du von einer Interfaceklasse sowohl die Klasseninstanz als auch die Interfaces benutzt, dann darfst du die Klasse nicht selber freigeben. Bzw es kann auch passieren, dass die Klasseninstanz ohne dein Wissen gelöscht wird, wenn kein Interface mehr benutzt wird. Und dann würde dir die Klasse unter dem Allerwertesten weggeschossen werden. Entsprechend solltest du selbst für deine normalen Klassenfunktionen ein Interface definieren und dieses benutzen. Aber nicht mischen. Eine Klasse kann problemlos 2-30 Interface unterstützen. Allerdings solltest du wirklich nur über Interface arbeiten.
PS: Crossposting im Delphi-forum (oder auch anderswo) bitte nächstes Mal selber mit angeben. Das kann es verhindern, dass man doppelte Hinweise schreibt.
Registriert: Di Okt 03, 2006 14:07 Beiträge: 1277 Wohnort: Wien
Für Delphi 5 gab es eine anständige Dokumentation. Ein Teil davon ist die Object Pascal Sprachreferenz (Object Pascal Language Guide), Kapitel 10, "Schnittstellen". Dieses Buch ist als PDF kostenlos im Internet erhältlich (einfach googeln) und wirklich SEHR informativ.
Mitglieder in diesem Forum: 0 Mitglieder und 5 Gäste
Du darfst keine neuen Themen in diesem Forum erstellen. Du darfst keine Antworten zu Themen in diesem Forum erstellen. Du darfst deine Beiträge in diesem Forum nicht ändern. Du darfst deine Beiträge in diesem Forum nicht löschen. Du darfst keine Dateianhänge in diesem Forum erstellen.