Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi Mischen von Interface- und Klassenreferenzen (https://www.delphipraxis.net/195725-mischen-von-interface-und-klassenreferenzen.html)

mjustin 20. Mär 2018 13:45

Delphi-Version: 5

Mischen von Interface- und Klassenreferenzen
 
Hallo,

im neuen Blogartikel "Don't mix objects and interfaces" erläutert Dalija Prasnikar die Probleme die entstehen wenn Objekte und Interface vermischt werden.

Ihre Codebeispiel sind überraschend, denn wie hier in der DP gelegentlich geschrieben wurde kann in Factorymethoden eine interne Instanz der Klasse erzeugt werden und am Ende eine Interfacereferenz zurückgegeben:

Delphi-Quellcode:
function CreateFoo: IFoo;
var
  TmpFoo: TFoo;
begin
  // Instanz der Klasse TmpFoo erzeugen, die von TInterfacedObject abgeleitet ist und IFoo implementiert
  TmpFoo := TmpFoo.Create();
  // diverse Properties setzen die nicht Teil des IFoo Interface sind
  TempFoo.X := 1;
  // ...

  // Interface zurückgeben
  Result := TmpFoo;
end;
Laut Blogartikel ist allerdings das

TmpFoo := TmpFoo.Create();

ein "huge no-no". Ist das Codebeispiel dennoch eine saubere Verwendung von Interfaces?

Namenloser 20. Mär 2018 13:57

AW: Mischen von Interface- und Klassenreferenzen
 
In diesem speziellen Fall sollte es keine Probleme verursachen.

Gefährlich ist aber z.B. sowas:
Delphi-Quellcode:
procedure DoSomethingWithFoo(Foo: IFoo);
begin
end;

function CreateFoo: IFoo;
var
  TmpFoo: TFoo;
begin
  TmpFoo := TmpFoo.Create();
  TempFoo.X := 1;

  DoSomethingWithFoo(TmpFoo);

  Result := TmpFoo;
end;
In dem Fall würde es crashen, weil intern grob folgendes passiert:
Delphi-Quellcode:
procedure DoSomethingWithFoo(Foo: IFoo);
begin
end;

function CreateFoo: IFoo;
var
  TmpFoo: TFoo;
  TmpInterface: IFoo;
begin
  TmpFoo := TmpFoo.Create(); // RefCount = 0
  TempFoo.X := 1;

  TmpInterface := TmpFoo; // inc(RefCount) (= 1)
  DoSomethingWithFoo(TmpInterface);
  TmpInterface := nil; // dec(RefCount) (= 0) -> TmpFoo.Free;
 
  Result := TmpFoo; // inc(RefCount)... crash, weil Objekt schon freigegeben ist
end;
Es muss natürlich nicht genau an der Stelle crashen, sondern kann auch irgendwann später crashen.

Uwe Raabe 20. Mär 2018 14:18

AW: Mischen von Interface- und Klassenreferenzen
 
Die Äußerungen beziehen sich auch nur auf den Fall, wo auch ein Reference-Counting stattfindet - genauer dieses zum Destroy der Instanz führt. Dies gilt z.B. bei allen Abkömmlingen von
Delphi-Quellcode:
TInterfacedObject
. Damit im Create einer solchen Klasse das RefCounting keinen Ärger macht (dort gibt es nämlich noch keine Interface-Referenz, die das Objekt am Leben halten könnte), wird im
Delphi-Quellcode:
TInterfacedObject.NewInstance
der RefCount künstlich auf 1 gesetzt:
Delphi-Quellcode:
// Set an implicit refcount so that refcounting during construction won't destroy the object.
class function TInterfacedObject.NewInstance: TObject;
begin
  Result := inherited NewInstance;
  TInterfacedObject(Result).FRefCount := 1;
end;
Dies wird dann im AfterConstruction wieder zurückgenommen:
Delphi-Quellcode:
procedure TInterfacedObject.AfterConstruction;
begin
// Release the constructor's implicit refcount
  AtomicDecrement(FRefCount);
end;
Die darauf folgende Zuweisung auf eine Interface-Referenz sorgt dann wieder für das korrekte RefCounting.

Beim Destroy gibt es aber eine sehr ähnliche Problematik, wenn z.B. im Destroy irgendwelche RefCounting relevante Befehle ausgeführt werden. Dort wählt man aber mittlerweile (seit XE7 - in älteren Versionen crasht das noch) einen anderen Weg über ein Flag namens objDestroyingFlag.

Wie immer muss man halt genau wissen was man tut.

Blup 21. Mär 2018 14:04

AW: Mischen von Interface- und Klassenreferenzen
 
Namenloser hat das richtig dargestellt.

Die Factorymethode hat nur eine Aufgabe, das Interface-Objekt zu erstellen.
Das fertige Objekt als Interface zu verwenden, gehört nicht zu dieser Aufgabe.
Wenn das erfoderlich ist, sollte man in zwei Methoden trennen.
Delphi-Quellcode:
function CreateFoo: IFoo;
var
  TmpFoo: TFoo;
begin
  // Instanz der Klasse TmpFoo erzeugen, die von TInterfacedObject abgeleitet ist und IFoo implementiert
  TmpFoo := TmpFoo.Create();
  // diverse Properties setzen die nicht Teil des IFoo Interface sind
  TempFoo.X := 1;
  // ...

  // Interface zurückgeben
  Result := TmpFoo;
end;

function CreateAndInitFoo: IFoo;
begin
  Result := CreateFoo;
  DoSomethingWithFoo(Result);
end;


Alle Zeitangaben in WEZ +1. Es ist jetzt 12:43 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