A Symbian C++ programozási nyelv

Egyéb sajátosságok

Bevezetés

Már eddig is láthattuk, hogy a Symbian programozása sok szokatlan konvenció elsajátítását igényli. Most további alapvető programozási szabályokról és módszerekről lesz szó, amelyek a Symbian programozásában fontos szerepet játszanak.

Kódhatékonyság

A fejlesztés során oda kell figyelnünk arra, hogy az eszközök, amikre fejlesztünk kevés erőforrással rendelkeznek. A stack használatánál arra kell odafigyelni, hogy minden szálhoz egy nagyságrendileg 10 kB-os (szokásosan 8 vagy 12) stack tartozik. Ezért nem szabad túl nagy objektumot létrehozni lokális változoként, inkább a heap használata a javasolt. A már nem szükséges változókat szüntessük meg, illetve az alaptípusokon kívül érdemes mutatókat vagy referenciákat átadni.

Ha egy apalértelmezett paraméterű függvényt gyakran használunk az alapértelmezett paraméterével, akkor célszrű két függvényt készíteni. Külön egyet az alapértelmezett paraméterre, ekkor spórolhatunk a kódhosszal.

A referenciák használtata nemcsak olvashatóbb kódot eredményez, hanem gyorsabb is, mint a mutatók használata. Ennek oka, hogy az ősosztályra történő konverzió a mutatóknál többlet munkát eredményez.

Oda kell figyelni a lebegőpontos számok használatára is. Bár a Symbianos processzorok meglehetősen gyorsak, de lebegőpontos számokkal nehezen tudnak dolgozni, ezért mindig érdemes megvizsgálni nincs-e lehetőség egészszámok használatára.

Az inline függvények, bár gyorsítják a kódot, de sokkal több memória-többletet igényelnek. Emellett az inline függvények későbbi változtatása bináris kompatibilitási hibákat is okozhat. Ezért inline függvényeket csak egyszerű inicializáló konstruktoroknak, egyszerű beállító és lekérdező metódusoknak, illetve olyan egyszerű metódusoknak érdemes választani, melyek implmentációja már nem változik.

ThinTemplate

A template-ekkel kapcsolatos konvenció az úgynevezett "thin template" elv. A template-ek nagyon hasznosak az adatszerkezetek, adattárolók általánosításához. (Például tetszőleges típus tárolását támogató tömb.) Sajnos minden egyes típus létrehozásakor a tároló osztály kódja újra jelen lesz a lefordított kódban. Mivel valószínű vannak közös részek (pl tömböknél az idexhatárok ellenőrzése), ezért ezeket felesleges mindenhol szerepeltetni. Elképzelhető lenne egy TAny* típusú mutatók adattárolására alkalmas osztály készítése, de problémát okozhatnának a nem biztonságos típuskonverziók.

A megoldás a következő: Készítünk egy TAny* tárolására alkalmas tárolót, és hozzá egy olyan templatet, ami a TAny* tárolónk metódusait használja, de minden egyes hívásnál a template paraméter típusára konvertálja a mutatót. Így szinte nincs többletkód, vagy csak nagyon minimális.

/*a nem template tarolo egyik metodusa*/ CAlaptomb: public CBase { const TAny *Elem(TInt aIndex) const; }; /*a template tarolo ugyanezen metodusa*/ class CTomb : public CAlapTomb { inline const T& Elem(TInt aIndex) const { return (*((const T *) CAlapTomb::Elem(aIndex))); } }

Hibák elleni védekezés

A hibák megtalálásának legjobb módja, ha a hibás állapot észlelése után minél előbb jelezzük azt. Több módszer is van a hibás helyzetek vizsgálatainak elhelyezésére a kódunkban.

A megadott feltétel nem teljesülésekor az _ASSERT_ALWAYS minden esetben jelez a neki megadott függvénynek, míg az _ASSERT_DEBUG azt csak debug fordítása esetén hívja meg. Legtöbbször a User::Panic függvény szokott lenni a jelző függvény, amelynek éppen az a célja, hogy a programozói hibákról adjon tájékoztatást, és a futást megszakítsa.

Célszerű bonyolultabb osztályok esetén az osztály helyes állapotának az ellenőrzése minden publikus metódus elején. Az osztály helyes állapotának ellenőrzése _TEST_INVARIANT makróval tehető meg, ha az osztálydeklarációnk legutolsó sorába a _DECLARE_TEST makrót írtuk, és biztosítottunk egy állapottesztelő metódust, melynek neve __DbgTestInvariant, de ez (a nevétől függetlenül) nem csak debug fordítása esetén hívódik meg.

Hasznáható még _UHEAP_MARKEND heap tesztelésére, megvizsgálja, hogy a heap ugyanannyi szabad helyet tartalmaz, mint egy korábbi hívásakor.

A típuskonverziók biztosnságosságát is ellenőrizhetjük. Ugyanakkor a C++-ban megszokott dynamic_cast operátor nem használható, mert túl nagy erőforrás-igényű. A többi konverziós operátor használható, a régi kornverziós markók: REITERPRET_CSAT, STATIC_CAST, CONST_CAST, elavultak, a fordító nem támogatja őket, hanem a C konverziós operátort helyettesítik be.

Aszinkron szolgáltatások, az ActiveObject keretrendszer

A Symbian operációsrendszer megalkotása során fontos szempont volt az erőforrások hatékony felhasználása (memória, processzor, akkumulátor). Ennek megfelelően az alkalmazásunkban nem célszerű szálakat használni, ugyanis a szálak közti váltogatással járó context switching több erőforrást is használ. Azonban párhuzamosan futó szálak nélkül egy hosszan futó folyamat blokkolja az alkalmazás fő (és evvel együtt egyetlen) szálát. Ez egy interaktív alkalmazás esetén megengedhetetlen, hiszen a művelet vége előtt képtelenek lennénk a beviteli eszközöket olvasni, illetve a felhasználói felületet frissíteni. Ezen feladatok megoldására a Symbian egy, az azonos nevű tervezési minta alapján bevezette az ActiveObject keretrendszert, ami az eseményvezérelt felhasználói felület alapját is képezi.

Minden Symbianos vezérlési szál rendelkezik egy úgynevezett request szemaforral. A request szemaforon keresztül jelezhető egy szál felé, ha egy általa indított aszinkron folyamat befejeződött. A request szemaforra épül az aktív ütemező, melyet a Symbian rendszerben a CActiveScheduler osztály implementál. Az ütemező elindításakor a szemaforon várakozik. Amint egy esemény bekövetkezik, a szemafor felemelkedik, az ütemező pedig megkeresi, hogy a bekövetkezett eseményt ki kezeli. A kezelő megtalálása után lefuttatja az eseményt kezelő függvényt, majd a szemaforon keresztül várakozik a következő esemény bekövetkeztére. Az eseményeket egy, a CActive osztály leszármazottja kezelhet. Ha egy adott eseményt kezelni akarunk, akkor egy megfelelő CActive leszármazottat kell az ütemezőre felfűzni. A jobb hangolhatóság érdekében az egyes kezelő objektumokhoz prioritást is rendelhetünk. Egy ütemezőn belül rekurzívan további ütemezők is indíthatóak. Ezt a lehetőséget a Symbian dialógus ablakainak implementációjánál fel is használják.

Az aszinkron műveletek eredményének, valamint az események kezelésén túl az ActiveObject keretrendszer alkalmas hosszan futó műveletek részekre bontására is.

A kezelő, azaz az aktív objektum

Az ActiveObject konstruktorában kell megadni az objektumhoz tartozó prioritást. A prioritások előredefiniált értékekből választhatóak. Az objektum teljes konstrukciójának végén érdemes csak az aktív ütemezőre felfűzni, ugyanis ha egy objektum konstrukciója során kivétel váltódik ki (a konstrukció második fázisa levael), akkor egy félig megkonstruált objektum lesz az ütemezőre felfűzve. Ha korábban kívánjuk az objektumot az ütemezőre felfűzni, akkor a destruktorban oda kell figyelnünk az ütemezőről történő leválasztásról. Egy objektummal célszerű egy típusú műveletet kezelni, valamint egy ütemezőhöz nem tartozhat egy időben két aktív aszinkron kérés.

Az objektumhoz tarozó esemény bekövetkeztekor az aktív ütemező az objektum RunL meghívásával jelzi azt. A művelet végrehajtásának eredményességét az iStatus TRequestStatus típusú adattagja jelzi. Egy művelet végrehajtása sikeres volt, ha ennek értéke KErrNone. Az iStatus értéke KErrCancel, ha az aszinkron műveletet visszavonták. Minden más érték hibát jelez.

Ha a RunL metódus leavel, akkor meghívódik az objektumunk RunError metódusa, melynek paramétere az a kód, amivel a RunL leavelt. A RunError alapértelmezetten a paraméterül kapott kóddal elpánikoltatja a processzünket. Amennyiben lehetőséget szeretnénk biztosítani az aktív objektumunk által kezelt művelete visszavonására, akkor azt a származtatott osztályunk DoCancel metódusának implementálásával tehetjük meg. A műveletet megszakítása valójában a Cancel metódus meghívásával történik, mely belülről a doCancel -t hívja meg.

Aktív ütemező

Minden UI alkalmazás automatikusan rendelkezésünkre bocsájt egy aktív ütemezőt, így ebben az esetben nem kell annak inicializációjával foglalkoznunk. Konzol alkalmazások esetében azonban nincs alapértelmezett aktív ütemezőnk. Ilyen alkalmazások fejlesztése esetén (amennyiben aktív objektumokat szeretnénk használni) a fejlesztőnek létre kell hoznia egy aktív ütemezőt és installálnia kell abban a futtatási szálban, amelyben azt használni szeretné.

CActiveSchedule* scheduler = new (ELeave) CActiveScheduler(); CleanupStack::PushL(scheduler); CActiveScheduler::Install(scheduler);

Annak érdekében, hogy az installált ütemező megkezdhesse az események fogadását, azt el kell indítani. Az ütemező a CActiveScheduler::Sart() hívással indítható el. Fontos azonban, hogy az elindítás előtt legalább egy aktív objektumot felfűzzünk az ütemezőre, ugyanis a start hívás blokkolja az aktuális futtatási szálat, különben ha nincs esemény, amit lereagáljunk, az alkalmazásunk holt pontba kerül.

Active object aszinkron hívások kezelése

A kliens-szerver keretrendszer

A Symbian OS egy mikrokernel alapú operációs rendszer. Ennek következtében az operációs rendszer szolgáltatásainak jó része úgynevezett szervereken keresztül érhetőek el. Ezeket azért hívjuk szervernek, mert használatuk nagyban hasonlít a hálózati szerverekéhez, azaz a használat előtt fel kell venni a kapcsolatot, majd a műveletek végrehajtása után le kell zárnunk a kapcsolatot. A szerveren végzett műveletek szinte minding aszinkronak, így ezek végrehajtását célszerű aktív objektumokba csomagolni.
Ilyen, sokat használt szerver például a fájlszerver - ami a fájlrendszer elérésére szolgál-, vagy a szoketszerver - ami a hálózati végpontok kezelését támogatja.
A szerverek a kliensekre nézve más futtatási szálban, de az esetek többségében mást processzben is futnak. Ennek következtében a szerverek IPC csatornán keresztül érhetőek el. Egy szerver implementációja a következő főbb részekből áll: