Delphi-PRAXiS
Seite 1 von 2  1 2      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Delphi Verweis auf Interface-Instanz weitergeben - ist das erlaubt? (https://www.delphipraxis.net/214721-verweis-auf-interface-instanz-weitergeben-ist-das-erlaubt.html)

Bodenseematze 27. Feb 2024 09:17

Verweis auf Interface-Instanz weitergeben - ist das erlaubt?
 
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 :wink:)

jaenicke 27. Feb 2024 09:41

AW: Verweis auf Interface-Instanz weitergeben - ist das erlaubt?
 
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.

Zitat:

Zitat von Bodenseematze (Beitrag 1533913)
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?

himitsu 27. Feb 2024 10:00

AW: Verweis auf Interface-Instanz weitergeben - ist das erlaubt?
 
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).

Bodenseematze 27. Feb 2024 12:12

AW: Verweis auf Interface-Instanz weitergeben - ist das erlaubt?
 
Liste der Anhänge anzeigen (Anzahl: 1)
Zitat:

Zitat von jaenicke (Beitrag 1533917)
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...


Zitat:

Zitat von jaenicke (Beitrag 1533917)
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...

Zitat:

Zitat von Bodenseematze (Beitrag 1533913)
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...

Bodenseematze 27. Feb 2024 12:20

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

Zitat von himitsu (Beitrag 1533923)
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 );

himitsu 27. Feb 2024 13:28

AW: Verweis auf Interface-Instanz weitergeben - ist das erlaubt?
 
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)

jaenicke 27. Feb 2024 13:30

AW: Verweis auf Interface-Instanz weitergeben - ist das erlaubt?
 
Liste der Anhänge anzeigen (Anzahl: 1)
Zitat:

Zitat von Bodenseematze (Beitrag 1533927)
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:
Anhang 56673

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?

Zitat:

Zitat von Bodenseematze (Beitrag 1533928)
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.

himitsu 27. Feb 2024 13:58

AW: Verweis auf Interface-Instanz weitergeben - ist das erlaubt?
 
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

Bodenseematze 27. Feb 2024 14:31

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

Zitat von jaenicke (Beitrag 1533933)
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"...

Zitat:

Zitat von jaenicke (Beitrag 1533933)
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;
Zitat:

Zitat von jaenicke (Beitrag 1533933)
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... :oops:

Zitat:

Zitat von jaenicke (Beitrag 1533933)
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...


Zitat:

Zitat von himitsu (Beitrag 1533934)
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 :wink:

himitsu 27. Feb 2024 15:00

AW: Verweis auf Interface-Instanz weitergeben - ist das erlaubt?
 
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 :angle:

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.


Alle Zeitangaben in WEZ +1. Es ist jetzt 19:04 Uhr.
Seite 1 von 2  1 2      

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