Results 1 to 8 of 8

Thread: Objects, Interfaces en ObjectList

  1. #1
    TDigitalTrain user Hans Brenkman's Avatar
    Join Date
    Mar 2002
    Location
    Weert
    Posts
    1,861

    Objects, Interfaces en ObjectList

    Ik probeer interfaces te begrijpen en toe te passen en heb een bestaande class en het gebruik daarvan aangepast door voor die class een interface (type IInterface) te schrijven en te koppelen aan die class.
    Vervolgens worden instanties van die class in een TObjectList gestopt. Die TObjectList dient bij een hernieuwde weergave van het report waar de class in gebruikt wordt, eerst leeggemaakt te worden.

    Delphi Code:
    1. FMyObjectList := TObjectList.Create({OwnsObjects}True);

    Oorspronkelijke situatie:

    Delphi Code:
    1. procedure TDmReportNormen.ControleerNormen;
    2. var
    3.   CurrentItem: TColorItem;    // class
    4.   i: integer;
    5. begin
    6.   // Eerst FMyObjectList leegmaken voordat deze opnieuw gevuld wordt.
    7.   for i := Pred(FMyObjectList.Count) downto 0 do
    8.   begin
    9.     CurrentItem := FMyObjectList.Items[i] as TColorItem;
    10.     FMyObjectList.Remove(CurrentItem);
    11.   end;
    12.   {$IFDEF DEBUG}
    13.   ShowMessage(FMyObjectList.Count.ToString)
    14.   {$ENDIF}
    15.   FMyObjectList.Clear;
    16.  
    17.   cdsReport.First;
    18.   while not cdsReport.Eof do
    19.   begin
    20.      FMyObjectList.Add(TColorItem.Create());
    21.      cdsReport.Next;
    22.   end;
    23.   ...
    24. end;

    In de situatie hierboven bevat ieder object TColorItem 2 kleurwaarden. Met de toevoeging van de interface bevat ieder object van TColorItem (definitie "CurrentItem: TColorItem") 3 waarden: FRefCount: 0 en 2 kleurwaarden.

    Nieuwe situatie:

    Als CurrentItem niet van het type TColorItem (class) wordt gemaakt maar van IColorItem (interface) dan laat de debugger zien dat CurrentItem als waarde "TColorItem($7057174) as IColorItem" heeft.
    Tevens kan ik dan de items in FMyObjectList niet meer opruimen zoals hierboven met "FMyObjectList.Remove(CurrentItem);". Dan krijg ik bij compileren de foutmelding "E2250 There is no overloaded version of 'Remove' that can be called with these arguments". Dus dat kan (zo) niet meer.

    Daarnaast krijg ik bij "FMyObjectlist.Clear" (indien deze de 2e keer uitgevoerd wordt) een foutmelding : "EAccessViolation: Access violation at adress 00000000 in module 'DBS.exe'. Read of adress 00000000". Er wordt dus verwezen naar een object dat niet (meer) bestaat, terwijl FMyObjectList nog 9 items bevat.

    Waar ga ik de fout in / wie kan mij een zetje in de juiste richting geven ?

    Overigens zie ik het "ontkoppelen" nog niet zoals door Nick Hodges sterk aanbevolen wordt, maar dat terzijde.
    Testen kan niet de afwezigheid van fouten aantonen, slechts de aanwezigheid van gevonden fouten.

    Het is verdacht als een nieuw ontwikkeld programma direct lijkt te werken: waarschijnlijk neutraliseren twee ontwerpfouten elkaar.

  2. #2
    TObjectList is een lijst voor objecten. TInterfacedList zou dan een betere kandidaat zijn. Interfaces zijn reference counted. Als het aantal referenties verhoogd wordt en vervolgens weer verlaagd tot 0, dan wordt het object vrijgegeven. Wat er daardoor denk ik gebeurt in jouw situatie, is dat je objecten en interfaces mengt, waardoor je uiteindelijk nog een objectreferentie hebt, terwijl het object (vanwege de refcount) al is vrijgegeven. Je hebt helaas niet de falende code gepost, maar ik denk dat er zoiets gebeurt:

    Je voegt een item toe. Wordt gecontrueerd via de class en als TObject opgeslagen.
    Interface is hier nog niet in gebruik, van refcount is dus nog geen sprake:

    Delphi Code:
    1. procedure TDmReportNormen.VoegToe;
    2. begin
    3.   FMyObjectList.Add(TColorItem.Create);
    4. end;

    Je wilt dan iets doen met de interface. Daarom doe je een typecast. Op dit moment treedt de refcount in werking en heeft je object een refcount van 1.

    Je gebruikt het object in de procedure 'Verwerk', maar aan het eind gaat de variabele CurrentItem uit scope en wordt refcount 0.
    Daardoor wordt het object vrijgegeven en heb je dus een ongeldige objectreferentie in je objectlist staan. Een tweede aanroep naar Verwerk, of andere handelingen die verwachten dat er een geldig object in de collection zit zullen dus fout gaan en waarschijnlijk een foutmelding (bijvoorbeeld een access violation) veroorzaken:

    Delphi Code:
    1. procedure TDmReportNormen.Verwerk;
    2. var
    3.   CurrentItem: IColorItem;
    4. begin
    5.   CurrentItem := FMyObjectList.Items[0] as IColorItem;
    6. end;

    Door een andere container te gebruiken, namelijk één die interfaces snapt, kan je dat probleem omzeilen. Je instantieert het object, maar je slaat direct alleen de interfac op en dat is het enige waar je mee werkt. Geen verdere typecasts van object naar interface of andersom.
    1+1=b

  3. #3
    TDigitalTrain user Hans Brenkman's Avatar
    Join Date
    Mar 2002
    Location
    Weert
    Posts
    1,861
    Allereerst dank voor je snelle reactie.

    Quote Originally Posted by GolezTrol View Post
    is dat je objecten en interfaces mengt
    Ik had idd gelezen dat je daar zeker voor op moest passen dat niet te doen (Coding in Delphi - Nick Hodges) maar was me er niet bewust van dat ik dat deed.

    Quote Originally Posted by GolezTrol View Post
    Je hebt helaas niet de falende code gepost, maar ik denk dat er zoiets gebeurt:

    Je voegt een item toe. Wordt gecontrueerd via de class en als TObject opgeslagen.
    Interface is hier nog niet in gebruik, van refcount is dus nog geen sprake:

    Delphi Code:
    1. procedure TDmReportNormen.VoegToe;
    2. begin
    3.   FMyObjectList.Add(TColorItem.Create);
    4. end;
    Klopt, zie code in 1e post.

    Interface :
    Delphi Code:
    1. type
    2.   IColorItem = interface(IInterface)
    3.     ['{83DB00C0-7C49-4B4E-9089-3CAA33563BA9}']
    4.  ...

    Class:
    Delphi Code:
    1. type
    2.   /// Class TColorItem t.b.v. weergave cellen in afwijkende kleur in TRzDBGrid
    3.   TColorItem = class(TInterfacedObject, IColorItem)
    4.   ...

    Ik heb door je laatste voorbeeldcode wél het loskoppelen nu in beeld.
    Wordt vervolgd ....
    Last edited by Hans Brenkman; 25-Apr-17 at 12:36.
    Testen kan niet de afwezigheid van fouten aantonen, slechts de aanwezigheid van gevonden fouten.

    Het is verdacht als een nieuw ontwikkeld programma direct lijkt te werken: waarschijnlijk neutraliseren twee ontwerpfouten elkaar.

  4. #4
    TDigitalTrain user Hans Brenkman's Avatar
    Join Date
    Mar 2002
    Location
    Weert
    Posts
    1,861
    Ik heb van de private FMyObjectList een FMyInterfaceList: TInterfaceList; gemaakt en vervolgens waar nodig gecast naar IColorItem i.p.v. TColorItem. FMyInterfaceList kan dan ook gewoon gefreed worden.

    Die TColorItem komt niet meer in desbetreffende unit voor BEHALVE in

    Delphi Code:
    1. FMyInterfaceList.Add(TColorItem.Create());

    Daarna ook geen memoryleaks (System.ReportMemoryLeaksOnShutdown := True; )
    Last edited by Hans Brenkman; 25-Apr-17 at 14:11.
    Testen kan niet de afwezigheid van fouten aantonen, slechts de aanwezigheid van gevonden fouten.

    Het is verdacht als een nieuw ontwikkeld programma direct lijkt te werken: waarschijnlijk neutraliseren twee ontwerpfouten elkaar.

  5. #5
    TDigitalTrain user Hans Brenkman's Avatar
    Join Date
    Mar 2002
    Location
    Weert
    Posts
    1,861
    Een soort gelijke opzet gaat zo te zien niet werken als je het object toekent aan een component dat als parameter een object verwacht, bijv.

    Delphi Code:
    1. TRzCheckList.AddItem(Item: string; AObject: TObject);

    Zijn er dan ook mogelijkheden om toch iets met interfaces te doen of houdt het in dergelijke situaties hier dan op ?
    Testen kan niet de afwezigheid van fouten aantonen, slechts de aanwezigheid van gevonden fouten.

    Het is verdacht als een nieuw ontwikkeld programma direct lijkt te werken: waarschijnlijk neutraliseren twee ontwerpfouten elkaar.

  6. #6
    Tja, objecten aan zo'n control koppelen is sowieso wat dubieus, en de enige oplossing die ik zo even kan bedenken is ook niet super elegant. Je zou een object kunnen maken dat een interface-property heeft.

    Delphi Code:
    1. type
    2.   TContainer = class
    3.   private
    4.     FIntf: IInterface;
    5.   public
    6.     constructor Create(AIntf: IInterface);
    7.     property Intf: IInterface read FIntf;
    8.   end;
    9.  
    10. constructor TContainer.Create(AIntf: IInterface);
    11. begin
    12.   FIntf := AIntf;
    13. end;
    Die zou je dan zo moeten kunnen aanroepen:
    Delphi Code:
    1. RzCheckList.AddItem('Item', TContainer.Create(TColorItem.Create()));
    En weer uitlezen met (ongeveer, ik ken RzCheckList niet precies).
    Delphi Code:
    1. ((RzCheckList.Items.Objects[0] as TContainer) as IColorItem).DoeIets;

    Die typecasts zijn natuurlijk niet zo fraai. Je kan dat deels voorkomen door een TColorItemContainer te schrijven die specifiek een IColorItem teruggeeft, maar dat is natuurlijk weer een hoop duplicate code als je containers voor meerdere interfaces nodig hebt.
    Als je een versie van Delphi hebt die generics ondersteunt, dan kan je het een heel klein tikje handiger zonder dat je voor elke interface een aparte container hoeft te schrijven:

    Delphi Code:
    1. type
    2.   TContainer<T: IInterface> = class
    3.   private
    4.     FIntf: T;
    5.   public
    6.     constructor Create(AIntf: T);
    7.     property Intf: T read FIntf;
    8.   end;
    9.  
    10.   TColorItemContainer = TContainer<IColorItem>;
    11.  
    12. constructor TContainer<T>.Create(AIntf: T);
    13. begin
    14.   FIntf := AIntf;
    15. end;

    Delphi Code:
    1. RzCheckList.AddItem('Item', TColorItemContainer.Create(TColorItem.Create()));
    En weer uitlezen met:
    Delphi Code:
    1. (RzCheckList.Items.Objects[0] as TColorItemContainer).Intf.DoeIets;

    Hier zit dan wel het 'nadeel' aan dat je een object (TContainer) maakt, en dat object controleert weer de life time van de interface. Je zal die TContainer dus weer expliciet vrij moeten geven, om daar geen leaks te krijgen.
    Het lijkt alsof je daarmee weer terug bij af bent, maar dat valt denk ik wel mee. Het is een vrij specifieke situatie waarin je objecten aan controls wilt koppelen, en je gebruikt ze ook alleen voor dat doel, dus ze hebben een vrij specifiek te bepalen levensduur en je zal ze niet gaan doorgeven aan (of injecteren in) andere objecten.
    Last edited by GolezTrol; 25-Apr-17 at 23:43.
    1+1=b

  7. #7
    TDigitalTrain user Hans Brenkman's Avatar
    Join Date
    Mar 2002
    Location
    Weert
    Posts
    1,861
    Tja, dat is dan ook weer een hele constructie. Dat doet me besluiten voor nu eerst te focussen op de situaties waarin het wel kan op een normale manier om daar ervaring mee op te bouwen.

    RzCheckList is een component van Raize Components, tegenwoordig Konopka Signature VCL Controls. Ik gebruik Delphi 10 Seattle dus met generics zou ook mogelijk zijn. Generics is een andere te nemen uitdaging waarvan ik de voordelen wel zie, maar daarnaast de toepasbaarheid in je projecten moet zien te vinden als je dat nieuw voor je is.
    Testen kan niet de afwezigheid van fouten aantonen, slechts de aanwezigheid van gevonden fouten.

    Het is verdacht als een nieuw ontwikkeld programma direct lijkt te werken: waarschijnlijk neutraliseren twee ontwerpfouten elkaar.

  8. #8
    Klopt. Generics beginnen nu pas door te druppelen in onze framework-laag, maar daarvoor heb ik ze wel al op kleine schaal weten te gebruiken, bijvoorbeeld door een TDictionary<> te gebruiken i.p.v. een gesorteerde TStringList en een hoop typecasts. Op die manier raak je er in ieder geval bekend mee, en kan je ze langzaam maar zeker breder gaan inzetten.
    1+1=b

Thread Information

Users Browsing this Thread

There are currently 1 users browsing this thread. (0 members and 1 guests)

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •