Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Supports(..) liefert Referenz welche AV auslöst (https://www.delphipraxis.net/183554-supports-liefert-referenz-welche-av-ausloest.html)

Der schöne Günther 19. Jan 2015 18:50

Delphi-Version: XE7

Supports(..) liefert Referenz welche AV auslöst
 
Ich möchte kein konkretes Problem lösen. Ich möchte nur verstehen warum folgender Code mit der Zeile
Delphi-Quellcode:
if castSuccessful then myIntf.doSomething();
eine
Zitat:

Im Projekt Project3.exe ist eine Exception der Klasse $C0000005 mit der Meldung 'access violation at 0x00000001: read of address 0x00000001' aufgetreten.
wirft:

Delphi-Quellcode:
program Project3;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils;

type
   IMyInterface = interface
   ['{88DDD9A5-F4BC-47B9-9240-31B1C986F230}']
      procedure doSomething();
   end;

   TMyClass = class(TInterfacedObject, IMyInterface)
      public
            constructor Create();
         procedure doSomething();
   end;

procedure justSupportsThings();
var
   myObject:   TObject;
   myIntf:      IMyInterface;
   castSuccessful:   Boolean;
begin
   myObject := TMyClass.Create();
   castSuccessful := Supports(myObject, IInterface, myIntf);
   if castSuccessful then myIntf.doSomething();
end;

constructor TMyClass.Create();
begin
   _AddRef(); // Damit mich das Supports(..) nicht abräumt
end;

procedure TMyClass.doSomething;
begin
   // nop
end;

begin
  try
   justSupportsThings();
  except
   on E: Exception do
     Writeln(E.ClassName, ': ', E.Message);
  end;
  readln;
end.


Mir ist klar, dass im Aurfuf
Delphi-Quellcode:
Supports(myObject, IInterface, myIntf);
die letzten beiden Parameter nicht wirklich zusammenpassen, denn
Delphi-Quellcode:
myIntf
ist vom Typ
Delphi-Quellcode:
IMyInterface = Interface(IInterface)
.
Bedeutet das etwa dass
Delphi-Quellcode:
Supports(..)
die Referenz nur soweit "befüllt" wie ich mit der GUID angebe?
Ich habe mich mit den ganzen Dingen im Hintergrund (VMT, all das) nie beschäftigt...

Sir Rufo 19. Jan 2015 19:37

AW: Supports(..) liefert Referenz welche AV auslöst
 
Nur so fürs Protokoll, versuche es mal so
Delphi-Quellcode:
program dp_183554;

{$APPTYPE CONSOLE}
{$R *.res}

uses
  System.SysUtils;

type
  IMyInterface = interface
    ['{88DDD9A5-F4BC-47B9-9240-31B1C986F230}']
    procedure doSomething( );
  end;

  TMyClass = class( TInterfacedObject, IMyInterface )
  public
    constructor Create( );
    procedure doSomething( );
  end;

procedure justSupportsThings( );
var
  myObject: TObject;
  myIntf: IMyInterface;
  castSuccessful: Boolean;
begin
  myObject := TMyClass.Create( );
  castSuccessful := Supports( myObject, IMyInterface, myIntf );
  if castSuccessful
  then
    myIntf.doSomething( );
end;

constructor TMyClass.Create( );
begin
  inherited;
  // Schwachfug
  //  _AddRef(); // Damit mich das Supports(..) nicht abräumt
end;

procedure TMyClass.doSomething;
begin
  // nop
end;

begin
  try
    justSupportsThings( );
  except
    on E: Exception do
      Writeln( E.ClassName, ': ', E.Message );
  end;
  readln;

end.

Der schöne Günther 19. Jan 2015 20:17

AW: Supports(..) liefert Referenz welche AV auslöst
 
Natürlich, der Leitsatz aus deiner Signatur greift einfach immer. 8-)

Aber das war mir klar, meine Frage ist ja auch eine andere

Uwe Raabe 19. Jan 2015 21:25

AW: Supports(..) liefert Referenz welche AV auslöst
 
Supports liefert dir im dritten Parameter einen Zeiger auf die VMT genau des Interfaces, das du abfragst. Da in diesem Fall die Typsicherheit nicht greift, da der Parameter typlos ist, musst du selbst dafür sorgen, daß die Interface-Variable dort auch den passenden Typ hat (nämlich den, den du abfragst).

Sir Rufo 19. Jan 2015 22:40

AW: Supports(..) liefert Referenz welche AV auslöst
 
Ihr müsst ja auch nicht gleich alle auf mir rumhacken :mrgreen:

Dann versuch mal das hier
Delphi-Quellcode:
type
  IMyInterface = interface
    ['{88DDD9A5-F4BC-47B9-9240-31B1C986F230}']
    procedure doSomething( );
  end;

  TMyClass = class( TInterfacedObject, IInterface, IMyInterface ) // <- Augen auf
  public
    constructor Create( );
    procedure doSomething( );
  end;

procedure justSupportsThings( );
var
  myObject: TObject;
  myIntf: IMyInterface;
  castSuccessful: Boolean;
begin
  myObject := TMyClass.Create( );
  castSuccessful := Supports( myObject, IInterface, myIntf );
  if castSuccessful
  then
    myIntf.doSomething( ); // <- beim Eierkauf - Keine Exception mehr :o)
end;

Stevie 19. Jan 2015 22:59

AW: Supports(..) liefert Referenz welche AV auslöst
 
Hach ja, das alte Problem, dass "vererbte" Interfaces nicht implizit in der Klasse landen...

Uwe Raabe 20. Jan 2015 08:56

AW: Supports(..) liefert Referenz welche AV auslöst
 
Hier mal das Originalbeispiel etwas erweitert. Man vergleiche die Reihenfolge der Methodenaufrufe im Source mit der der tatsächlich ausgeführten Methoden.

Delphi-Quellcode:
program Project4;

{$APPTYPE CONSOLE}
{$R *.res}

uses
  System.SysUtils,
  System.Classes;

type
  IMyInterface = interface
    ['{88DDD9A5-F4BC-47B9-9240-31B1C986F230}']
    procedure doSomething();
  end;

  IMyInterface2 = interface
    ['{D9B8FC6D-2F71-4E31-A5E7-0CCA98146E78}']
    procedure doSomeotherthing();
  end;

  TMyClass = class(TInterfacedPersistent, IMyInterface, IMyInterface2)
  public
    procedure doSomething();
    procedure doSomeotherthing();
  end;

procedure justSupportsThings();
var
  myObject: TObject;
  myIntf: IMyInterface;
  myIntf2: IMyInterface2;
begin
  myObject := TMyClass.Create();
  try
    if Supports(myObject, IMyInterface, myIntf) then
      myIntf.doSomething();

    if Supports(myObject, IMyInterface2, myIntf2) then
      myIntf2.doSomeotherthing();

    if Supports(myObject, IMyInterface2, myIntf) then
      myIntf.doSomething();

    if Supports(myObject, IMyInterface, myIntf2) then
      myIntf2.doSomeotherthing();

  finally
    myObject.Free;
  end;
end;

procedure TMyClass.doSomeotherthing;
begin
  Writeln('Someotherthing');
end;

procedure TMyClass.doSomething;
begin
  Writeln('Something');
end;

begin
  try
    justSupportsThings();
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  readln;

end.

himitsu 20. Jan 2015 11:20

AW: Supports(..) liefert Referenz welche AV auslöst
 
Zitat:

Delphi-Quellcode:
_AddRef(); // Damit mich das Supports(..) nicht abräumt

Da ist aber jemand selber Schuld!

Entweder man benutzt nur Interface-Referenzen oder nur Objekt-Referenzen.
Nur z.B. bei TComponent-Nachfahren kann man Beides benutzen, da dort die Referenzzählung deaktiviert wurde.

Sir Rufo 20. Jan 2015 11:49

AW: Supports(..) liefert Referenz welche AV auslöst
 
@himitsu

Delphi-Referenz durchsuchenTInterfacedPersistent ;)

Und man kann sich auch eine eigene Interfaced-Klasse bauen, die keine Referenzzählung hat.

mjustin 20. Jan 2015 12:06

AW: Supports(..) liefert Referenz welche AV auslöst
 
Zitat:

Zitat von himitsu (Beitrag 1287123)
Nur z.B. bei TComponent-Nachfahren kann man Beides benutzen, da dort die Referenzzählung deaktiviert wurde.

Benutzen schon, aber eine Komponente einfach wie im folgenden Beispiel um ein Interface zu erweitern geht nur um den Preis von Memory Leaks. Der folgende Code erzeugt ein Leak einer Instanz von TMyImplementation:
Delphi-Quellcode:
program LeakTest;

uses
  Classes;

type
  MyInterface = interface
  end;

  TMyImplementation = class(TComponent, MyInterface)
  end;

  TMyContainer = class(TObject)
  private
    FInt: MyInterface;
  public
    property Impl: MyInterface read FInt write FInt;
  end;

var
  C: TMyContainer;
begin
  ReportMemoryLeaksOnShutdown := True;

  C := TMyContainer.Create;
  C.Impl := TMyImplementation.Create(nil);
  C.Free;
end.
Das Memory Leak ergibt sich nur bei TComponent als Basisklasse. Mit TInterfacedObject als Basisklasse wird das über Interface verwaltete Feld FInt beim Destroy mit freigegeben, und kein Leak bleibt zurück.

Unter Stackoverflow kann man den Hintergrund dieses Verhaltens detailliert nachlesen.
Why do interface implementations based on TComponent leak memory?

himitsu 20. Jan 2015 12:10

AW: Supports(..) liefert Referenz welche AV auslöst
 
Sir Rufo:
Drum "z.B." ... halt Alles, wo die Referenzzählung nicht für die Freigabe benutzt wird.

@mjustin:
Nein, das Leak ist her nur, weil TComponent/TInterfacedObject nicht über die Referenzzählung freigegeben wird, sondern ausschließlich über das Free der Objektinstanz.
Das wurde absichtlich so gemacht, damit die Instanzen nur von der VCL verwaltet werden und man dennoch Interfaces benutzen kann (nur für Funktionszugriffe und nicht für die Freigabe).

Also bist DU an den Leak Schuld, denn du versuchst ein Interface über die Referenzzzählung freizugeben, welches darüber nicht freigegeben wird.

mjustin 20. Jan 2015 12:14

AW: Supports(..) liefert Referenz welche AV auslöst
 
Zitat:

Zitat von himitsu (Beitrag 1287132)
Nein, das Leak ist her nur, weil TComponent/TInterfacedObject nicht über die Referenzzählung freigegeben wird, sondern ausschließlich über das Free der Objektinstanz.

Wird hier "Compilermagie" anstelle Referenzzählung verwendet? (Nach der Antwort Nummer eins auf Stackoverflow wird Referenzzählung verwendet, diese aber scheitert da die _Release Methode bei TComponent anders als bei TInterfacedObject implementiert ist, um die Lebenszeit über Objektbesitzerschaft zu steuern). Das Free führt daher - wenn ich es richtig verstehe - in beiden Fällen zu einem Dekrementieren des Referenzzählers und dem dadurch aufgerufenen _Release.

himitsu 20. Jan 2015 12:31

AW: Supports(..) liefert Referenz welche AV auslöst
 
Zitat:

Delphi-Quellcode:
function TComponent._Release: Integer;
begin
  if FVCLComObject = nil then
    Result := -1   // -1 indicates no reference counting is taking place
  else
    Result := IVCLComObject(FVCLComObject)._Release;
end;

Nein, TComponent wird niemals über eine Interface-Referenz freigegeben.
TComponent wird vorwiegend innerhalb der VCL verwendet und Diese gibt ausschließlich ihre Instanzen über Free frei,
da es z.B. keine Weak-Referenzen für Objekte gibt, welche bei Freigabe auf nil gesetzt werden.

Es wird maximal das in TComponent gekapselte Interface freigegeben, wenn RefCount auf 0 fällt.
z.B. bei sowas wie TXMLDokument TVCLAutoObject oder TActiveFormControl (spezielles TActiveXControl), wo die eigentliche Funktion in ein Interface weitergeleitet wird. (siehe IVCLComObject)


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