Files |  Tutorials |  Articles |  Links |  Home |  Team |  Forum |  Wiki |  Impressum

Aktuelle Zeit: Sa Jul 12, 2025 05:09

Foren-Übersicht » Programmierung » Allgemein
Unbeantwortete Themen | Aktive Themen



Ein neues Thema erstellen Auf das Thema antworten  [ 31 Beiträge ]  Gehe zu Seite 1, 2, 3  Nächste
Autor Nachricht
 Betreff des Beitrags: auf Interface casten
BeitragVerfasst: Di Sep 15, 2009 22:30 
Offline
DGL Member
Benutzeravatar

Registriert: Di Nov 07, 2006 13:37
Beiträge: 83
Wohnort: Partenheim
Hi!

Bisher kam ich in Delphi immer ohne Interfaces aus, aber die Zeiten ändern sich und jetzt gibt's auch schon, gleich am Anfang, ein Problem:

Ich habe eine Klasse C, die ein Interface I realisiert. Erstelle ich nun eine Instanz Z dieser Klasse C, caste Z anschließend auf das Interface I (I(Z)), ist alles wunderbar.
Schreibe ich aber vor dem Cast Z in einen Pointer P (P vom Typ Pointer) und caste dann P auf das Interface I (I(P)), bekomme ich eine Zugriffsverletzung.
Und ich hab' keine Ahnung, warum. Ihr?

Einfaches Beispiel, denn ich fürchte, mit der obigen Beschreibung ist keinem geholfen:
Code:
  1. type
  2.   IDrawable = Interface(IInterface)
  3.     Procedure Render();
  4.   end;
  5.  
  6.   TButton = Class(TInterfacedObject, IDrawable)
  7.   public
  8.     Procedure Render();
  9.   end;
  10.  
  11. Procedure TButton.Render();
  12. Begin
  13.   writeln('render');
  14. End;
  15.  
  16. Var
  17.   b : TButton;
  18.   d : IDrawable;
  19.   p : Pointer;
  20. Begin
  21.   b := TButton.Create();
  22.   p := b;
  23.   d := IDrawable(p);      // genau hier gibt's die Zugriffsverletzung
  24.  
  25.   d.Render();
  26.   readln;
  27. End.


Danke schonmal.

Conan

_________________
THE TRUTH IS NOT OUT THERE!


Nach oben
 Profil  
Mit Zitat antworten  
 Betreff des Beitrags: Re: auf Interface casten
BeitragVerfasst: Mi Sep 16, 2009 07:05 
Offline
DGL Member
Benutzeravatar

Registriert: Do Apr 09, 2009 12:51
Beiträge: 53
Programmiersprache: Lazarus
Hi,

auch wenn ich noch nie so wirklich mit Interfaces gearbeitet habe, glaube ich den Fehler zu sehen. :)
Da du den Pointer des TButton-Objekts nutzt, musst du vorher auch wieder auf diesen casten.
Heißt:
Conan hat geschrieben:
Code:
  1. begin
  2.   b := TButton.Create();
  3.   p := b;
  4.   d := IDrawable(TButton(p));
  5.   d.Render();
  6. end;


Alternativ könntest du auch direkt auf das Interface zeigen, brauchst halt nur noch eine var zum Interface. :P


Nach oben
 Profil  
Mit Zitat antworten  
 Betreff des Beitrags:
BeitragVerfasst: Mi Sep 16, 2009 08:09 
Offline
DGL Member

Registriert: So Aug 20, 2006 23:19
Beiträge: 564
Hat das denn überhaupt einen Zweck? Wozu einen Pointer auf b, wenn b doch schon ein Pointer ist? Oder bin ich grad auf dem Holzweg?


Nach oben
 Profil  
Mit Zitat antworten  
 Betreff des Beitrags:
BeitragVerfasst: Mi Sep 16, 2009 09:07 
Offline
DGL Member
Benutzeravatar

Registriert: Do Dez 05, 2002 10:35
Beiträge: 4234
Wohnort: Dortmund
Ich denke auch, dass der Umweg über den Pointer an der Stelle den Compiler verwirrt und es genau deswegen zu einem Problem kommt. Aber das ganze ist alles viel zu kompliziert. Du kannst das Interface auch direkt zuweisen.

Folgendes ist vollkommen ausreichen. Bzw solltest du "Umwege" über Klassen auch in jedem Fall vermeiden.
Code:
  1. Var
  2.   Drawable: IDrawable;
  3. Begin
  4.   Drawable := TButton.Create();
  5.   Drawable.Render();
  6.   readln;
  7. End.


Nach oben
 Profil  
Mit Zitat antworten  
 Betreff des Beitrags:
BeitragVerfasst: Mi Sep 16, 2009 18:53 
Offline
DGL Member
Benutzeravatar

Registriert: Di Nov 07, 2006 13:37
Beiträge: 83
Wohnort: Partenheim
Mein Fehler, ich hab' nicht gesagt, wozu ich's brauche:
Ich brauche es dummerweise genau so und in der Reihenfolge, wie ich es aufgeschrieben habe, denn ich habe eine Liste, in der Objekte aufbewahrt werden, die IDrawable realisieren. Diese Liste speichert die Information eben nur als Pointer. D.h. wenn ich ein neues Objekt hineinschreibe, dann kennt die Liste nicht den Typ des Objekts, außer, dass es IDrawable ist.
Um jetzt in der Liste auf jedes Objekt zugreifen zu können, will ich das Interface benutzen und dazu muss ich aus dem Pointer ein IDrawable machen.
Was ich allerdings noch machen könnte wäre, in der Liste keine Pointer, sondern IDrawables abzuspeichern. Das ist aber sehr umständlich, da es in Delphi keine Generics gibt und ich mir, wenn ich das richtig sehe, eine ganz neue Listenklasse nur für IDrawables schreiben müsste.
Mir fällt nur der Weg über den Pointer ein. Sehe ich das richtig? Ich hoffe, ich irre mich und ihr habt eine bessere Idee.
Aber schonmal vielen Dank für die Mühe.

Conan

_________________
THE TRUTH IS NOT OUT THERE!


Nach oben
 Profil  
Mit Zitat antworten  
 Betreff des Beitrags:
BeitragVerfasst: Mi Sep 16, 2009 19:45 
Offline
DGL Member
Benutzeravatar

Registriert: Mi Mär 09, 2005 15:54
Beiträge: 372
Wohnort: München
Programmiersprache: Delphi, C#, FPC
Ich hab jetzt auch noch nicht oft mit Interfaces in Delphi gearbeitet, aber vielleicht ist TInterfaceList eher das richtige als eine Liste mit Pointern - unter anderem auch wegen dem Reference Counting bei Interfaces.

_________________
Aktuelles Projekt: Gael - Development Blog
Website: LightBlackSoft.com


Nach oben
 Profil  
Mit Zitat antworten  
 Betreff des Beitrags:
BeitragVerfasst: Mi Sep 16, 2009 19:56 
Offline
DGL Member
Benutzeravatar

Registriert: Do Apr 09, 2009 12:51
Beiträge: 53
Programmiersprache: Lazarus
Ich hab noch nicht ganz verstanden, wo das Problem jetzt liegt.
Die oben auftretende Exception kann so umgangen werden:
Code:
  1. begin
  2.   b := TButton.Create();
  3.   p := b;
  4.   d := IDrawable(TButton(p));
  5.   d.Render();
  6. end;

Wenn du jetzt in der Liste noch andere Objekte als TButton hast, die von IDrawable abgeleitet sind und du trotzdem auf Render zugreifen muss, hast du imho einen Designfehler.
'In Objektlisten gehören nur Objekte desselben Typs'
Erstelle dazu ein Objekttyp welches von IDrawable abgeleitet ist und zB die Rendermethode als virtual; abstract; definiert.
Von diesem Objekttyp kannst du nun TButton ableiten und Render implementieren.

Diesem Objekttyp kannst du jetzt für die Klasse sichtbar machen und dann alle Objekte der Objektliste in dieses casten.

Noch schnell ein grobes Beispiel:

Code:
  1. interface
  2.  
  3. type
  4.   IDrawable = Interface(IInterface)
  5.     Procedure Render();
  6.   end;
  7.  
  8.   TAIDrawableObj = class(TInterfacedObject, IDrawable)
  9.   public
  10.     procedure Render; virtual; abstract;
  11.   end;
  12.  
  13.   TButtonA = class(TAIDrawableObj)
  14.   public
  15.     procedure Render; override;
  16.   end;
  17.  
  18.   TAnotherButtonB = class(TAIDrawableObj)
  19.   public
  20.     procedure Render; override;
  21.   end;
  22.  
  23. implementation
  24.  
  25. procedure TButtonA.Render;
  26. begin
  27.   MessageBox(0, 'funzt!', '', MB_ICONINFORMATION or MB_OK);
  28. end;
  29.  
  30. procedure TAnotherButtonB.Render;
  31. begin
  32.   MessageBox(0, 'tadaa funzt auch o.O!', '', MB_ICONINFORMATION or MB_OK);
  33. end;
  34.  
  35. procedure TForm.btn1Click(Sender: TObject);
  36. var
  37.   b: TAIDrawableObj;
  38.   d: IDrawable;
  39.   p: Pointer;
  40. begin
  41. //  b := TButtonA.Create();
  42.   b := TAnotherButtonB.Create();
  43.   p := b;
  44.   d := IDrawable(TAIDrawableObj(p));
  45.   d.Render();
  46. end;

Auch wenn das Interface jetzt schon wieder unnötig ist, ich hoffe das war jetzt das Problem. :D


Nach oben
 Profil  
Mit Zitat antworten  
 Betreff des Beitrags:
BeitragVerfasst: Mi Sep 16, 2009 20:55 
Offline
DGL Member
Benutzeravatar

Registriert: Do Sep 02, 2004 19:42
Beiträge: 4158
Programmiersprache: FreePascal, C++
Du könntest anstatt den Pointer auf das Interface einfach eine Referenz auf das TObject speichern. Dann kannst du von da immer GetInterface mit der GUID deines Interfaces aufrufen. Solange du das nicht aus (anderssprachigen) DLLs importieren willst, sollte das seinen dienst tun.

greetings

_________________
If you find any deadlinks, please send me a notification – Wenn du tote Links findest, sende mir eine Benachrichtigung.
current projects: ManiacLab; aioxmpp
zombofant networkmy photostream
„Writing code is like writing poetry“ - source unknown


„Give a man a fish, and you feed him for a day. Teach a man to fish and you feed him for a lifetime. “ ~ A Chinese Proverb


Nach oben
 Profil  
Mit Zitat antworten  
 Betreff des Beitrags:
BeitragVerfasst: Mi Sep 16, 2009 20:59 
Offline
DGL Member
Benutzeravatar

Registriert: Di Nov 07, 2006 13:37
Beiträge: 83
Wohnort: Partenheim
Leider nicht, nein.
Deine Lösung funktioniert zwar, macht dabei aber Interfaces komplett unnötig.
Und in meinem Fall lässt sie sich leider nicht anwenden, da TButton ein Abkömmling der Liste ist, in die ich ihn reinpacke.
Somit müsste diese Liste den Typ ihres Abkömmlings kennen, der Abkömmling selbst muss aber natürlich den Typ seiner Mutterklasse kennen.
Das ergibt eine zirkuläre Referenz, die Delphi nicht gestattet, es sei denn, ich schreibe alles in eine Unit, was ich auf jeden Fall vermeiden will.

Zitat:
Wenn du jetzt in der Liste noch andere Objekte als TButton hast, die von IDrawable abgeleitet sind und du trotzdem auf Render zugreifen muss, hast du imho einen Designfehler.
'In Objektlisten gehören nur Objekte desselben Typs'


Ich weiß nicht genau, worauf du hinaus willst. Die Objekte sind alle selben Typs, insofern, dass sie alle das Interface IDrawable implementieren.
[sarkasmus]Wenn das ein Designfehler ist, dann ist meine Auffassung (und die vieler Professoren an meiner Uni) von Generalisierung grundlegend falsch[/sarkasmus]. Ich hoffe, es handelt sich hier nur um ein Missverständnis.

Mein Problem ist eigentlich ganz einfach formuliert:
Ist es denn nicht möglich, einen Pointer, der auf eine Instanz einer Klasse zeigt, auf ein Interface zu casten, das diese implementiert, ohne nochmal auf die Klasse selbst casten zu müssen?
Wenn es das nämlich nicht sein sollte, brauche ich gar nicht weiter nach Lösungen zu suchen.

Conan

_________________
THE TRUTH IS NOT OUT THERE!


Nach oben
 Profil  
Mit Zitat antworten  
 Betreff des Beitrags:
BeitragVerfasst: Mi Sep 16, 2009 22:02 
Offline
DGL Member
Benutzeravatar

Registriert: Di Nov 07, 2006 13:37
Beiträge: 83
Wohnort: Partenheim
Lord Horazont hat geschrieben:
Du könntest anstatt den Pointer auf das Interface einfach eine Referenz auf das TObject speichern. Dann kannst du von da immer GetInterface mit der GUID deines Interfaces aufrufen. Solange du das nicht aus (anderssprachigen) DLLs importieren willst, sollte das seinen dienst tun.

greetings


Du bist mein Held. Nach einiger Rumprobiererei klappt's tatsächlich.
Vielen Dank!

Conan

_________________
THE TRUTH IS NOT OUT THERE!


Nach oben
 Profil  
Mit Zitat antworten  
 Betreff des Beitrags:
BeitragVerfasst: Do Sep 17, 2009 08:16 
Offline
DGL Member
Benutzeravatar

Registriert: Do Dez 05, 2002 10:35
Beiträge: 4234
Wohnort: Dortmund
Ich hatte das oben schon mal angedeutet. Allerdings will ich das gerade noch mal deutlicher sagen. Klasseninstanzen und Interface zu mischen ist keine gute Idee. Das wird mit großer wahrscheinlichkeit in Zugriffsverletzungen oder dreckigem Code enden. Denn Interfaces besitzen eine Referenzzählung. Diese Zählung sorgt dafür, dass die zu Grunde liegende Klasseninstanz gelöscht wird, wenn kein Interface mehr benutzt wird. Kleines Beispiel.
Code:
  1. Var
  2.   Button: TButton;
  3.   Drawable: IDrawable;
  4. Begin
  5.   Button := TButton.Create();
  6.  
  7.   Drawable := Button;
  8.   Drawable.Render();
  9.   Drawable := nil;
  10.  
  11.   Button.Blah; // Hier kann es knallen
  12. End.

Bei der Zuweisung von Nil auf Drawable wurde die Instanz von TButton gelöscht. Anschließende Zugriffe benutzen ein Objekt welches nicht mehr existiert. Wenn man glück hat wurde der Speicher noch nicht verändert und es knallt nicht. Aber das ist dann nur Zufall. Es spielt auch keine Rolle ob die Klasseninstanz als Pointer in einer Liste liegt oder als direkt als TButton irgendwo in einer Variable. Die Instanz wird gelöscht. Es genügt in dem Fall auch die Methode zu verlassen, da Drawable eine lokale Variable ist würde die anschließend auch nicht mehr existieren und die Instanz würde gelöscht werden.
Man könnte das vielleicht durch rumschrauben an dem Referenzzähler hinbekommen, damit die Klasse nie sinnvoll gelöscht wird. Außer man macht das per Hand. Aber das ist sicher nicht Sinn und Zweck des Ganzen. Dadurch würde man sich nur potentielle Sicherheitslücken oder Speicherlöcher einfangen. Entsprechend kann ich nur dringenst davon abraten Klasseninstanzen und Interfaces zur gleichen Zeit benutzen zu wollen. Das gibt nur ärger. Hatten wir hier im Forum auch schon einige male.

Ich kann nur das Empfehlen was littleDave schon empfohlen hat. Das Objekt TInterfaceList bzw. IInterfaceList. Wenn ein Interface dort hinzugefügt wird, dann wird der Referenzzähler erhöht. Wird es gelöscht, dann wird er verringert. Das Auslesen funktioniert dann ähnlich wie Lord Horazont dann schon gesagt hat. In der Liste werden nur IInterface abgelegt und du musst dann dein Interface erfragen. Deine Interfaces benötigen dabei dann aber eine GUID (tastenkombi strg+shift+g).

Code:
  1.   IBlah = interface
  2.     ['{DB544CC1-BF6C-4A21-9E34-414F38664427}']
  3.     procedure Blah;
  4.   end;
  5.  
  6. var
  7.   Blah: IBlah;
  8. begin
  9.   InterfaceList[0].QueryInterface(IBlah, Blah);
  10.   Blah.Blah;
  11. end;


[edit] PS: Du solltest bei so etwas auch FastMM benutzen. Beim Aufrufen von virtuellen Methoden von Klassen die gelöscht wurden schreit der kräftig auf. Dadurch finden man mitunter direkt Stellen die später irgendwann in sehr komischen Effekten enden.


Nach oben
 Profil  
Mit Zitat antworten  
 Betreff des Beitrags:
BeitragVerfasst: Do Sep 17, 2009 13:01 
Offline
DGL Member
Benutzeravatar

Registriert: Di Nov 07, 2006 13:37
Beiträge: 83
Wohnort: Partenheim
Oh, das passt mir gar nicht. :?
Was ist, wenn ich statt TInterfacedObject einfach das TObject nehme und die Methoden aus IInterface selbst implementiere (und zwar leer)?
Dann gibt's kein Reference Counting und kein selbstständiges Löschen mehr.

Von diesem FastMM hab' ich noch nie gehört, aber ich hätte es schon oft sehr gut gebrauchen können. Danke!

Conan

_________________
THE TRUTH IS NOT OUT THERE!


Nach oben
 Profil  
Mit Zitat antworten  
 Betreff des Beitrags:
BeitragVerfasst: Do Sep 17, 2009 15:52 
Offline
DGL Member
Benutzeravatar

Registriert: Di Sep 03, 2002 15:08
Beiträge: 662
Wohnort: Hamburg
Programmiersprache: Java, C# (,PhP)
Wieso prüft ihr vor dem "Knall"-Zugriff nicht einfach auf NIL? In der Java-Welt ist das prüfen auf NULL ein müßiges Allerlei das eine Menge vom Code bei Listen ausmacht.

_________________
(\__/)
(='.'=)
(")_(")


Nach oben
 Profil  
Mit Zitat antworten  
 Betreff des Beitrags:
BeitragVerfasst: Do Sep 17, 2009 16:08 
Offline
DGL Member
Benutzeravatar

Registriert: Do Dez 05, 2002 10:35
Beiträge: 4234
Wohnort: Dortmund
Evil-Devil: Versuchs mal. Wir sprechen uns dann wenns trotzdem knallt. Der Destruktor wird irgendwann intern aufgerufen. Dadurch wird doch der Pointer auf dem Stack oder in irgendwelchen Listen nicht gelöscht oder verändert. Wie soll das auch gehen. Entsprechend zeigt die Instanz dann irgendwo in den Speicher.

Conan: Ich muss gestehen ich weiß nicht ganz was du da vor hast. Willst du dann weiterhin die Interfaces benutzen oder alles auf eine klassische Objektstruktur umbasteln? Wenn Interfaces, dann weiß ich nicht ob das so ohne Weiteres klappt. Zu mal du auch so etwas wie QueryInterface implementieren musst. Allerdings stelle ich mir da auch die Frage was gegen TInterfaceList/IInterfaceList spricht? Die macht eigentlich genau das was du vor hast. Und die scheinst du gerade sehr erfolgreich zu ignorieren.


Nach oben
 Profil  
Mit Zitat antworten  
 Betreff des Beitrags:
BeitragVerfasst: Do Sep 17, 2009 16:24 
Offline
DGL Member
Benutzeravatar

Registriert: Do Sep 02, 2004 19:42
Beiträge: 4158
Programmiersprache: FreePascal, C++
QueryInterface ist einfach gemacht, das ist nichts weiter als ein Aufruf von GetInterface und die Rückgabe von ... einem Fehlercode, wenns nicht klappt.
Ich habe für meine Projekte auch das ganze umgemünzt, weil ich mit dem automatischen Löschen der Objekte mal rein garnichts anfangen konnte. Die Holzhammermethode ist, beim erzeugen die Referenzzählung auf 1 zu setzen, aber dann muss man das in BeforeDestruction wieder um einen senken, sonst knallts (da gibts ne Prüfung).

Evil-Devil: Lossy sagts schon ziehmlich genau. Wenn du irgendwo Free aufrufst, sind ja nicht gleich alle Verweise auf das Objekt nil. Leider (oder auch gut so) ;).

greetings

_________________
If you find any deadlinks, please send me a notification – Wenn du tote Links findest, sende mir eine Benachrichtigung.
current projects: ManiacLab; aioxmpp
zombofant networkmy photostream
„Writing code is like writing poetry“ - source unknown


„Give a man a fish, and you feed him for a day. Teach a man to fish and you feed him for a lifetime. “ ~ A Chinese Proverb


Nach oben
 Profil  
Mit Zitat antworten  
Beiträge der letzten Zeit anzeigen:  Sortiere nach  
Ein neues Thema erstellen Auf das Thema antworten  [ 31 Beiträge ]  Gehe zu Seite 1, 2, 3  Nächste
Foren-Übersicht » Programmierung » Allgemein


Wer ist online?

Mitglieder in diesem Forum: 0 Mitglieder und 10 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.

Suche nach:
Gehe zu:  
  Powered by phpBB® Forum Software © phpBB Group
Deutsche Übersetzung durch phpBB.de
[ Time : 0.013s | 15 Queries | GZIP : On ]