Delphi-PRAXiS
Seite 2 von 3     12 3      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Sonstige Fragen zu Delphi (https://www.delphipraxis.net/19-sonstige-fragen-zu-delphi/)
-   -   Delphi Plugin-System mit Interfaces und DLLs (https://www.delphipraxis.net/65830-plugin-system-mit-interfaces-und-dlls.html)

MasterEvil 23. Mär 2006 15:28

Re: Plugin-System mit Interfaces und DLLs
 
also gar nicht freigeben? wird auch das Objekt selber entfernt am Ende?

greetz
Steffen

xaromz 23. Mär 2006 15:45

Re: Plugin-System mit Interfaces und DLLs
 
Liste der Anhänge anzeigen (Anzahl: 1)
Hallo,

das Zerstören des Objekts überlässt Du dem GC. Daher ruf ich auch _Release auf. Durch das manuelle hochzählen des Referenzzählers steht der am Ende auf Eins. Um das Objekt zu zerstören, rufst Du _Release auf, womit der Zähler auf Null geht und das Objekt sich selbst zerstört. Deshalb ist auch kein Free nötig.

Ich häng mal ein Beispiel ran:
Die beiden oberen Buttons erstellen ein Objekt, einmal mit _AddRef, einmal ohne.
Der mittlere Knopf ruft eine Methode auf, der das Objekt als Interface übergeben wird.
Der untere Knopf zerstört das Objekt mit _Release.
Das Objekt gibt seinen Zustand mit einer MessageBox an.

Ich hoffe, damit wird es etwas klarer.

Gruß
xaromz

//Edit:
@shmia: So wie ich das verstanden habe, will er ja das Objekt in seiner Hauptanwendung weiterverwenden, also nicht nur für eine einzige Prozedur erstellen.

negaH 23. Mär 2006 18:22

Re: Plugin-System mit Interfaces und DLLs
 
Der Aufruf von ._AddRef() und ._Release() ist eine sehr schlechte Idee und sollte eigentlich niemals notwendig sein.

Bei der Benutzung von Interfaces in Delphi ist ein Punkt ganz wichtig: Alle Zugriffe auf ein Objekt sollten immer über das Interface erfolgen. Eine manuelle Freigabe der Objekte hinter dem Interface oder des Interfaces selber ist niemals notwendig.


Werden Interfaces als Paramater benutzt so sollte man diese als CONST deklarieren. Dies ist aber keine zwingende Notwendigkeit wenn man zb. die Variable selber in der Funktion noch weiter benutzen möchte. Der Unterschied zwischen CONST und nicht CONST ist nachfolgender:

Delphi-Quellcode:

procedure XYZ(MyIntf: IInterface);
// try                      
//   MyIntf._AddRef;      <- unsichtbarer Code durch den Compiler erzeugt
begin

  MyIntf := nil;        // zulässige Anweisung um das Interface "freizugebenen" innerhalb der lokalen Gültigkeit
//  MyIntf._Release;      <- unsichtbarer Code durch Compiler

  MyIntf := ABC;        // zulässig
//  MyInt._Release;       <- usichtbarer Code
//  ABC._AddRef  
//  MyIntf := ABC;


  MyIntf._Release;     // TÖDLICH und absolut FALSCH, Variable MyIntf ist weiter <> nil und erzeugt AV im
                        // letzten unsichtbaren try finally Block unten
end;
// finally                <- unsichtbarer    
//   MyIntf._Release;
// end;


procedure XYZ(const MyIntf: IInterface);
// <- KEIN try finally durch compiler
begin

  MyIntf := nil; // <- unzulässig da MyIntf ein Konstante
  MyIntf := ABC; // <- unzulässig

  MyIntf._Release; // eventuell TÖDLICH da das Interface danach einen Reference Counter von X -1 hat. Unter
                    // Umständen wird das Objekt hinter dem Interface freigegeben
                    // Es kann also auch erwünscht sein
end;
Man sieht das bei dem Aufruf mit CONST
1.) die Anwendung schneller wird weil der Compiler auf ein try finally Block verzeichten kann
2.) der Source SICHERER wird da man nicht unbeabsichtig wie im ersten Beispiel die Paramatervariable modifizieren kann


Bei der Erzeugung von Interface geht man immer so vor

Delphi-Quellcode:
var
  MyIntf: IInterface;
// try                          <- unsichtbarer Code des Compilers
//   Pointer(MyIntf) := nil;  
begin
  MyIntf := TObjectClass.Create;
  MyIntf.Methode1();
end;
// finally                      <- unsichtbarer Code
//   MyIntf._Release;
// end;
Man sieht auch hier wieder das ein Aufruf von ._AddRef() und ._Release() wiederum TÖDLICHE Konsequenzen haben wird.

Gruß Hagen

xaromz 23. Mär 2006 19:05

Re: Plugin-System mit Interfaces und DLLs
 
Hallo Hagen,
mit Deinen Ausführungen hast Du natürlich recht.
Weiter oben hat MasterEvil aber geschrieben, dass er im Hauptprogramm auf Methoden/Eigenschaften des Objektes zugreift, die nicht per Interface erreichbar sind. Das ist ja wegen der Sichtbarkeit vom "objektorientierten Standpunkt" aus auch gut so. Leider ist so ein Mix aber nicht wirklich gefahrlos möglich, da er zu den beschriebenen Seiteneffekten führt.
Übrigens kann man manchmal auf die beiden Methoden _AddRef und _Release nicht verzichten. Mas muss nur wissen, was man tut (Ich musste mir auch mal meine eigene Implementierung der beiden Methoden schreiben, aber das ist 'ne andere Geschichte).

Gruß
xaromz

jbg 23. Mär 2006 19:41

Re: Plugin-System mit Interfaces und DLLs
 
Ich habe mir für ein Programm ein Klasse geschrieben, die erst auf die Interface-Referenzzählung "hört", wenn ich mittels einer eigenen Methode "Release" (unterschied zu _Release) als Freizugeben markiert habe. Damit kann ich das Objekt auch ohne Interface nutzen. Defekte Plugins zerschießen mir damit auch nicht das gesamte Programm.

Delphi-Quellcode:
type
  TInterfaceNeutralObject = class(TModifyObject)
  private
    FRefCount: Integer;
    FAllowRelease: Boolean;
  protected
    procedure CleanUp; virtual; // hier den Destruktorcode einfügen
  public
    destructor Destroy; override;
    function QueryInterface(const IID: TGUID; out Obj): HRESULT; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;

    procedure Release; // Statt .Free muss Release aufgerufen werden
  end;

{ TInterfaceNeutralObject }

function TInterfaceNeutralObject.QueryInterface(const IID: TGUID; out Obj): HRESULT;
begin
  if GetInterface(IID, Obj) then
    Result := 0
  else
    Result := E_NOINTERFACE;
end;

procedure TInterfaceNeutralObject.Release;
begin
  if (FRefCount = 0) and not FAllowRelease then
  begin
    CleanUp;
    FAllowRelease := True;
    Free;
  end
  else
  begin
    CleanUp;
    FAllowRelease := True;
    if FRefCount = 0 then
      Free;
  end;
end;

procedure TInterfaceNeutralObject.CleanUp;
begin
end;

function TInterfaceNeutralObject._AddRef: Integer;
begin
  Result := -1;
  Inc(FRefCount);
end;

function TInterfaceNeutralObject._Release: Integer;
begin
  Result := -1;
  Dec(FRefCount);
  if (FRefCount <= 0) and FAllowRelease then
    Free;
end;

destructor TInterfaceNeutralObject.Destroy;
begin
  {$IFDEF INTFDEBUG}
  if not FAllowRelease then
    //raise Exception.CreateFmt('InterfaceNeutralObject "%s" was not destroyed by Release', [ClassName]);
    MessageBox(0, PChar(Format('InterfaceNeutralObject "%s" was not destroyed by Release', [ClassName])), 'Error', MB_ICONHAND or MB_OK);
  {$ENDIF INTFDEBUG}
  inherited Destroy;
end;
Aber wenn man es richtig macht, dann sollte man besser ein weiteres Interface deklarieren, dass dann den Zugriff auf die "versteckten" Eigenschaften erlaubt. Dem Plugin-System muss man dieses Interface ja nicht verraten. Alternativ kann man auch eine GetObject: TObject Methode dem Interface hinzufügen, dass dann das dahinterliegende Objekt zurückliefert.

negaH 23. Mär 2006 21:30

Re: Plugin-System mit Interfaces und DLLs
 
Man kann ein Objekt auch gemischt mit Interfaces benutzen, das geht dann so:

Delphi-Quellcode:
var
  Obj: TMyObject;
  Intf: IInterface;
begin
  Obj := TMyObject.Create;
  Intf := Obj;

  Intf.MethodA();
  Obj.MethodA();

end;
Wichtig ist nur:

sobald man eine Interface Variable mit dem einer Objektvariable zugewiesen hat wirkt das automatische Referencecounting der Interfaces und dem Delphi Compiler. D.h. die variable Obj muß und darf nicht mehr über .Free oder so freigegeben werden.

Diese Ausführungen beziehen sich natürlich nur auf Klassen die auch eine Referenzzählung implementieren und diese auch benutzen.

Man kann es auch so machen:

Delphi-Quellcode:

type
  IPublicInterface = interface
    {GUID} 
    function MethodA: Integer;
  end;

implementation

type
  TMyObject = class;

  ISelf = interface
    {GUID}
    function _Self: TMyObject;
  end;

  TMyObject = class(TInterfacedObject, IPublicInterface, ISelf)
    function MethodA: Integer;
    function _Self;
  end;

function TMyObject.MethodA: Integer;
begin
  Result := 0;
end;

function TMyObject._Self: TMyObject;
// Nichts, In EAX == versteckter 1. Parameter ist Self schon drinnen, und Result == EAX
begin
end;


procedure Test;
var
  Intf: IInterface;
begin
  Intf := TMyObject.Create;

  (Intf as ISelf)._Self.MethodA;
end;

Gruß Hagen

mirage228 23. Mär 2006 21:36

Re: Plugin-System mit Interfaces und DLLs
 
Hi Hagen,

Ich glaube bei dir hat sich ein kleiner Schreibfehler eingeschlichen.
Bei der Dekleration der Funktion "_Self" hast Du den Ergebnistypen vergessen:
Delphi-Quellcode:
function _Self: TMyObject; //: TMyObject; fehlte
mfG
mirage228

xaromz 23. Mär 2006 21:41

Re: Plugin-System mit Interfaces und DLLs
 
Hallo,
Zitat:

Zitat von negaH
Wichtig ist nur:

sobald man eine Interface Variable mit dem einer Objektvariable zugewiesen hat wirkt das automatische Referencecounting der Interfaces und dem Delphi Compiler. D.h. die variable Obj muß und darf nicht mehr über .Free oder so freigegeben werden.

Das ist noch nicht alles. Schau Dir mal mein Demoprogramm an. Da wird das Objekt schon durch den Aufruf einer Prozedur (mit dem Objekt als Parameter) freigegeben, weil eben die Referenzzählung durcheinendergerät.

Gruß
xaromz

negaH 24. Mär 2006 04:28

Re: Plugin-System mit Interfaces und DLLs
 
Zitat:

Das ist noch nicht alles. Schau Dir mal mein Demoprogramm an. Da wird das Objekt schon durch den Aufruf einer Prozedur (mit dem Objekt als Parameter) freigegeben, weil eben die Referenzzählung durcheinendergerät.
Nein, die Referenzzählung arbeitet einwandfrei, du machst einfach einen logischen Denkfehler.

Delphi-Quellcode:


procedure TuWas(Intf: IInterface);
// try
//   Intf._AddRef  <-  Referenzcounter ist jetzt +1
begin
end;
// finally
//   Intf._Release; <- Referenzcounter ist jetzt 0, ergo Object wird freigegeben
// end;

procedure Test1;
begin
  TuWas(TMyObject.Create); // <- hier impliziter Aufruf, KEINE SICHTBARE Referenzzählung
end;

procedure Test2;
var
  Intf: IInterface;
begin
  Intf := TMyObject.Create;
  TuWas(Intf);
end;
Bei Test1 existiert defakto KEIN lokaler Gültigkeitsbereich für ein Interface. Ergo wenn man ein Object direkt einer Funktion übergibt die ein Interface erwartet und dieses NICHT als const deklariert hat, so wird das Objekt zerstört. Das ist auch logisch, denn betrachte mal den Fall das man TuWas() aufruft mit einem gerade frisch allozierten Interface das später im Program NICHT weiter benutzt wird. Würde der Compiler anderes arbeiten würde dieses allozierte Interface als Speicherleiche übrig bleiben.

Bei Test2 hat man nun explizit das erzeugte Object in eine Interfacevariable gespeichert. Man hat also damit auch explizit eine Scope=Gültigkeitsbereich der offensichtlich die Scope der lokalen Funktion ist, bzw. des lokalen Stacks. Der Compiler kann nun, weil WIR es IHM auch gesagt haben, das Refenerenzecounting benutzen.

Es lässt sich nun streiten ob es nicht richtiger von Borland gewesen wäre für den Testfall in Test1 temporär eine "unsichtbare" lokale Interfacevariable anzulegen die dann gleich nach Rückkehr von TuWas auf nil gesetzt, ergo freigegeben wird. Das ändert nämlich nichts am Sachverhalt das diese Temporäre unsichtbare Interfacevariable wiederum inetwa so aussähe:

Delphi-Quellcode:
 
//  try
//    Temp := MyObject; // RefCounter == +1
   TuWas(Temp);
//  finally
//    Temp._Release ;   // RefCounter == 0, ergo MyObject.Free
//  end;
Du siehst in jedem Falle würde nach dem Aufruf von TuWas() das Objekt immer fregegeben werden.
Das liegt einfach daran das der Compiler nicht das wissen kann was DU einfach mal so vorraussetzt. Nämnlich das FMyObject ein Feld deiner TForm Klasse ist und somit aus DEINER Sicht eine globale Gültigkeit besitzt.
Demo ist aber tatsächlich nicht so, da FMyObject eine Klasse ist und kein Interface !!

Eines ist also sicher, dieses Verhalten ist durchaus logisch, aber leider nicht so richtig dokumentiert.

Fazit: wenn man mit Objekten und Interfaces arbeiten möchte dann sollte man immer mit Hilfe einer lokalen Interfacevariablen, in der man das Object speichert, arbeiten.

Wenn DU also möchtest das dein FMyObject global zur kompletten laufzeit deines TForm auch gültig bleibt dann musst DU dies dem Compiler auch sagen. Inetwa so

Delphi-Quellcode:
type
  TForm1 = class
    FMyObject: TMyObject;
    FMyObjectIntf: IInterface;
  end;

procedure TForm1.FormCreate();
begin
  FMyObject := TMyObject.Create;
  FMyObjectIntf := FMyObject;
end;
Fertig ! Beim zerstören von TForm1 wird FMyObjectIntf automatisch auf nil gesetzt, ergo auch FMyObject.Free aufgerufen.

Gruß Hagen

xaromz 24. Mär 2006 08:55

Re: Plugin-System mit Interfaces und DLLs
 
Hallo,
Zitat:

Zitat von negaH
Nein, die Referenzzählung arbeitet einwandfrei, du machst einfach einen logischen Denkfehler.

Ich hab' da keinen Denkfehler, keine Angst. Erklärung folgt.
Zitat:

Zitat von negaH
Bei Test1 existiert defakto KEIN lokaler Gültigkeitsbereich für ein Interface. Ergo wenn man ein Object direkt einer Funktion übergibt die ein Interface erwartet und dieses NICHT als const deklariert hat, so wird das Objekt zerstört. Das ist auch logisch, denn betrachte mal den Fall das man TuWas() aufruft mit einem gerade frisch allozierten Interface das später im Program NICHT weiter benutzt wird. Würde der Compiler anderes arbeiten würde dieses allozierte Interface als Speicherleiche übrig bleiben.

Du benutzt aber ein anderes Beispiel als wir hier diskutieren. Bei uns sieht das so aus:
Delphi-Quellcode:
procedure Test1;
begin
  FMyObject := TMyObject.Create;
  TuWas(FMyObject);

  TuWasAnderes(FMyObjekt);
end;
Hier knallts, weil eben der erste Aufruf den Referenzzähler erhöht und wieder vermindert, wodurch das Objekt freigegeben wird. Bei TuWasAnderes ist also das Objekt schon zerstört.
Natürlich ist dieses Verhalten korrekt (was die Referenzzählung von Interfaces angeht), aber, und das ist der Punkt, unerwartet. Wer noch nicht so viel Ahnung von Interfaces hat, würde annehmen, dass FMyObjekt ja auch eine Refernez ist, aber auf das Objekt. Nur gibt's bei Objekten keinen Referenzzähler :( . Darum geht's mir, ich will nur auf diese Stolperfalle hinweisen. Und hier könnte der Delphi-Compiler tatsächlich erkennen, dass ich das Objekt nicht freigeben will und noch einen Referenzblock rumbasteln. Da Delphi das nicht macht, kann man es selbst machen.
Zitat:

Zitat von negaH
Fazit: wenn man mit Objekten und Interfaces arbeiten möchte dann sollte man immer mit Hilfe einer lokalen Interfacevariablen, in der man das Object speichert, arbeiten.

Was übrigens das Gleiche macht, wie ich, nur implizit statt explizit.

Aber wie wäre es eigentlich damit, und hier sind wir wieder beim Thema des Threads, wenn das Objekt zwei Interfaces implementiert, wobei nur das eine an das Plugin weitergegeben wird, während das andere im Hauptprogramm verwendet wird? So bräuchts man sich nicht mit diesem Problem rumschlagen.

Gruß
xaromz


Alle Zeitangaben in WEZ +1. Es ist jetzt 21:29 Uhr.
Seite 2 von 3     12 3      

Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz