[Delphi] Wie beheert mijn object nu?

Pagina: 1
Acties:
  • 137 views sinds 30-01-2008
  • Reageer

  • jelmervos
  • Registratie: Oktober 2000
  • Niet online

jelmervos

Simple user

Topicstarter
Ik heb een probleem waar ik nog nooit echt een oplossing voor heb bedacht. Ik loop er niet zo vaak tegen aan, maar ben wel benieuwd of er een goede oplossing voor is.

Stel ik heb 2 project objecten welke allebij dezelfde inboeking krijgen. Dus maak ik een inboek object aan een geef deze aan beide projecten mee:
code:
1
2
3
4
  Inboeking := TInboeking.Create;
  Inboeking.Omschrijving := 'Divers';
  Project1.Inboekingen.Add(Inboeking);
  Project2.Inboekingen.Add(Inboeking);

Beide projecten hebben nu dus dezelfde inboeking in hun inboekingen lijst. Maar als ik project1 vrijgeef heeft project2 een ongeldige referentie naar een object in z'n lijst omdat project1 zijn interne object netjes vrijgeeft.

Hoe los je zoiets op?

"The shell stopped unexpectedly and Explorer.exe was restarted."


  • SWfreak
  • Registratie: Juni 2001
  • Niet online
Bijvoorbeeld door een reference count bij te houden voor elke Inboeking. Pas als die nul is verwijderen (zo doet Windows dat met DLLs)

  • jelmervos
  • Registratie: Oktober 2000
  • Niet online

jelmervos

Simple user

Topicstarter
Bieden Interfaces in dit geval een goede oplossing?

"The shell stopped unexpectedly and Explorer.exe was restarted."


  • Delphi32
  • Registratie: Juli 2001
  • Laatst online: 22-08 10:56

Delphi32

Heading for the gates of Eden

Niet helemaal volgens mij. Ik heb hier (een tijdje geleden, was met D5 geloof ik) wat tests op gedaan. Als je de Interface pointer in die twee lijstjes zet en je Interface pointer gaat out of scope, dan wordt het object achter de Interface vrijgegeven, was meen ik onze conclusie. Het op meerdere plaatsen gebruiken van een pointer naar een interface gaat dan dus niet werken: je krijgt de raarste access violations. Begrijpelijk maar onhandig. NOOT: Ik heb toen gewerkt met TLists, kan zijn dat je met iets als TInterfaceList (die is er geloof ik ook in D6/7) verder komt. Maar daar kan ik (helaas, helaas....) weinig over zeggen.

Eigenlijk vind ik dat je in jouw voorbeeld eerst helemaal helder moet maken wat de lifetime van de Inboeking moet wezen. Als daar uitkomt dat je beide lijstjes een eigen levensduur hebben, dan kan het zinvol zijn om in de methode Add van de lijstjes niet simpel een reference op te nemen, maar een soort van Create gevolgd door een Assign. Daarmee worden de lijstjes dan eigenaar van hun eigen kopie van je Inboeking object.

Maar nogmaals, het kan zijn dat D6/7 je betere tools biedt om met interfaces te werken, en als dat zo is dan wil ik je zeker aanraden om daarvoor te gaan. Het is mijn persoonlijke frustratie dat ik je op dat gebied niet verder kan helpen, maar dit terzijde.

  • Tomatoman
  • Registratie: November 2000
  • Laatst online: 22:26

Tomatoman

Fulltime prutser

Delphi32 schreef op 02 April 2003 @ 00:30:
Maar nogmaals, het kan zijn dat D6/7 je betere tools biedt om met interfaces te werken, en als dat zo is dan wil ik je zeker aanraden om daarvoor te gaan. Het is mijn persoonlijke frustratie dat ik je op dat gebied niet verder kan helpen, maar dit terzijde.
Zie deze site voor hulp ;).

Een goede grap mag vrienden kosten.


  • LordLarry
  • Registratie: Juli 2001
  • Niet online

LordLarry

Aut disce aut discede

Delphi schreef op 01 april 2003 @ 21:34:
Beide projecten hebben nu dus dezelfde inboeking in hun inboekingen lijst. Maar als ik project1 vrijgeef heeft project2 een ongeldige referentie naar een object in z'n lijst omdat project1 zijn interne object netjes vrijgeeft.

Hoe los je zoiets op?
Inprinciepe degene die het object gecreeerd heeft. De beide andere zijn alleen maar gebruikers, geen eigenaar. Als je ze wel eigenaar wilt maken zal je idd zoiets als reference counting kunnen gebruiken. Als je Interfaces gebruikt krijg je die reference counting er automatisch bij en hoe je je niet meer druk te maken wie m vrij geeft.

Delphi 5 (of zelfs al 4?) heeft al een complete ondersteuning voor interfaces en is meer dan genoeg voor wat jij wil gebruiken. Delphi 6 en 7 hebben maar weinig toegevoegde waarde op dit gebied.

Ik ben bang dat Delphi32's conclusie over interfaces niet helemaal correct is, want het verhaal dat hij schetst moet weldegelijk goed werken, ook in Delphi 5. Wat je niet moet doen is Ttjes en Itjes door elkaar gebruiken. Gebruik na de Create overal en altijd interfaces en het gaat allemaal goed.

We adore chaos because we like to restore order - M.C. Escher


  • whoami
  • Registratie: December 2000
  • Laatst online: 23:02
Als je gebruik maakt van een TObjectList, kan je dan niet uit de voeten met de property OwnsObjects?

https://fgheysels.github.io/


  • Tomatoman
  • Registratie: November 2000
  • Laatst online: 22:26

Tomatoman

Fulltime prutser

Delphi heeft in veel componenten precies de functionaliteit ingebouwd die jij zoekt. Stel je voor dat je een popupmenu op een form zet en ook een TEdit en een TButton (twee willekeurig gekozen controls). In de Object Inspector stel je de PopupMenu property van de edit control en de button in door het popupmenu te selecteren. Er zijn nu twee controls met een referentie naar het TPopupMenu en geen van beide is de owner van de popup. Als je nu op je form het popupmenu verwijdert (nog steeds in design time), zie je in de Object inspector dat de PopupMenu property van de TEdit en de TButton is gewist. Hoe werkt dat?

Zodra je aan de button en de edit control een waarde aan de PopupMenu property toekent, melden ze aan het desbetreffende popupmenu dat ze notificaties willen ontvangen. Het popupmenu houdt een lijstje bij met ‘aangemelde’ controls. Zodra het popupmenu wordt vernietigd, roept het een procedure NotifyControls aan, die alle aangemelde controls meldt dat het popupmenu wordt vernietigd. De controls stellen tenslotte hun PopupMenu property op de waarde nil in.

Dit gaat niet alleen op voor popupmenu’s, maar voor heel veel aan elkaar refererende componenten. Je zult even in de source code van de componenten moeten zoeken om te kijken hoe dit mechanisme precies is geïmplementeerd, maar het is eigenlijk een heel eenvoudig principe dat je zo kunt kopiëren naar je eigen componenten.

Een goede grap mag vrienden kosten.


  • Delphi32
  • Registratie: Juli 2001
  • Laatst online: 22-08 10:56

Delphi32

Heading for the gates of Eden

Ik heb even een ranzig testprojectje in elkaar gedraaid om te laten zien waarom ik het nogal lastig vond om interfaces op te slaan in lijstjes, zonder dat ze out of scope gingen en dus niet meer werkten. Omdat ik graag iets nieuws leer, pleur ik het hier even neer, in de hoop dat er wat opbouwend commentaar op kan komen... misschien begrijp ik het allemaal gewoon verkeerd, en dat zou zomaar heel goed kunnen :)
Delphi:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
type
  ITestIntf = interface
    function GetTestValue: string;
    procedure SetTestValue(aValue: string);
  end;

  TTestObject = class(TInterfacedObject, ITestIntf)
  private
    FTestValue: string;
  public
    function GetTestValue: string;
    procedure SetTestValue(aValue: string);
  end;

  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
    FList: TList;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

{ TTestObject }

function TTestObject.GetTestValue: string;
begin
  result := FTestValue;
end;

procedure TTestObject.SetTestValue(aValue: string);
begin
  FTestValue := aValue;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  FList := TList.Create;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  FreeAndNil(FList);
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  testIntf: ITestIntf;
begin
  testIntf := TTestObject.Create;
  testIntf.SetTestValue('hoi' + IntToStr(FList.Count));
  FList.Add(TObject(testIntf));
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  i : integer;
begin
  for i := 0 to FList.Count - 1 do begin
    ShowMessage(ITestIntf(FList.Items[i]).GetTestValue);
  end;
end;


Als ik in dit project 2x op Button1 klik worden er (naar ik hoopte) 2 interfaces bewaard in de TList (ook geprobeerd met TObjectList, to no avail). Klik ik op Button2, dan wil ik dus 2x een msgbox zien: eerst hoi0, dan hoi1. Wie schetst mijn verbazing als ik de eerste keer een msgbox zie zonder enige tekst, en daarna bij de tweede een access violatie om den oren krijg.
Ik heb in regel 72 de cast naar ITestIntf ook veranderd in TTestObject, maar dan crasht ie bij de eerste al.

Waarom ik dit dus post: TS wil een stuk of wat objecten vanuit verschillende andere objecten kunnen benaderen, en er is gesuggereerd dat dmv interfaces de refcount > 0 blijft en daardoor het object pas vrijgegeven wordt als het laatste gebruikende object de reference vrijgeeft. Ik heb aangegeven dat ik hiermee slechte ervaringen had, hierbij mijn slechte ervaring :)

Gaarne reactie van de mensen die mij ofwel naar Stichting Korrelatie doorverwijzen (waarvoor dank :P) ofwel weten waarom dit wel zou moeten werken of wat je moet wijzigen om dit concept te laten werken. Lijkt me dat TS daar dan weer mee uit de voeten kan.

Overigens was de laatste opmerking van Tomatoman over het messaging mechanisme natuurlijk gewoon een mooi voorstel. Alleen werkt dat alleen als de lifetime van de aanmaker van het object langer is dan die van het gecreëerde object, want een object kan zichzelf toch (nog) niet freeën :?

  • LordLarry
  • Registratie: Juli 2001
  • Niet online

LordLarry

Aut disce aut discede

Hier gebeurt waar ik al voor waarschuwde. Ttjes (Objecten) en Itjes (Interfaces) worden doorelkaar gebruikt. Na de Create stop je m netjes in een Itje, maar hierna typecast je m hard weer terug naar een TObject. Ten eerste kan je van een Itje normaal nooit terug naar een Ttje, maar zeker niet door zomaar hard te typecasten. De reference counting raakt hiervan in de war, want de reference count wordt door de typecast niet opgehoogt terwijl jij dat toch aanneemt. Hieronder wat pseudo code van wat er op de achtergrond gebeurt:
Delphi:
1
2
3
4
5
6
7
8
9
10
procedure TForm1.Button1Click(Sender: TObject);
var
  testIntf: ITestIntf;
begin
  testIntf := TTestObject.Create;
  testIntf._AddRef;  // RefCount = 1
  testIntf.SetTestValue('hoi' + IntToStr(FList.Count));
  FList.Add(TObject(testIntf));
  testIntf._Release;  // RefCount = 0, dus het object wordt gefreeed
end;

Wat je dus in je List hebt is een pointer naar een object dat al niet meer bestaat.

Er zijn meerdere oplossingen voor dit 'probleem'. Je kan handmatig een AddRef doen zodat de RefCount op het einde van de functie niet 0 wordt. Je zal er dan zelf voor moeten zorgen dat er dan ooit weer een Release aangeroepen wordt of zelf het Object freeen. Het heeft nu geen nut meer om interfaces te gebruiken aangezien de programmeur het toch nog allemaal zelf moet regelen.
Delphi:
1
2
3
4
5
6
7
8
9
procedure TForm1.Button1Click(Sender: TObject);
var
  testIntf: ITestIntf;
begin
  testIntf := TTestObject.Create;  // Refcount = 1
  testIntf.SetTestValue('hoi' + IntToStr(FList.Count));
  testIntf._AddRef;    // RefCount = 2
  FList.Add(TObject(testIntf));
end;  // RefCount = 1, dus wordt er niets gefreed


De betere oplossing is een TInterfaceList gebruiken.
Delphi:
1
2
3
4
5
6
7
8
procedure TForm1.Button1Click(Sender: TObject);
var
  testIntf: ITestIntf;
begin
  testIntf := TTestObject.Create;
  testIntf.SetTestValue('hoi' + IntToStr(FList.Count));
  FList.Add(testIntf);
end;


Het is eigenlijk ook niet erg netjes en veilig hard te typecasten. Beter is 'as' gebruiken. Bij een harde typecast vertrouwd Delphi op de programmeur zelfs als de typecast niet kan. Bij 'as' wordt er gekeken of de typecast wel kan. Dus
Delphi:
1
    ShowMessage((FList.Items[i] as ITestIntf).GetTestValue);

in plaats van
Delphi:
1
    ShowMessage(ITestIntf(FList.Items[i]).GetTestValue);

'as' moet wel een manier hebben om de verschillende interfaces of objecten te identificeren. Bij Objecten is dat de VMT, bij interfaces een GUID. Om volledig gebruik te maken van alle voordelen die interfaces bieden zou je eigenlijk elke interface ook een unieke GUID moeten geven (ctrl-shift-G). De interface gaat er dan zo uit zien:
Delphi:
1
2
3
4
5
6
type
  ITestIntf = interface
  ['{22AE25E3-813D-4F83-9702-6226B8445179}']
    function GetTestValue: string;
    procedure SetTestValue(aValue: string);
  end;


Dus: Nooit Itjes en Ttjes doorelkaar mixen. Daar krijg je alleen problemen van.

[ Voor 3% gewijzigd door LordLarry op 03-04-2003 09:37 ]

We adore chaos because we like to restore order - M.C. Escher


  • Delphi32
  • Registratie: Juli 2001
  • Laatst online: 22-08 10:56

Delphi32

Heading for the gates of Eden

Een korte verklaring mijnerzijds dan, na de fraaie uiteenzetting van LordLarry.
Het is heel simpel: het bestaan van de TInterfaceList was mij dus niet bekend... dus ik moest het doen met een TList of een TObjectList. Om daarin die interface op te slaan, moest ik natuurlijk wel casten naar Pointer of TObject, anders wilde de boel niet compileren... Ik zou dat normaal dus nooit doen, maar bij gebrek aan TInterfaceList (die er gelukkig dus blijkt te zijn, moest ik wel hard casten.
Ik van mijn kant wil je bedanken voor je mooie verklaring, en ik hoop dat TS er ook wat mee kan.

  • LordLarry
  • Registratie: Juli 2001
  • Niet online

LordLarry

Aut disce aut discede

Je bent vergeven :+

We adore chaos because we like to restore order - M.C. Escher

Pagina: 1