AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren

Data-Pointer in Interfacevariable casten

Ein Thema von RedOne · begonnen am 14. Jul 2010 · letzter Beitrag vom 16. Jul 2010
Antwort Antwort
RedOne

Registriert seit: 2. Jun 2008
71 Beiträge
 
Delphi XE2 Professional
 
#1

AW: Data-Pointer in Interfacevariable casten

  Alt 15. Jul 2010, 08:23
Merci für Deine Antwort. Wir kommen näher

Der Speicher wird vor der Funktion initialisiert. Ich habe den Code zusammengestrichen, da ist sie mir wohl abhanden gekommen

Zitat:
1) warum verwendest du eine Objektlist wenn du mit Interfaces arbeitest? Gibt auch eine TInterfaceList
Weil ich diese bisher nicht kannte

Zitat:
2) wenn du eine ObjectList verwendest, ist auch OwnsObjects auf false, sonst sind die Objekte beim Free alle wieder weg (auch deine TInterfaceObjects)
OwnsObject setzte ich bis anhin in der Funktion auf false

Ich habe das ganze jetzt mal gemäss Deinem Beispiel umgebaut und es funktioniert fast. Weder gibt es einen Compillier- noch einen Laufzeitfehler
In der Methode "Delete" gebe zu Testzwecken FID aus. Diese ist jedoch immer 0. Beim abfüllen beziehe ich den Text für die Listview direkt aus dem Objekt, die ID ist also beim Start definitiv gesetzt.

Verkürzter Code:
Delphi-Quellcode:
IDataObject = interface( IInterface )
  ['{C94DCAC7-CA42-45D3-95D7-9BC321BA2E2A}']
  procedure Delete;
end;

TAccountData = class( TInterfacedObject )
protected
  FID: Integer;
public
  property ID: Integer read FID write FID;
end;

TAccountDataExtend = class( TAccountData, IDataObject )
public
  procedure Delete;
end;

procedure TFormAccounts.Refresh;
var
  tmp: IInterface;
  i: Integer;
  item: TListItem;
  x: TInterfaceList;
begin
  //Vorgängig Listview aufräumen, Objekte zerstören, Interface Releasen
         
  x:= TInterfaceList.Create;
  try
    GetAllAccounts( x ); //Siehe Funktion unten
    for i:= 0 to x.Count - 1 do begin
      tmp:= x[ i ];
      item:= ListViewMain.Items.Add;
      item.Caption:= IntToStr( TAccountDataExtend( tmp ).ID ); //Zeigt korrekte ID an
      item.Data:= Pointer( tmp );
    end;
  finally
    FreeAndNil( x ); //Ich nehme an die InterfaceList zerstört die Objekte nicht
  end;
end {LoadListView};

procedure GetAllAccounts( _o: TInterfaceList );
var
  tmp: IDataObject;//Oder IInterface?
begin
  for i:= 0 to 4 do begin
    tmp:= TAccountDataExtend.Create;
    TAccountDataExtend( tmp ).FID:= i;
    tmp._AddRef;
    _o.Add( tmp );
  end;
end {GetAllAccounts};


procedure TFormEditBase.ListViewMainClick(Sender: TObject);
var
  obj: IInterface;
  obj3: IDataObject;
begin
  Inherited;
  if ListViewMain.ItemIndex >= 0 then begin
    obj:= IInterface( Pointer( ListViewMain.Selected.Data ));
    if Succeeded( obj.QueryInterface( IDataObject, obj3 )) then begin
      obj3.Delete; //Funktion siehe unten
    end;
  end;
end;

//----------------- TAccountDataExtend
procedure TAccountDataExtend.Delete;
begin
  ShowMessage( IntToStr( FID )); //Gibt immer "0" aus
end {Delete};
Vielen Dank
RedOne

Geändert von RedOne (15. Jul 2010 um 08:39 Uhr)
  Mit Zitat antworten Zitat
Benutzerbild von himitsu
himitsu

Registriert seit: 11. Okt 2003
Ort: Elbflorenz
44.374 Beiträge
 
Delphi 12 Athens
 
#2

AW: Data-Pointer in Interfacevariable casten

  Alt 15. Jul 2010, 08:43
tmp._AddRef; brauchst du bei einer InterfaceList nicht, da diese die Referenzzählung schon richtig behandelt.

also ab Delphi 2010 soll es ja nun so gehn (davor gibt es keinen offiziellen Weg, um aus einen Interface wieder ein Objekt zu machen)
> aber sowas würde ich hier nicht machen
Delphi-Quellcode:
var tmp: IInterface;

tmp:= TAccountDataExtend.Create;
(tmp as TAccountDataExtend).FID:= i;
_o.Add( tmp );
oder es erst noch als Objekt belassen und dann direkt übergeben (später umwandeln lassen)
Delphi-Quellcode:
var tmp: TAccountDataExtend;

tmp := TAccountDataExtend.Create;
tmp.FID:= i;
// aber tmp nicht zwischenzeitlich als Interface verwenden!!!
_o.Add( tmp );
oder es erst noch als Objekt belassen und später in ein Interface umwandeln
Delphi-Quellcode:
var otmp: TAccountDataExtend;
  tmp: IInterface;

otmp := TAccountDataExtend.Create;
otmp.FID:= i;
// aber tmp nicht zwischenzeitlich als Interface verwenden!!!
tmp := otemp;
// jetzt kann man das Interface verwenden
_o.Add( tmp );
oder besser doch so:
Delphi-Quellcode:
var tmp: IInterface;

tmp := TAccountDataExtend.Create(i);
_o.Add( tmp );
also entweder dein FID im Constructor mit übergeben
oder das FID auch im Interface verfügbar machen
Delphi-Quellcode:
IDataObject = interface( IInterface )
  ['{C94DCAC7-CA42-45D3-95D7-9BC321BA2E2A}']
  procedure Delete;
  property FID read ... write ...; // bzw. nur eine SetFID-Prozedur
end;

var tmp: IInterface;

tmp := TAccountDataExtend.Create;
tmp.FID := i; // tmp.SetFID(i);
_o.Add( tmp );
Ein Therapeut entspricht 1024 Gigapeut.

Geändert von himitsu (15. Jul 2010 um 08:48 Uhr)
  Mit Zitat antworten Zitat
RedOne

Registriert seit: 2. Jun 2008
71 Beiträge
 
Delphi XE2 Professional
 
#3

AW: Data-Pointer in Interfacevariable casten

  Alt 15. Jul 2010, 10:30
Macht durchaus Sinn zuerst das Objekt zu erstellen und dann daraus ein Interface zu kreieren:
Delphi-Quellcode:
procedure GetAllAccounts( _o: TInterfaceList );
var
  tmp: TAccountDataExtend;
begin
  for i:= 0 to 4 do begin
    tmp:= TAccountDataExtend.Create;
    tmp.FID:= i;
    _o.Add( tmp );
    tmp._AddRef;
  end;
end {GetAllAccounts};
Sonst funktionierts nämlich nicht
Die Referenzzählung brauche ich meiner Meinung nach weil ich die InterfaceList zerstöre. Ansonsten bringt er eine Exception bei beenden des OnClick-Events.
Er gibt mir jetzt in der Deletefunktion meinen gewünschten Wert aus.
Dafür... im Grid nicht mehr Jetzt zeigt dieses immer "0" an.
Ich bekomme von der Funktion GetAllAccounts eine Interfacelist. Ich sollte diese doch gleich wie beim OnClick-Ereignis in ein TAccountDataExtend casten können?

Delphi-Quellcode:
var
  i: IDataObject;
  obj: IInterface;
  obj3: IDataObject;
  
begin
  obj:= IInterface( Pointer( ListViewMain.Selected.Data ));
  if Succeeded( obj.QueryInterface( IDataObject, obj3 )) then begin
    ShowMessage( TAccountDataExtend( obj3.XYZ ));
  end;
end;
Arbeite übrigens mit Delphi 2007

Vielen Dank
  Mit Zitat antworten Zitat
RedOne

Registriert seit: 2. Jun 2008
71 Beiträge
 
Delphi XE2 Professional
 
#4

AW: Data-Pointer in Interfacevariable casten

  Alt 15. Jul 2010, 14:31
Ich muss übrigens nicht zwingend diese Architektur verwenden. Wenn jemand eine schönere Lösung weiss, so bin ich gerne bereit alles umzubauen
Ich möchte einfach vermeiden dass in der Formklasse zusätzlich zum Grid eine Objektliste gepflegt werden muss und somit die Daten eigentlich doppelt vorhanden sind.
Im Anhang ein bescheidener Versuch diesen Teil der Software UML-mässig abzubilden. Musste mich zuerst im Enterprise Architect zurecht finden... deshalb etwas stümperhaft
Zu den beiden Formulare/Objekte Account und Action's gesellen sich noch einige mehr hinzu, deren Aufbau aber immer gleich ist.

Vielen Dank
Angehängte Grafiken
Dateityp: png uml.PNG (28,6 KB, 5x aufgerufen)

Geändert von RedOne (15. Jul 2010 um 14:38 Uhr)
  Mit Zitat antworten Zitat
brechi

Registriert seit: 30. Jan 2004
823 Beiträge
 
#5

AW: Data-Pointer in Interfacevariable casten

  Alt 15. Jul 2010, 18:51
Also ich hab das auch mit Delphi2007 getestet. Das AddRef brauchst du weil die InterfaceListe zerstört wird aber du immernoch über Data auf das Object zugreifen willst.

So gehts:

Delphi-Quellcode:
type
  IDataObject = interface(IInterface)
    ['{C94DCAC7-CA42-45D3-95D7-9BC321BA2E2A}']
    procedure Delete;
  end;

  IAccountData = interface(IInterface)['{DA626905-CB35-4474-B353-9F8E973709AA}']
    function GetId: Integer;
    procedure SetId(_Id: Integer);
    property ID: Integer read Getid write SetId;
  end;

  TAccountData = class(TInterfacedObject, IAccountData)
  protected
    FID: Integer;
    function Getid: Integer;
    procedure SetId(_Id: Integer);
  public
    destructor Destroy; override;
  end;

  TAccountDataExtend = class(TAccountData, IDataObject)
  public
    procedure Delete;
  end;

procedure TForm9.ListViewMainClick(Sender: TObject);
var
  obj: IInterface;
  obj3: IDataObject;
begin
  inherited;
  if ListViewMain.ItemIndex >= 0 then begin
    obj := IInterface(ListViewMain.Selected.Data);
    if Succeeded(obj.QueryInterface(IDataObject, obj3)) then begin
      obj3.Delete; //Funktion siehe unten
    end;
  end;
end;

procedure TForm9.ListViewMainDeletion(Sender: TObject; Item: TListItem);
begin
  IInterface(Item.Data)._Release;
end;

procedure TForm9.Refresh;
var
  tmp: IAccountData;
  i: Integer;
  item: TListItem;
  x: TInterfaceList;
begin
  //Vorgängig Listview aufräumen, Objekte zerstören, Interface Releasen

  x := TInterfaceList.Create;
  try
    GetAllAccounts(x); //Siehe Funktion unten
    for i := 0 to x.Count - 1 do begin
      if Succeeded(x[i].QueryInterface(IAccountData, tmp)) then begin
        item := ListViewMain.Items.Add;
        item.Caption := IntToStr(tmp.ID); //Zeigt korrekte ID an
        item.Data := Pointer(tmp);
      end;
    end;
  finally
    FreeAndNil(x); //Ich nehme an die InterfaceList zerstört die Objekte nicht
  end;
end {LoadListView};

procedure TForm9.Button1Click(Sender: TObject);
begin
  Refresh;
end;

procedure TForm9.GetAllAccounts(_o: TInterfaceList);
var
  tmp: IAccountData; //Oder IInterface?
  i: integer;
begin
  for i := 0 to 4 do begin
    tmp := TAccountDataExtend.Create;
    tmp.ID := i;
    tmp._AddRef;
    _o.Add(tmp);
  end;
end {GetAllAccounts};

//----------------- TAccountDataExtend

procedure TAccountDataExtend.Delete;
begin
  ShowMessage(IntToStr(FID));
end {Delete};

{ TAccountData }

destructor TAccountData.Destroy;
begin
  MessageBox(0,'del',nil,0);
  inherited;
end;

function TAccountData.Getid: Integer;
begin
  Result := Fid;
end;

procedure TAccountData.SetId(_Id: Integer);
begin
  Fid := _id;
end;
hab auch mal nen destructor eingebaut, damit du siehst wann das object zerstört wird (ohne _addref schon beim zerstören der InterfaceList). Und damit du kein Speicherleck hast musst du auch wieder _Release aufrufen.

Im grunde brignt das das nte viel bei dem Beispiel weil du ne Sonderbehandlung bei Data brauchst. Dann kannst auch auf interfaces verzichten und gleich selbst freigeben (es sei denn es wird ein externes Modul wo du mit interfaces drauf zugreifen willst).
  Mit Zitat antworten Zitat
brechi

Registriert seit: 30. Jan 2004
823 Beiträge
 
#6

AW: Data-Pointer in Interfacevariable casten

  Alt 15. Jul 2010, 19:08
Oder du schreibst dir deine eigene TListView die alles für dich übernimmt:

Delphi-Quellcode:
unit Unit9;

interface

uses
  Windows,
  Messages,
  SysUtils,
  Variants,
  Classes,
  Graphics,
  Controls,
  Forms,
  Dialogs,
  ComCtrls,
  StdCtrls;

type
  TListView = class(comCtrls.TListView)
  private
    FOnDeletion: TLVDeletedEvent;
    procedure SetOnDeletion(const Value: TLVDeletedEvent);
    function GetOnDeletion: TLVDeletedEvent;
  protected
    procedure InternOnDeletion(Sender: TObject; Item: TListItem);
  public
    constructor Create(AOwner: TComponent); override;
  published

    procedure AddItem(Item: String; AObject: IInterface); reintroduce;
    property OnDeletion: TLVDeletedEvent read GetOnDeletion write SetOnDeletion;
  end;

  TForm9 = class(TForm)
    ListViewMain: TListView;
    Button1: TButton;
    procedure ListViewMainClick(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
    procedure Refresh;
    procedure GetAllAccounts(_o: TInterfaceList);
  public
    { Public declarations }
  end;

var
  Form9: TForm9;

implementation

{$R *.dfm}

type
  IDataObject = interface(IInterface)
    ['{C94DCAC7-CA42-45D3-95D7-9BC321BA2E2A}']
    procedure Delete;
  end;

  IAccountData = interface(IInterface)['{DA626905-CB35-4474-B353-9F8E973709AA}']
    function GetId: Integer;
    procedure SetId(_Id: Integer);
    property ID: Integer read Getid write SetId;
  end;

  TAccountData = class(TInterfacedObject, IAccountData)
  protected
    FID: Integer;
    function Getid: Integer;
    procedure SetId(_Id: Integer);
  public
    destructor Destroy; override;
  end;

  TAccountDataExtend = class(TAccountData, IDataObject)
  public
    procedure Delete;
  end;

procedure TForm9.ListViewMainClick(Sender: TObject);
var
  obj: IInterface;
  obj3: IDataObject;
begin
  inherited;
  if ListViewMain.ItemIndex >= 0 then begin
    obj := IInterface(ListViewMain.Selected.Data);
    if Succeeded(obj.QueryInterface(IDataObject, obj3)) then begin
      obj3.Delete; //Funktion siehe unten
    end;
  end;
end;

procedure TForm9.Refresh;
var
  tmp: IAccountData;
  i: Integer;
  x: TInterfaceList;
begin
  //Vorgängig Listview aufräumen, Objekte zerstören, Interface Releasen

  x := TInterfaceList.Create;
  try
    GetAllAccounts(x); //Siehe Funktion unten
    for i := 0 to x.Count - 1 do begin
      if Succeeded(x[i].QueryInterface(IAccountData, tmp)) then begin
        ListViewMain.AddItem(IntToStr(tmp.ID), tmp);
      end;
    end;
  finally
    FreeAndNil(x); //Ich nehme an die InterfaceList zerstört die Objekte nicht
  end;
end {LoadListView};

procedure TForm9.Button1Click(Sender: TObject);
begin
  Refresh;
end;

procedure TForm9.GetAllAccounts(_o: TInterfaceList);
var
  tmp: IAccountData; //Oder IInterface?
  i: integer;
begin
  for i := 0 to 4 do begin
    tmp := TAccountDataExtend.Create;
    tmp.ID := i;
    _o.Add(tmp);
  end;
end {GetAllAccounts};

//----------------- TAccountDataExtend

procedure TAccountDataExtend.Delete;
begin
  ShowMessage(IntToStr(FID));
end {Delete};

{ TAccountData }

destructor TAccountData.Destroy;
begin
  MessageBox(0,'del',nil,0);
  inherited;
end;

function TAccountData.Getid: Integer;
begin
  Result := Fid;
end;

procedure TAccountData.SetId(_Id: Integer);
begin
  Fid := _id;
end;


{ TListView }

procedure TListView.AddItem(Item: String; AObject: IInterface);
begin
  AObject._AddRef;
  inherited AddItem(Item, Pointer(AObject))
end;

procedure TListView.SetOnDeletion(const Value: TLVDeletedEvent);
begin
  FOnDeletion := Value;
end;

constructor TListView.Create(AOwner: TComponent);
begin
  inherited;
  inherited OnDeletion := InternOnDeletion;
end;

function TListView.GetOnDeletion: TLVDeletedEvent;
begin
  Result := FOnDeletion;
end;

procedure TListView.InternOnDeletion(Sender: TObject; Item: TListItem);
begin
  if Assigned(item.Data) then
    IInterface(item.Data)._Release;
end;

end.
  Mit Zitat antworten Zitat
Benutzerbild von himitsu
himitsu

Registriert seit: 11. Okt 2003
Ort: Elbflorenz
44.374 Beiträge
 
Delphi 12 Athens
 
#7

AW: Data-Pointer in Interfacevariable casten

  Alt 15. Jul 2010, 19:13
In GetAllAccounts hat tmp._AddRef; nichts zu suchen, denn die Interfacelist arbeites ja korreckt und das Objekt wird da auch nie als Objekt verwendet.

Das Problem mit der Referenzzählung liegt an einer ganz anderen Stelle.
Und zwar bei item.Data := Pointer(tmp); , denn da wird eine Instanz des Interfaces gespeichert, aber ohne Berücksichtigung der Referenzzählung.
Nun gibt die InterfaceList natürlich beim FreeAndNil(x) ihre Referenzen frei und da es sonst keine registrierten RTeferenzen gibt, würden da die Interfaces/Objekte natürlich freigegeben.

Außerdem ist der Cast IInterface(ListViewMain.Selected.Data); falsch, denn in .Data steckt ein IAccountData und kein IInterfce.
Beide Interfaces (IAccountData und IInterfce) implementieren zum Glück das selbe QueryInterface, so daß es hier zufällig nicht zu einem Problem kommt.


Delphi-Quellcode:
type
  IDataObject = interface(IInterface)
    ['{C94DCAC7-CA42-45D3-95D7-9BC321BA2E2A}']
    procedure Delete;
  end;

  IAccountData = interface(IInterface)['{DA626905-CB35-4474-B353-9F8E973709AA}']
    function GetId: Integer;
    procedure SetId(_Id: Integer);
    property ID: Integer read Getid write SetId;
  end;

  TAccountData = class(TInterfacedObject, IAccountData)
  protected
    FID: Integer;
    function Getid: Integer;
    procedure SetId(_Id: Integer);
  public
    destructor Destroy; override;
  end;

  TAccountDataExtend = class(TAccountData, IDataObject)
  public
    procedure Delete;
  end;

procedure TForm9.ListViewMainClick(Sender: TObject);
var
  obj: IAccountData;
  obj3: IDataObject;
begin
  //inherited; // himi: nicht nötig ... oder wurde hier was überschrieben?
  if ListViewMain.ItemIndex >= 0 then begin
    obj := IAccountData(ListViewMain.Selected.Data);
    if Succeeded(obj.QueryInterface(IDataObject, obj3)) then begin
      obj3.Delete; //Funktion siehe unten
    end;
  end;
end;

procedure TForm9.ListViewMainDeletion(Sender: TObject; Item: TListItem);
begin
  // himi: korreckt, nur daß hier nun nicht mehr die
  // fehlerhafte Referenz von .GetAllAccounts freigegeben
  // wird, sondern die aus .Refresh ;)
  IAccountData(Item.Data)._Release;
end;

procedure TForm9.Refresh;
var
  tmp: IAccountData;
  i: Integer;
  item: TListItem;
  x: TInterfaceList;
begin
  // Vorgängig Listview aufräumen, Objekte zerstören, Interface Releasen
  // himi: das sollte diese Zeile erledigen
  ListViewMain.Items.Clear;

  x := TInterfaceList.Create;
  try
    GetAllAccounts(x); //Siehe Funktion unten
    for i := 0 to x.Count - 1 do begin
      if Succeeded(x[i].QueryInterface(IAccountData, tmp)) then begin
        item := ListViewMain.Items.Add;
        item.Caption := IntToStr(tmp.ID); //Zeigt korrekte ID an
        tmp._AddRef; // himi: hier die Referenz für .Data reservieren
        item.Data := Pointer(tmp);
      end;
    end;
  finally
    FreeAndNil(x);
    //Ich nehme an die InterfaceList zerstört die Objekte nicht
    // himi: doch, wenn sie die "einzige" Referenz der
    // Interfaces besitzen würde
  end;
end {LoadListView};

procedure TForm9.Button1Click(Sender: TObject);
begin
  Refresh;
end;

procedure TForm9.GetAllAccounts(_o: TInterfaceList);
var
  tmp: IAccountData; //Oder IInterface?
    // himi: nee, denn sonst könnte man ja nicht auf .ID zugreifen
  i: integer;
begin
  for i := 0 to 4 do begin
    tmp := TAccountDataExtend.Create;
    tmp.ID := i;
    //tmp._AddRef; // himi: wie gesagt ... ist falsch hier
    _o.Add(tmp);
  end;
end {GetAllAccounts};

//----------------- TAccountDataExtend

procedure TAccountDataExtend.Delete;
begin
  ShowMessage(IntToStr(FID));
end {Delete};

{ TAccountData }

destructor TAccountData.Destroy;
begin
  ShowMessage('del ' + IntToStr(FID));
  inherited;
end;

function TAccountData.GetId: Integer;
begin
  Result := FID;
end;

procedure TAccountData.SetId(Id: Integer);
begin
  FID := Id;
end;
Ein Therapeut entspricht 1024 Gigapeut.

Geändert von himitsu (15. Jul 2010 um 19:18 Uhr)
  Mit Zitat antworten Zitat
brechi

Registriert seit: 30. Jan 2004
823 Beiträge
 
#8

AW: Data-Pointer in Interfacevariable casten

  Alt 15. Jul 2010, 19:30
Delphi-Quellcode:
tmp._AddRef; // himi: hier die Referenz für .Data reservieren
        item.Data := Pointer(tmp);
hier ist das Addref wirklich besser aufgehoben (in meinem 2. Post mach ich das direkt über das AddItem)

"Beide Interfaces (IAccountData und IInterfce) implementieren zum Glück das selbe QueryInterface, so daß es hier zufällig nicht zu einem Problem kommt."
Ne ist ja gerade kein Zufall da IAccountData = interface(IInterface).
Gut beim Casten kann man natürlich direkt richtig casten, aber man hat dann ggf. Probleme wenn man unterschiedliche Interfaces in die Liste eingefügt hat, dann ist ein IInterface.QueryInterface schon geeigneter, oder übersehe ich da jetzt was ganz dummes?
  Mit Zitat antworten Zitat
Antwort Antwort

Themen-Optionen Thema durchsuchen
Thema durchsuchen:

Erweiterte Suche
Ansicht

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 18:33 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