![]() |
Delphi-Version: 2006
Reference Counting von TComponent
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:
an alle folgenden Prozeduren übergeben werden:
type
TPorsche = class(TAuto,ISpoiler) ... end;
Delphi-Quellcode:
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:
procedure machWasMit(aPorsche: TPorsche);
procedure machWasMit(aAuto: TAuto); procedure machWasMit(aSpoiler: ISpoiler);
Delphi-Quellcode:
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:
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;
Delphi-Quellcode:
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:
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;
Delphi-Quellcode:
In der Implementation von TComponent konnte ich nichts finden, warum kein Fehler kommt...
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; Meine Fragen sind also: hab ich alles richtig verstanden (Erklärungen im ersten Teil) und wie macht TComponent das? dank und Gruß |
AW: Reference Counting von TComponent
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, ![]() Desweiteren habe ich mir noch ein Zitat zur Seite gelegt, wirklich darüber gegrübelt habe ich noch nicht: Zitat:
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 :stupid: |
AW: Reference Counting von TComponent
Ein Blick in die Doku hätte dafür gereicht
![]() konkret da ![]() und wenn es nicht Tcomponent sein soll, dann geht auch ![]() |
AW: Reference Counting von TComponent
Zitat:
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. |
AW: Reference Counting von TComponent
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
Delphi-Quellcode:
in
CALL DWORD PTR [EAX] + VMTOFFSET IInterface._Release
Delphi-Quellcode:
der System.pas geht mal schief und mal nicht, was auch immer das bedeutet :)
function _IntfClear(var Dest: IInterface): Pointer;
|
AW: Reference Counting von TComponent
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:
Man sollte den Objektzeiger in diesem Fall gar nicht erst verwenden.
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! 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 |
AW: Reference Counting von TComponent
Zitat:
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?) |
Alle Zeitangaben in WEZ +1. Es ist jetzt 17:03 Uhr. |
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