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ß