AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Programmierung allgemein Algorithmen, Datenstrukturen und Klassendesign Delphi Verweis auf Interface-Instanz weitergeben - ist das erlaubt?
Thema durchsuchen
Ansicht
Themen-Optionen

Verweis auf Interface-Instanz weitergeben - ist das erlaubt?

Ein Thema von Bodenseematze · begonnen am 27. Feb 2024 · letzter Beitrag vom 1. Mär 2024
Antwort Antwort
Seite 1 von 2  1 2      
Bodenseematze

Registriert seit: 10. Jul 2023
68 Beiträge
 
#1

Verweis auf Interface-Instanz weitergeben - ist das erlaubt?

  Alt 27. Feb 2024, 10:17
Hallo,

ich bin mir aktuell etwas unsicher, was die Speicherung und Weitergabe von Interface-Verweisen anbelangt.
Ist das erlaubt oder wird mir hier bei der Weiterreichung der "Zeiger" kaputt gemacht wg. Referenzzählung?

Ich versuche mal, das ganze hier (stark vereinfacht) darzustellen...

Ich habe Schnittstellendefinitionen, z.B.:
Delphi-Quellcode:
ITestGet = interface[ '{725D0235-8604-4E26-88E4-29223A3E6EB1}' ]
   function GetTestName() : String;
   property TestName : String
                                    read GetTestName;
end;


ITestSet = interface( ITestGet )[ '{2F54CC2F-70A8-4621-99C6-2B1CD5AE079F}' ]
   procedure SetTestName( const sName_ : String );
   property TestName : String
                                    write SetTestName;
end;


ITestImpl = interface( ITestGet )[ '{1C48CB92-1DA9-452E-9921-9B43A598715A}' ]
   procedure SetIfTestImpl( ifTestImpl_ : ITestImpl );
   function GetIfTestImpl() : ITestImpl;

   property IfTestImpl : ITestImpl
                                    read GetIfTestImpl
                                    write SetIfTestImpl;
end;

Dann habe ich folgende Klassen:
Delphi-Quellcode:
TMyBaseForm = class( TForm )
public
   constructor Create( owner_ : TComponent ); override;
   constructor CreateMe( owner_ : TComponent ); virtual;

   procedure SetIfTestImpl( ifTestImpl_ : ITestImpl ); virtual;
   function GetIfTestImpl() : ITestImpl; virtual;

   function GetIsIfTestImplSelf() : Boolean; virtual;

   property IfTestImpl : ITestImpl
                                    read GetIfTestImpl
                                    write SetIfTestImpl;

   property IsIfTestImplSelf : Boolean
                                    read GetIsIfTestImplSelf;

private
   _ifTestImpl : ITestImpl;
end;


//***************************************************************************
TMyOtherForm = class( TMyBaseForm )
published
   pbTest : TButton;


public
   constructor CreateMe( owner_ : TComponent ); override;
end;


//***************************************************************************
TMyMainBaseForm = class( TMyBaseForm, ITestImpl )
public
   constructor CreateMe( owner_ : TComponent ); override;

   procedure SetIfTestImpl( ifTestImpl_ : ITestImpl ); override;

   function GetTestName() : String; virtual;

   property TestName : String
                                    read GetTestName;
end;


//***************************************************************************
TMyMainForm = class( TMyMainBaseForm, ITestSet )
public
   constructor CreateMe( owner_ : TComponent ); override;

   function GetTestName() : String; override;
   procedure SetTestName( const sName_ : String ); virtual;

   property TestName : String
                                    read GetTestName
                                    write SetTestName;

private
   _sTestName : String;
end;

Hier die Implementierungen:
Delphi-Quellcode:
constructor TMyBaseForm.Create(
                                    owner_ : TComponent );
begin
   CreateMe( owner_ );
end;


constructor TMyBaseForm.CreateMe( owner_ : TComponent );
var
   ifTestImplMy : ITestImpl;
begin
   _ifTestImpl := nil;
   if ( Assigned(owner_) and Supports(owner_, ITestImpl, ifTestImplMy) ) then begin
      SetIfTestImpl( ifTestImplMy );
   end;
end;


procedure TMyBaseForm.SetIfTestImpl( ifTestImpl_ : ITestImpl );
begin
   _ifTestImpl := ifTestImpl_;
end;


function TMyBaseForm.GetIfTestImpl() : ITestImpl;
begin
  Result := _ifTestImpl;
end;


function TMyBaseForm.GetIsIfTestImplSelf() : Boolean;
var
  ifTestImplMy : ITestImpl;
begin
   Result := false;
   if ( Assigned(_ifTestImpl) ) then begin
      if ( Supports(Self, ITestImpl, ifTestImplMy) ) then begin
         if ( _ifTestImpl = ifTestImplMy ) then begin
            Result := true;
         end;
      end;
   end;
end;


//***************************************************************************
constructor TMyOtherForm.CreateMe( owner_ : TComponent );
var
   ifTestImplMy : ITestImpl;
begin
   inherited CreateMe( owner_ );

   pbTest := TButton.Create( Self );
   pbTest.Caption := 'Unknown';
   ifTestImplMy := IfTestImpl;
   if ( Assigned(ifTestImplMy) ) then begin
      pbTest.Caption := ifTestImplMy.TestName;
   end;
end;


//***************************************************************************
constructor TMyMainBaseForm.CreateMe( owner_ : TComponent );
var
   ifTestImplMy : ITestImpl;
begin
   inherited CreateMe( owner_ );
   ifTestImplMy := GetIfTestImpl();
   if ( NOT Assigned(ifTestImplMy) ) then begin
      SetIfTestImpl( nil );
   end;
end;


procedure TMyMainBaseForm.SetIfTestImpl( ifTestImpl_ : ITestImpl );
var
   ifTestImplMy : ITestImpl;
begin
   ifTestImplMy := ifTestImpl_;
   if ( NOT Assigned(ifTestImplMy) ) then begin
      Supports( Self, ITestImpl, ifTestImplMy );
   end;
   inherited SetIfTestImpl( ifTestImplMy );
end;


function TMyMainBaseForm.GetTestName() : String;
var
   ifTestImplMy : ITestImpl;
begin
   Result := EmptyStr;
   ifTestImplMy := GetIfTestImpl();
   if ( Assigned(ifTestImplMy) then begin
      if ( IsIfTestImplSelf ) then begin
         Result := Format( '%s.%s', [Classname, Name] );
      end
      else begin
         Result := ifTestImplMy.GetTestName();
      end;
   end;
end;


//***************************************************************************
constructor TMyMainForm.CreateMe( owner_ : TComponent );
begin
   inherited CreateMe( owner_ );
   _sTestName := EmptyStr;
end;


function TMyMainForm.GetTestName() : String;
begin
    if ( Length(_sTestName) > 0 ) then begin
       Result := _sTestName;
    end
    else begin
       Result := inherited GetTestName();
    end;
end;


procedure TMyMainForm.SetTestName( const sName_ : String );
begin
   _sTestName := sName_;
end;
In der Art wie TMyOtherForm gibt es ganz viele Klassen, so wie TMyMainForm gibt es normalerweise nur eine; wenn die MyOtherForm-Klassen instanziert werden, existiert die Instanz von TMyMainForm bereits und sie erhalten als Owner diese Instanz.

Ich habe jetzt das Problem, dass manchmal bei den Aufrufen von GetIfTestImpl bzw. dem Property IfTestImpl als Ergebnis nil zurück kommt, obwohl die interne Klassenvariable _ifTestImpl gesetzt ist.
Ich habe da bereits in der Methode eine Log-Ausgabe der Art Format( '%8.8X', [Integer(_ifTestImpl)] ) die zeigt eine Adresse an - wenn ich das ganze dann nach zurückkehren der Methode im Aufrufer mit dem zugewiesenen Ergebnis mache, kommt 0 als Ergebnis
--> deswegen meine Frage: ist es erlaubt, Interface-Zeiger in einer Klassenvariablen zu speichern und "herumzureichen"?

(das ganze wie immer bei mir für Delphi7 )
  Mit Zitat antworten Zitat
Benutzerbild von jaenicke
jaenicke

Registriert seit: 10. Jun 2003
Ort: Berlin
9.648 Beiträge
 
Delphi 11 Alexandria
 
#2

AW: Verweis auf Interface-Instanz weitergeben - ist das erlaubt?

  Alt 27. Feb 2024, 10:41
Du hast dort gar keine Referenzzählung, denn du leitest von TForm ab. In den visuellen Komponenten sind zwar die Interface-Routinen vorhanden, so dass du direkt Interfaces implementieren kannst, aber eben ohne Referenzzählung.

Wenn du also ein solches Objekt in einer Interfacereferenz speicherst und das Objekt freigegeben wird, zeigt die Interfacereferenz auf ein ungültiges Objekt. Allerdings ist sie deshalb nicht 0, was genau das Problem an der Stelle ist. Deshalb muss man dort aufpassen, was man tut.

Ich habe jetzt das Problem, dass manchmal bei den Aufrufen von GetIfTestImpl bzw. dem Property IfTestImpl als Ergebnis nil zurück kommt, obwohl die interne Klassenvariable _ifTestImpl gesetzt ist.
Das kannst du nicht im Debugger nachstellen und dort prüfen?
Sebastian Jänicke
AppCentral
  Mit Zitat antworten Zitat
Benutzerbild von himitsu
himitsu

Registriert seit: 11. Okt 2003
Ort: Elbflorenz
44.184 Beiträge
 
Delphi 12 Athens
 
#3

AW: Verweis auf Interface-Instanz weitergeben - ist das erlaubt?

  Alt 27. Feb 2024, 11:00
Eigentlich ja, aber nein.

TComponent besitzt eine Standardimplementation für Interfaces und in Dieser ist die Referenzzählung des Interfaces inktiv.
Interfaces auf TComponent sollten daher immer nur kurzfristig verwendet und dann freigegeben (NIL gesetzt, bzw. Variable läuft aus dem Scope).
$2B or not $2B

Geändert von himitsu (27. Feb 2024 um 13:23 Uhr)
  Mit Zitat antworten Zitat
Bodenseematze

Registriert seit: 10. Jul 2023
68 Beiträge
 
#4

AW: Verweis auf Interface-Instanz weitergeben - ist das erlaubt?

  Alt 27. Feb 2024, 13:12
Wenn du also ein solches Objekt in einer Interfacereferenz speicherst und das Objekt freigegeben wird, zeigt die Interfacereferenz auf ein ungültiges Objekt.
Das ist mir schon klar - das sollte auch nicht passieren...


Allerdings ist sie deshalb nicht 0, was genau das Problem an der Stelle ist. Deshalb muss man dort aufpassen, was man tut.
Die Klassen-Instanz, auf die der Interface-Verweis zeigt, wird als letztes abgebaut --> das sollte eigentlich passen
Mir kommt es so vor, als ob durch das Verlassen der Get-Methode der Interface-Zeiger abgeräumt und auf nil gesetzt wird...

Das kannst du nicht im Debugger nachstellen und dort prüfen?
Hmm, ich habe den oben geposteten Beispielcode mal vervollständigt und als Mini-Delphi-Projekt angelegt.
Da muss noch ein anderes (für mich gerade nicht sichtbares) Problem bestehen - da kommt jetzt bei mir beim Start im Debugger eine Access-Violation;
allerdings ohne, dass ich den Call-Stack sehen kann

Ich habe das Projekt mal als .zip angehängt...
Angehängte Dateien
Dateityp: zip IfTest.zip (6,5 KB, 1x aufgerufen)
  Mit Zitat antworten Zitat
Bodenseematze

Registriert seit: 10. Jul 2023
68 Beiträge
 
#5

AW: Verweis auf Interface-Instanz weitergeben - ist das erlaubt?

  Alt 27. Feb 2024, 13:20
Interfaces auf TComponent sollten daher immer nur nurzfristig verwendet und dann freigegeben (NIL gesetzt, bzw. Variable läuft aus dem Scope)
Heisst das, das dann doch jemand die Interface-Zeiger automatisch bei verlassen des Scopes abräumt?
Wenn ich das in einer Klassenvariablen speichere, verliert es doch seinen Scope erst dann, wenn die Klasse abgeräumt wird, oder?

Gibt es eigentlich einen Unterschied (im Ergebnis-Zeiger) bei den folgenden Methoden, die Variable myIf zu setzen (Voraussetzung ist natürlich, dass MyClassInst das Interface implmentiert)?
Delphi-Quellcode:
var myIf : IMyInterface;
myIf := MyClassInst as IMyInterface;
myIf := IMyInterface( MyClassInst );
Supports( MyClassInst, IMyInterface, myIf );

Geändert von Bodenseematze (27. Feb 2024 um 13:30 Uhr)
  Mit Zitat antworten Zitat
Benutzerbild von himitsu
himitsu

Registriert seit: 11. Okt 2003
Ort: Elbflorenz
44.184 Beiträge
 
Delphi 12 Athens
 
#6

AW: Verweis auf Interface-Instanz weitergeben - ist das erlaubt?

  Alt 27. Feb 2024, 14:28
Das macht Delphi der immer, Managed Types automatisch aufräumen.
Interface, Variant, LongStrings (WideString und alle Delphi-Strings, außer ShortString-Typen) und dynamische Arrays.

Im Falle von Interface-Variablen wird bei <>nil ein intf._Release ausgeführt und die Variable quasi auf nil gesetzt.

Wurde also das TObjekt bereits freigegeben (Destroy/Free aufgerufen, oder dessen Owner oder Parent wurde freigegeben)
und nachfolgend wird der Variable was neues zugewiesen oder die Variable wird Freigegeben (z.B. läuft aus dem Scope),
dann täte es im _Release knallen.


Alternativ kein Interface auf das TComponente selbst, sondern IM Component ein Interface,
also z.B. TInterfacedPersistent als Vorfahre. (ich glaub es gab auch einen TComponentNachfahre, der sowas hat)
$2B or not $2B

Geändert von himitsu (27. Feb 2024 um 14:35 Uhr)
  Mit Zitat antworten Zitat
Benutzerbild von jaenicke
jaenicke

Registriert seit: 10. Jun 2003
Ort: Berlin
9.648 Beiträge
 
Delphi 11 Alexandria
 
#7

AW: Verweis auf Interface-Instanz weitergeben - ist das erlaubt?

  Alt 27. Feb 2024, 14:30
Da muss noch ein anderes (für mich gerade nicht sichtbares) Problem bestehen - da kommt jetzt bei mir beim Start im Debugger eine Access-Violation;
allerdings ohne, dass ich den Call-Stack sehen kann
Delphi 7 halt...
In einer aktuelleren Version (hier die Community Edition) siehst du die Ursache:
screenshot-2024-02-27-131554.png

Ursache:
Delphi-Quellcode:
constructor TMyMainForm.CreateMe( owner_ : TComponent );
begin
   inherited Create( owner_ );
Besser:
Delphi-Quellcode:
constructor TMyMainForm.CreateMe( owner_ : TComponent );
begin
   inherited;
Wenn sich die Methodensignatur des geerbten Aufrufs nicht ändert, sollte man nur inherited schreiben. An anderer Stelle fehlte das inherited, so dass die Initialisierung des Formulars fehlte.

Grundregel:
Wenn es irgendwie anders geht, sollte man Konstruktoren NIE NIE NIE anders als Create nennen. Das ist wirklich nur eine Notlösung (und den Sinn verstehe ich hier nicht, da es zumindest in dem gekürzten Beispiel ohne viel einfacher geht).

Ein weiterer Fehler:
FormCreate und btOKClick waren unter public statt unter published (weshalb diese Eventhandler automatisch direkt nach der Klassendeklaration erzeugt werden, wo sie published sind, auch ohne das Wort). Unter public können sie aber nicht gefunden werden.

Ansonsten kann ich keine Probleme feststellen, auch nicht mit Delphi 7. Was muss ich tun?

Gibt es eigentlich einen Unterschied (im Ergebnis-Zeiger) bei den folgenden Methoden, die Variable myIf zu setzen (Voraussetzung ist natürlich, dass MyClassInst das Interface implmentiert)?
In aktuellen Delphiversionen gibt es da keinen Unterschied mehr. In älteren Versionen konnte man nicht einfach auf das Interface casten. Ich weiß nicht mehr, wann das geändert wurde.
Sebastian Jänicke
AppCentral
  Mit Zitat antworten Zitat
Benutzerbild von himitsu
himitsu

Registriert seit: 11. Okt 2003
Ort: Elbflorenz
44.184 Beiträge
 
Delphi 12 Athens
 
#8

AW: Verweis auf Interface-Instanz weitergeben - ist das erlaubt?

  Alt 27. Feb 2024, 14:58
Ja, seit einer ganzen Weile existiert ein virtuelles Interface, welches TInterface nach TObject casten kann, z.B. einfach mit AS. (seit mindestens D2009/XE)

Ansonsten implementiert TComponent schon seit vielen Jahren auch noch ein IInterfaceComponentReference, welches eine Methode GetComponent bietet.
Delphi-Referenz durchsuchenIInterfaceComponentReference.GetComponent
$2B or not $2B
  Mit Zitat antworten Zitat
Bodenseematze

Registriert seit: 10. Jul 2023
68 Beiträge
 
#9

AW: Verweis auf Interface-Instanz weitergeben - ist das erlaubt?

  Alt 27. Feb 2024, 15:31
Ursache:
Delphi-Quellcode:
constructor TMyMainForm.CreateMe( owner_ : TComponent );
begin
   inherited Create( owner_ );
Besser:
Delphi-Quellcode:
constructor TMyMainForm.CreateMe( owner_ : TComponent );
begin
   inherited;
Wenn sich die Methodensignatur des geerbten Aufrufs nicht ändert, sollte man nur inherited schreiben. An anderer Stelle fehlte das inherited, so dass die Initialisierung des Formulars fehlte.
Du hast natürlich Recht - aber das war nicht der Fehler; das war nur temporär geändert (und nicht wieder zurück geändert), weil ich damit gespielt hatte, die MyMainForm testweise nur von TForm abzuleiten (und die hat ja kein CreateMe-Konstruktor) - dann kam die Access Violation "später"...

Grundregel:
Wenn es irgendwie anders geht, sollte man Konstruktoren NIE NIE NIE anders als Create nennen. Das ist wirklich nur eine Notlösung (und den Sinn verstehe ich hier nicht, da es zumindest in dem gekürzten Beispiel ohne viel einfacher geht).
Der Grund dafür ist, dass ich den Konstruktor virtuell brauche, damit in abgeleiteten Klassen deren Konstruktor aufgerufen wird...
...und ich kann doch den Standard "Create"-Konstruktor nicht als virtuell überschreiben / neu schreiben, da dieser nicht virtuell ist - oder geht das irgendwie?
Deswegen der neue Konstruktor "CreateMe" in TMyBaseForm und eine "Umlenkumg" auf diesen im Standard-Konstruktor "Create"...
Oder verstehe ich da was falsch?

Was aber tatsächlich gefehlt hatte, war das inherited in TMyBaseForm auf den Basis-Konstruktor von TForm:
Delphi-Quellcode:
constructor TMyBaseForm.CreateMe( owner_ : TComponent );
var
   ifTestImplMy : ITestImpl;
begin
   inherited Create( owner_ );

   _ifTestImpl := nil;
   if ( Assigned(owner_) and
        Supports(owner_, ITestImpl, ifTestImplMy) ) then begin
      SetIfTestImpl( ifTestImplMy );
   end;
end;
Ein weiterer Fehler:
FormCreate und btOKClick waren unter public statt unter published (weshalb diese Eventhandler automatisch direkt nach der Klassendeklaration erzeugt werden, wo sie published sind, auch ohne das Wort). Unter public können sie aber nicht gefunden werden.
Das verstehe ich nicht so richtig - kannst Du da nochmal anders/genauer erklären?
Ich habe den Sinn vom "published" sowieso noch nie so richtig verstanden...

Ansonsten kann ich keine Probleme feststellen, auch nicht mit Delphi 7. Was muss ich tun?
Ich leider auch nicht - in dem Beispiel-Projekt funktioniert die Auswertung des Interfaces
Also muss im realen Programm noch irgendwas anders / komplizierter sein - ich muss da nochmal genauer schauen...


Ansonsten implementiert TComponent schon seit vielen Jahren auch noch ein IInterfaceComponentReference, welches eine Methode GetComponent bietet.
Delphi-Referenz durchsuchenIInterfaceComponentReference.GetComponent
Sehr interessant - kannte ich noch nicht
  Mit Zitat antworten Zitat
Benutzerbild von himitsu
himitsu

Registriert seit: 11. Okt 2003
Ort: Elbflorenz
44.184 Beiträge
 
Delphi 12 Athens
 
#10

AW: Verweis auf Interface-Instanz weitergeben - ist das erlaubt?

  Alt 27. Feb 2024, 16:00
Zitat:
und ich kann doch den Standard "Create"-Konstruktor nicht als virtuell überschreiben / neu schreiben, da dieser nicht virtuell ist - oder geht das irgendwie?
Warum nicht?

siehe TObject.Create und TComponent.Create

Ob man beim "Verdecken" micht einem neuen Contructor noch ein reintroduce benötigt, das mehrkt man dann schon.
Bei gleicher Signatur (Parameter) geht nur verdecken. Und ansonsten muß man noch überlegen, ob ein overload benötigt wird, um alternativ auch die Constructoren der Vorfahren aufrufen zu können.
$2B or not $2B
  Mit Zitat antworten Zitat
Antwort Antwort
Seite 1 von 2  1 2      

 

Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 12:20 Uhr.
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz