AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Thema durchsuchen
Ansicht
Themen-Optionen

Reference Counting von TComponent

Ein Thema von dpc41 · begonnen am 13. Mär 2013 · letzter Beitrag vom 15. Mär 2013
Antwort Antwort
dpc41

Registriert seit: 13. Mär 2013
2 Beiträge
 
#1

Reference Counting von TComponent

  Alt 13. Mär 2013, 15:38
Delphi-Version: 2006
Hallo zusammen,

ich arbeite mich gerade in das Thema Interfaces mit Delphi ein.

Aus anderen Sprachen kenne ich die unbeschwerte Verwendung eines Objektes entweder als eines seiner implementierten Interfaces oder als eines seiner Objecttypen aus der Vererbungshierachie. Als Beispiel kann ein so deklariertes Object

Delphi-Quellcode:
type

 TPorsche = class(TAuto,ISpoiler)
   ...
 end;
an alle folgenden Prozeduren übergeben werden:

Delphi-Quellcode:
procedure machWasMit(aPorsche: TPorsche);

procedure machWasMit(aAuto: TAuto);

procedure machWasMit(aSpoiler: ISpoiler);
Was ich mittlerweile schon gelernt habe, dass man für diese gemischte Verwendung das Reference Counting für Interfaces "ausschalten" muss/sollte. Dafür wird meistens die Implementierung von TInterfacedObject so überschrieben:

Delphi-Quellcode:
function TMyObject.QueryInterface(const IID: TGUID; out Obj): HResult;
begin

  if GetInterface(IID, Obj) then
    Result := S_OK
  else
    Result := E_NOINTERFACE;

end;

function TMyObject._AddRef: Integer;
begin
  Result := -1 // -1 indicates no reference counting is taking place
end;

function TMyObject._Release: Integer;
begin
  Result := -1 // -1 indicates no reference counting is taking place
end;
Anscheinend ist das allerdings nur die Hälfte der Lösung. Dies verhindert zwar das automatische Freigeben, aber natürlich nicht die eigentlichen Aufrufe von _AddRef und _Release. Man muss sicherstellen, dass bevor man das Object manuell freigibt, alle Interface-Referenzen auf das Object auf nil gesetzt werden. Sonst wird irgendwann nach dem Destructor Aufruf des Object evt. noch mal das _Release der Interface-Reference aufgerufen, die jetzt ins Nirvana zeigt und es passiert ein Fehler. Dies widerspricht dem üblichen Delphi-Programmierstil und ist somit gefährlich, denn bei normalen Object-Referencen ist man gewöhnt, sie einfach nach der Freigabe nur nicht mehr zu benutzen. Sie explizit auf nil zu setzen ist wahrschl. sicherer, aber nicht nötig. Bei Interface-Referencen benutzt der Compiler sie aber immer noch weiter, ohne dass ich den Code dafür geschrieben habe, zB wenn eine Interface-Variable aus dem Scope geht und eben das _release aufgerufen wird. So eine Access Violation lässt sich recht einfach beim Schliessen der Form mit folgendem Code herstellen:

Delphi-Quellcode:
TMyObject = class(TObject,IInterface)
  
    public

      function QueryInterface(const IID: TGUID; out Obj): HResult; virtual; stdcall;
      function _AddRef: Integer; stdcall;
      function _Release: Integer; stdcall;

  end;

  TForm1 = class(TForm)

    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);

  private
    { Private-Deklarationen }

  public
    { Public-Deklarationen }
    
    var
    
      obj: TMyObject;
      intf: IInterface;
     
  end;

var
  Form1: TForm1;


implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin

  Self.obj := TMyObject.Create;

  Self.intf := Self.obj;

end;

procedure TForm1.FormDestroy(Sender: TObject);
begin

// Self.intf := nil;

  self.obj.Free;

end;

{ TMyObject }

function TMyObject.QueryInterface(const IID: TGUID; out Obj): HResult;
begin

  if GetInterface(IID, Obj) then
    Result := S_OK
  else
    Result := E_NOINTERFACE;

end;

function TMyObject._AddRef: Integer;
begin
  Result := -1 // -1 indicates no reference counting is taking place
end;

function TMyObject._Release: Integer;
begin
  Result := -1 // -1 indicates no reference counting is taking place
end;
Wenn ich die Zeile Self.intf := nil in TForm1.FormDestroy einkommentiere, bleibt der Fehler aus. Als nächsten Schritt wollte ich mir also einen Mechanismus überlegen, der die Interface-Referencen automatisch beim Object-zerstören handelt und mir das "normale" Object-Feeling bei Interfaces erlaubt. Weitere Recherchen, und jetzt wird es langsam interessant, brachten mich auf die Spur von TComponent. Leitet man TMyObject von TComponent statt TObject ab und lässt die eigene Implementierung von _addRef usw. weg kommt kein Fehler, obwohl TComponent die Prozeduren gleich implementiert (bis auf ein if FVCLComObject = nil mal abgesehen). Konkret:

Delphi-Quellcode:
  TMyObject = class(TComponent,IInterface)

  end;

  TForm1 = class(TForm)

  private
    { Private-Deklarationen }

  public
    { Public-Deklarationen }
    
    var
    
      compo: TComponent;
      coInt: IInterface;
     
  end;

var
  Form1: TForm1;


implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin

  self.compo := TComponent.Create(nil);

  Self.coInt := Self.compo;

end;

procedure TForm1.FormDestroy(Sender: TObject);
begin

  Self.compo.free;

end;
In der Implementation von TComponent konnte ich nichts finden, warum kein Fehler kommt...
Meine Fragen sind also: hab ich alles richtig verstanden (Erklärungen im ersten Teil) und wie macht TComponent das?

dank und Gruß
  Mit Zitat antworten Zitat
Der schöne Günther

Registriert seit: 6. Mär 2013
6.110 Beiträge
 
Delphi 10 Seattle Enterprise
 
#2

AW: Reference Counting von TComponent

  Alt 13. Mär 2013, 15:58
Hallo -

Ich bin noch relativ neu in Delphi und habe das Thema "Delphi und Interfaces" noch vor mir. Schonmal zurechtgelegt habe ich mir ein Tutorial zum Thema von einem Herrn Kleiner, zu finden hier.

Desweiteren habe ich mir noch ein Zitat zur Seite gelegt, wirklich darüber gegrübelt habe ich noch nicht:
Zitat:
"Wenn eine TXMLDocument-Instanz ohne Eigentümer (Owner) erstellt wird, verhält sie sich wie ein Objekt mit Interface-Unterstützung. Die Freigabe der Instanz erfolgt dann automatisch, nachdem alle Referenzen auf ihr Interface freigegeben wurden. Wird ein TXMLDocument-Objekt mit Eigentümer erstellt, ist dieser wie bei jeder anderen Komponente für die Freigabe verantwortlich."

Tut mir leid falls das nichts zur Lösung beiträgt, aber ich dachte ich werfe es einfach mal in den Raum ohne das Thema wirklich verstanden zu haben
  Mit Zitat antworten Zitat
Benutzerbild von Sir Rufo
Sir Rufo

Registriert seit: 5. Jan 2005
Ort: Stadthagen
9.454 Beiträge
 
Delphi 10 Seattle Enterprise
 
#3

AW: Reference Counting von TComponent

  Alt 13. Mär 2013, 20:37
Ein Blick in die Doku hätte dafür gereicht Delphi-Referenz durchsuchenTComponent

konkret da http://docwiki.embarcadero.com/Libra...ses.TComponent

und wenn es nicht Tcomponent sein soll, dann geht auch Delphi-Referenz durchsuchenTInterfacedPersistent
Kaum macht man's richtig - schon funktioniert's
Zertifikat: Sir Rufo (Fingerprint: ‎ea 0a 4c 14 0d b6 3a a4 c1 c5 b9 dc 90 9d f0 e9 de 13 da 60)

Geändert von Sir Rufo (13. Mär 2013 um 20:39 Uhr)
  Mit Zitat antworten Zitat
Patito

Registriert seit: 8. Sep 2006
108 Beiträge
 
#4

AW: Reference Counting von TComponent

  Alt 14. Mär 2013, 08:56

In der Implementation von TComponent konnte ich nichts finden, warum kein Fehler kommt...
Meine Fragen sind also: hab ich alles richtig verstanden (Erklärungen im ersten Teil) und wie macht TComponent das?

dank und Gruß
Ich denke Du hast es ganz gut verstanden. Dass Du keinen Fehler siehst liegt wohl daran, dass Deine Fehlererkennungsmechanismen nicht richtig funktionieren.

Ich hab deinen Code bei mir mal mit FastMM im Debug-Modus probiert. Es kommt eindeutig ein Zugriff auf freigegebenen Speicher. TComponent hat da keine speziellen Tricks auf Lager.
  Mit Zitat antworten Zitat
dpc41

Registriert seit: 13. Mär 2013
2 Beiträge
 
#5

AW: Reference Counting von TComponent

  Alt 14. Mär 2013, 15:20
Dank an alle für Links und Anmerkungen.

Leite ich TMyObject von TXMLDocument ab kommt kein Fehler, was nicht weiter überrascht, da TXMLDocument von TComponent erbt Übrigens bei beiden mit Owner = nil.

Hab den Test dank des Hinweises von Sir Rufo natürlich auch mit TInterfacedPersistent gemacht und erhalte die Access Violation.

Ich fasse zusammen: egal von was ich TMyObject ableite, in _addRef und _Release wir in allen Fällen Result := -1 gesetzt. Bei TObject, TinterfacedObject, TinterfacedPersistent kommt ein Fehler, bei TComponent und TXMLDocument keiner. Fehlererkennungsmechanismen wie Patito sie erwähnte, habe ich nicht, wobei ich nicht komplett sicher bin, was er damit meint. Der Code, der verwendet wird, ist oben gepostet, keine versteckten try-Blöcke, oder ähnliches.
Also wo ist der Unterschied? Hab mich schon durch die wildesten Assembler Stellen in den System Untits debuggt und finde nichts. Fakt ist die Zeile

CALL DWORD PTR [EAX] + VMTOFFSET IInterface._Release in

function _IntfClear(var Dest: IInterface): Pointer; der System.pas geht mal schief und mal nicht, was auch immer das bedeutet
  Mit Zitat antworten Zitat
Benutzerbild von sx2008
sx2008

Registriert seit: 15. Feb 2008
Ort: Baden-Württemberg
2.332 Beiträge
 
Delphi 2007 Professional
 
#6

AW: Reference Counting von TComponent

  Alt 14. Mär 2013, 16:12
Man muss 4 Fälle unterscheiden.
1.) Von TInterfacedObject abgeleitet
man darf nur den Interfacezeiger speichern und über das Interface auf das Objekt zugreifen
Delphi-Quellcode:
var
  test : IMyInterface; {Interfacezeiger}
  myobj : TMyInterfacedObject; {Objektzeiger}
begin
  // korrekte Anwendung
  test := TMyInterfacedObject.Create;
  test.Machwas;
  test := nil; // gibt das Objekt autom. frei
  
  // falsch
  myobj := TMyInterfacedObject.Create; // (*)
  test := myobj as IMyInterface;
  test.Machwas;
  myobj.MachwasAnderes; // gefährlich
  myobj.Free; // verboten!
Man sollte den Objektzeiger in diesem Fall gar nicht erst verwenden.
Das Problem entsteht in der Zeile die mit (*) markiert ist.
2.) Von TComponent abgeleitete Klasse (Owner=nil)
gleiche Regeln wie 1.)
3.) Von TComponent abgeleitete Klasse (Owner <> nil)
Man darf auf den Objektzeiger zugreifen
Manuelle Freigeben mit Free sollte man vermeiden (dies wird automatisch durch den Owner erledigt)
Wichtig: Bevor der Owner freigeben wurde und damit auch das Objekt, muss man alle Interfacezeiger auf nil setzen
oder die Interfacezeiger müssen zuvor "out-of-Scope" gekommen sein
4.) Klassen mit ausgeschalteter Referenzzählung
Bevor das Objekt freigeben wird, muss man alle Interfacezeiger auf nil setzen
oder die Interfacezeiger müssen zuvor "out-of-Scope" gekommen sein
  Mit Zitat antworten Zitat
Patito

Registriert seit: 8. Sep 2006
108 Beiträge
 
#7

AW: Reference Counting von TComponent

  Alt 15. Mär 2013, 08:03
Dank an alle für Links und Anmerkungen.

Ich fasse zusammen: egal von was ich TMyObject ableite, in _addRef und _Release wir in allen Fällen Result := -1 gesetzt. Bei TObject, TinterfacedObject, TinterfacedPersistent kommt ein Fehler, bei TComponent und TXMLDocument keiner. Fehlererkennungsmechanismen wie Patito sie erwähnte, habe ich nicht, wobei ich nicht komplett sicher bin, was er damit meint.
Deine Fehlermeldung kommt ja irgendwoher. Da du den Speicher nicht mit einem Speichermanager im Debug-Modus überprüfst, siehst du vermutlich nur die Access-Violation von Windows. Windows kann dir aber nicht alle Fehler mitteilen.

Der Speicher-Manager in deinem Programm holt sich von Windows immer ganze Pools von Speicher und gibt die dann auch nicht immer wieder direkt zurück an Windows, sondern behält eventuell den Speicher um ihn für andere Objekte zu verwenden. Somit kann dir Windows nicht immer eine Fehlermeldung geben wenn Du den Speicher anspringst. Die Größe des Objektes und die
aktuelle Ausrichtung im Speicher an Pool-Grenzen können dabei einen Unterschied machen.

Wie gesagt: Dein TComponent und dein TMyObject springen soweit ich sehe im _Release() beide freigegebenen Speicher an. Wenn man Glück hat ist der Speicher zurück an Windows gegeben worden und man bekommt eine Access-Violation. Wenn man Pech hat ist im Speicher irgendein anderes Delphi-Objekt und man ruft irgendeine zufällige Prozedur auf. Es kann auch sein, dass dort im Speicher noch die alte _Release Prozedur steht und nichts passiert.

Fazit: Ohne Debug-Tool für Speicherzugriffe kriegt man nicht wirklich mit was vor sich geht. Idealerweise sagt man da dem Speichermanager, dass er Speicher nicht wiederverwenden soll, und dass er beim Freigeben den Speicher so markieren soll, dass man einen Zugriff auf freigegebenen Speicher an irgendeinem Muster erkennen kann.

PS: Ich habe bei mir im Code das var vor deinen Referenzen weggeleassen (willst du da wirklich Klassen-Variablen haben?)
  Mit Zitat antworten Zitat
Antwort Antwort


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 13:14 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