PLIB REFERENCE Version 2.10 February 3, 1995 (C) Copyright Psion PLC 1990-95 All rights reserved. This manual and the programs referred to herein are copyrighted works of Psion PLC, London, England. Reproduction in whole or in part, including utilization in machines capable of reproduction or retrieval, without express written permission of Psion PLC, is prohibited. Reverse engineering is also prohibited. The information in this document is subject to change without notice. Psion and the Psion logo are registered trademarks, and Psion, Psion MC, Psion HC, Psion Series 3, Psion Series 3a and Psion Workabout are trademarks of Psion PLC. TopSpeed is a registered trademark of Clarion Software Corporation. Intel 8086 and 80286 are registered trademarks of Intel Corporation. IBM, IBM XT and IBM AT are registered trademarks of International Business Machines Corp. Microsoft and MS-DOS are registered trademarks of Microsoft Corporation. Apple and Macintosh are registered trademarks of Apple Computer Inc. VAX and VMS are registered trademarks of Digital Equipment Corporation. Brief is a registered trademark of Underware Inc. Psion PLC acknowledges that some other names referred to are registered trademarks. Contents 1 ANTRODUCTION es oes oan SSAA Fisles cetesesvenceeteetoadbes nnsee es dace oeresseee tes eee eee ncese 1 PLIB;> errr ESS 8 Asynchronous Requests and Semaphores .............scccecsescocsccssscosccesccessccccenecees 79 SEMAPNOLSS: cans snases orca seeseteta lee Cease ied 0 Oe ea ooo 79 Rrocess SCHEAUIING: ccs: Nive te Maa ccec ttt tae soe ee ie eccicodee 79 SMAREG ACCESS icon anes detescedaa tees sttvaete wis seetees seeks eh ae ee 80 SuUpplier=CONSUM EIR ee eas aicasees chevec stele ee Eee, es ne 80 ASYNCHFONOUS FEQUESTS.............ceescescenccesecuscccecssserceseverseseeseascesssecensenceuss 80 TNE MOeSEM APOE ti. swceeiie steed sca doyereesvalsce ee ere ee ee eee 81 SLALUSHWOKKS (i. Sacuse vegas tate ds dees sadessaasvandrdpaeonsad vaianse aoe OMe 81 Cancelling an asynchronous request .........ccccccssccsccscceesccccecceceesseseuceass 82 Waiting for a particular Completion .............cscceseccsesscsescesecusscnsecascenerees 82 Constructing synchronous fUNCtIONS.............ssccssecessscrsceceeceseceasceseteesss 82 VAIL TANGIEIS serine ssce usenet teeccmeucadlida Sarma Ne na treet: care eo e ioe ce tenascin, 83 Polling rather than Waiting............cccsccoscssscocescssersssesecoesaucessestensvarseeene 84 Attached 1/0! GEVICCS .sis5..cc.ecnevccnrccesedoas gee aekeste ce caceescdedes oeetGs Novela ose 84 Primutive:-semaphore functionsm weve! ee So ee eo es 84 Create a semaphore: (p*Semcrt) Aivic. ti. tiien trevislebetecsdece sess tec te kee scawe cas 84 Delete a semaphore (p_semdel) .................cecccsscousceseccaceceecsarsavssosceees 84 Wait on a semaphore (p_ WAIE)E te Retcete ts ecoanes bass retacwew ens eaciesiat vevcash Me ec eenes 85 Signal “a semaphore (p. Sigttal) 2+. 211. sco00000.04ececeedeveneceneesecscaved¥ideeseoede 85 Signal a semaphore rn times (p_SigmalMm)..............sssccsessecccesennsecessscnsccs 85 Signal a semaphore with no re-schedule (p_signalnr)............sessscecsseeeeens 85 The I/O ‘semaphore «.. ive tte ks SO ee ee ot 85 Signal the 10 semaphore (p_iosigmal) ...........c..cssssecssseescesesensceceeseceucess 85 Signal the 10 semaphore of another process (p_iosignalbypid) ................ 85 Wait on the 10 semaphore (p_iowait) ............cccsssccseecccccseseusvecevesenceesees 86 Allow any wait handlers to run (p_ioyield) .............cssscecssccesccescceevenseeese 86 Wait for a particular request to complete (P_WaitStat) 0.0... cecsecsecseneeees 86 Wait handlers: or ete. ae Aes ue i oc TE? 87 Add a wait handler function (p_Svecadd) .............cccssssesesccesseseseeccscensees 87 Activate/deactivate a wait handler (pesveccall)ccrt efi itee i scvecnctees asses 88 Remove a wait handler (p_Svecrem).............csscsssccssscssseceesceseceeceeessesees 88 rn DOS SYStSMM eo sccastes divas aiweun adic ne saules ousvacedeseissuowsvedsnbovees vassdccanticn tsaurtuesede eaaducs 89 VO. Device: Driers: vsexcvesceeisSeist te Bias to cdeastete tobeddn Coietiesctl eon eee dots 89 ED Ds anidiPODS \scsccsee Meee es Pte es Sie i ciateg th es eb Ee ote a ea waik 89 External device drivers ysssiiicecesss aveckssseic sce dovek ee eee vo aa so nwesies 89 Opening a channel to a device ...........cccsssuccescusceccsccesccevcoscesceeceveesesacs 90 Operations on an open 1/0 Channel...........sccecssecsecescesescccecscescesctesteccess 90 TNE: filGESORVOR ie. cetdeacectescees wxvewss cs easy hideesesacs ie iiami cae dioey dawcevedbi cus ce 91 Attached GrivGrs issn cesscocssccacasssasvedeiacoaatuaseacasecmcuseoeecuacksceteanecattaese ae 91 Channel-based |/O:funetions snide relic he See a ens oee 92 Open a channel to a device (p_Open) ..........c.ccsesseccescesssesceecsecseescnseeeees 92 Start anvlO operation: (p 108) .scsccccascscsvivesianvscssacivesessancdasetiaeusedvsstecss 93 Start an I/O operation with guaranteed completion (POC) ccevsece ot our oveavess 94 Start an I/O operation and wait for completion (p_iOW)..........cccscesseeseeeee 95 Close:ar 1/0: channel(p: ClOS6) ssc vsccncecdoseaccescbdvcheve Olas disse deactshonbcsdaveceekes 96 Read from an 1/O channel (DOA) sic. ccusecs chs iedass da Fist yas oeerdein dt Retevedieceones 96 Write to an 1/0 channel (p_ WOFILE) vsusr SuslsyenicvorilcSutougecncaatialhleides tered ad vovee: 97 Cancel requests on an I/O channel (p_iow(P_FCANCEL))...........cscsceececees 97 Device driver fUMCHONS: cess ccrscemncstynaveessunans vo paniieeeetessscven re eee eeocalans 98 Load a logical device driver (p_loadldd) ..............c.ccccssssssecceeneseseseavesoeee 98 Load a physical device driver (p_ lGadOdd) ee. sieves is... ee eek: 98 Delete a device driver (p_devdel) .............ccccccsssessseccccececersceeseececseccanens 98 Query the number of units supported by a device (p ) GOVQU) ...-ccsccscscneees 98 Eindvallsdevieess(p: devirid) aici: orsta. cose Sicevuiesbteereons loses os ste ee ccken dens 99 Simple COMSOE/O® sfc 131 Read media information of a local device (p_locdevice) ...........ceccscsceseeees 132 Direct read of local SSD (p_locreadpdd) ..............sesscevescesescssscesccusscancs 133 Operations on directories and fileS ..............cscccesccssscceescescesecseccescenscueesease 133 Geta directory list, (pcopen(P FDIR))sst202 thie, ih occc cc tees RRR Bion ica 133 Return file information (p_ finfo} Preeti er eSBs cE TO AS Pee 135 Test for the existence of a directory (p_testpth) ............:ccccssssesceseeeseoven 136 Rename a file or directory (p_reMamme) ............ccsecccsesccescceessneveseuesnseeues 136 Delete a file or directory (p_ delete) ............ccccccssssesecsceeccecsesscssssesersnsese 137 Make a new directory (p_Mkdir) ...........csssesececessevscevecsececcsssssessnananeneees 138 Set file attributes or label medium (DUSTStAt)s crecccanccusdaacesedsds cecevevocessee 138 Set file creation"date (p tdate) .c.i2<.<:-castceses-sscecsce seein duanognaeerceatvievavevne 139 Binary SiG ACCESS ise niasiieny cette aca ctecatwts beluga t ewsdanve Ra onegeacansce war cotebageasniia ter’ 140 SNaredsaccess so... eye Se ecl adele caica se dediciatevecee att ce otmtiocesadaccieseadeeuitess 140 Example of binary file access ..............cceccsccseccssececcectucascuccseeserssneseeess 140 Open a binary file (p_open(P_FSTREAM)) ...........sccsssccssesscesscnsssesenssceece 142 Close a binary file channel (p_ ClOSE) finns ieusucscesieinsoedsscisieisocaginaccvasavens's 143 Read from a binary file channel (p_read) ..........sscccccscscccssssceesesseseseseneees 144 Write to a binary file channel (p_write).........s:sssscccsssssseseevcecsececeeecseseees 144 Position a binary file channel (p_ TT: eae, nA A Cd ec 144 Flush internal file buffers (p_iow(P_FFLUSH)) .............csssccseccsesesecveseeees 145 Set end of file (p_iow(P_FSETEOF)) .........ccccccsccsesscscscsscecesssessessessnsesees 146 Cancel an asynchronous file channel request (p_iow(P_FCANCEL)) ......... 146 Stream textile ACCESS ici ccs uc ceescecasncteere dat ces Secvesbaavieiveoce nce socecsereilciac ed 146 Open a stream text file (p open(P FSTREAM TEXT))..........cccsscsesseecscess 147 TOXDTIS GCCESS) cdeccwisdsuais Caniysaners deueiaesSacedaoce etre Se taa eee ee aaa oe wae he ees 147 Open: text tile (p-apen(P IFTEKT)) scviscandeveasyassnssacetbveuvouvcecncweesmoaseare 148 Close.a.text. file channel.(PscloS@)escicceeessscaesinasn-danceupwnctneeneerverrsersrterte 148 Read from a text file channel (p_read) ............cccseccssescnseecsscceensceenscesees 148 Write to a text file chamnel (p_ write) ...........:.ssscsccsssssesscescecsuscceseseseeeees 149 Position a text file channel (p_ GCC net focrcce carer torte ever ars et ote 149 Flush internal file buffers (p_| iow(P_ FREUSH)) isccaesezedsivsdascceccctavensseoostass 150 Set end of text file (p_| iow(P_| PSETEOR)) wissen iasaceviadsviviesvecsnustwnniie vdateecasuss 150 Cancel an asynchronous file channel request (p_iow(P_FCANCEL)) ......... 150 rr py 12 Processes and Inter-Process Messaging ...........2.ccssccossecavsccesssecceccccccnsscssssanseees 151 PrOCOSSES. oss iiis. oo rivecnnvssocetuasueccacerenscteoecasctoseesecseet eves lrepsscneescaouies convene 151 SYSTEM [PLOCESSES.. Minh S coi castes sckesvuossacesocseuee cohadsoecdecanncdeeedevusedsvebennd 152 Process ID and process control DIOCK...........cccsssscscscccceccecccascscssescecscnss 152 PFOCESS ‘States vetieoiiis oechnnsswas Pen ccbesn se caei ete ticks totos (ete hives etutertoveceverviness 154 PrOCOSS QUEUES: 5 ...80s.is:4/6000 aves ceesaed sodosdawiceis an sduasepstabecd tie ietavetstey Samos 154 ProGeSS ipriOritieS: sic: c.sccsercs cacacts evsdevceiascacaccat odes tabupeneawacrsecdevecdersaavewe: 154 Peeemptive SCHEGUIING: eevee cacwiss e280 os eedeus S5 ees sein nec un tes boeeee ba neee te sans 155 PFOCESSIMAMES oo eiececiens decease cede tote O0k 53 FoSes eae sa Ne ee 155 Reserved statics (magic statics) ................ccccceccscscsessecscesecsecseccusescecess 156 Shared code SCQGMENts...........sscecssscavcceceecessusccsceccvevscececssesceseetancecaees 158 IMage Tiles 22.55. Feccuc. Menem caves etece at daeheeddebawescotheos acsnesaeeuieedeecele seventies 158 PLIB REFERENCE SSS Process: terminations cards cc eat tees focave ede cce Ric svus Sites TOD one sean 159 Creating a) PrOCess ci. c csi csvsstenenes sO to et 160 Load an IMage (DLOXECC) jm. ses ches EN ears Tsytie seas dae en oleae es teen ne 160 Load an image asynchronously (paexeccasynic):; irk Sethe. sch Ise does 162 Create;a,process: (p.pcreate) sc. ish lee esse See oc ree ona veceaes 162 Operations on the Current ProceSS.............ccccssccscsecscascsccsascssesesscesesecsceeces 163 Get'this process ID (psgetpid)s.v...:2.ccecteeeet cote cc eh co ee 164 Mark this process as non-active (p_UNMarka) ...........csccccseseeenssccesceceuess 164 Register activity: (p=tickle) ..ijciscvess..dcateecer. Te oDeeieeees Meee Men PR lei cvaze 164 Mark this process as active (P_Marka) ............sseccosccosconssosccecesncceuscenacs 164 Operations ‘Oni any ProCceSs i... site. <.arecsvevosovacsiedecessisoncetdcerccccacacteisusacvvacvevs 165 Geta PrOcesstpriority (Bb GOtp li) i. .5.0s.1c cote os, tlinsecdncecaeesacinrcst éetecms vies 165 SOtlay PrOCESs MniOrity. (DE SCtOM) cass cares cece nsse tan ce ukhetegOlsea ston vsneonsaeerasiaciass 165 RESUME a Process (P_PFESUME) ............ccesccescescoeccesteececcecescesseerenscaesss 165 Suspend a process (p_PSUSPENA) ...........cccsescsecssesccnceasecesscsccescesercanescs 165 Get a process:name by ID(pipname) svete. Me athin Siete bos 165 Rename a process (Pp _Premame) ..........csccssssesccecceccesteceucseceecesseeccausenvss 166 Get aiprocess ID by name (ppidfind) svate....ccts¥ee fed. con. enc sc hl son saas 166 Find: all’ processes (pgprind) 2.20) s.cAtvcrz. 22ers erie he Maske tees daanees 166 Determine the owner of a process (p_GetOWNET) ..........cceseccsseccceevecneeees 166 Accessing a process data SEQMENT...........cscsscecsecsccccececssscseetsreseecnceeaccssess 167 Copy data from a process (p_PCPYfr)..........cccssscsssssceescceuseceseveessesseuseos 167 Indirected string copy from a process (p_PiSCpyfr)...........s2sccsseccesseeseaees 167 Copy data to a process (P_PCPYtO)...........cccssccseccoetensccessescenscescesscssenses 167 IMtEr-PrOCESS MESSAGING ..........cceceeescescestenecaccusesseecuscersuseescessescssaseeaeseess 168 Message: Slots P5202. tees sescua tas suc tustaca cote snes s ataye ses ote ti oee cht inbias oCrouctvaves 168 Whatetherservertdoes ererrcrraserreecertrsverteetrer rer rae ee 169 What the: client does. i..c2isivccisevacescssctveet sees t cue ccs cat owas eves vasetutoestedsuduses 169 An example Of a S€PVEer ..........ceececsscescesececceeteccsensuscusncevecseevcesesencataes 169 Corresponding client code example ..............ccccecececocecasecescccscetaceececeecs 170 ASYNCHFONOUS MESSAGING .........ecescseceecsecenctecuvsuccscucescecsceecsseeenesassoenas 171 Message processing Order ...........ccssccsccsecsecssenccececcecsccscessecesausecseececes 172 SEPVEM FUNCTIONS siccdvcscacessate cevsdecesetore Steer ereons oce teen totes ces wont ume ethusuavis 172 Initialise for message reception (P_Minit).............cccccseceseceeccssccvesenseeues 173 Wait for message reception (p_mreceivew) .............scssssssecccccceeesereneese 173 Asynchronous message TECEPTION (P_MIECEIVE) .........cecscsccececsceresceceeens 173 Cancel a message receive request (p_ MGANCE]). ..scsvicaecceccscicicivevecesteseree 174 Free a message (p_Mfree) ..............ccssccssccosccssssrocsccsscesscovesseseecarcosscass 174 CleMtTUNCHONS seehissts caies dec sst cooncetho tues vscede suaace sous Gian be ou cecacecstnns es teens oot avi 174 Send a Message (P_MSENA)............ccsccsseesovccsccurevsceveeatscenatsceeseereonseaes 174 Send a message and wait for a reply (p_msendreceivew) .........cccceceeseees 175 Asynchronous send message and get reply (p_msendreceivea) 13 General System Services .............sccesccsssessevenveccuccseccvecceccecesccusaccesssersnsanteeseees 177 SYSTEM: INFOLMALION coersces le. ECE AER ca ens eee a aR ee 177 Get the operating system version: (Pp Version) ..c.eeivisecteseaevescesecestertias 177 Get the ROM version (p_romverSion)...........:sssscsesccscsseesceseesenesesceceeeees 177 Get the cause of the last system shut-down (p GOMES). ..s.ccccceesctestansoves 177 Get operating system data (p_Getosd) ..............sseccesscceesscestersesceeeeseeees 178 Get power supply type (p _getpsu) ANeea sed sbnlsien tesa dons eis Se 01SR ee oe ode tevieen A eee Ues 178 Language and COUNTY s.ccs.csowiv es oeostensaeeieeaceadooas ceed fer een eter eet 178 Get the language code (p_getlanguage) ..............ssccsecssscesceesccaseescssences 179 Get operating system text (p_Gettext)............ccccccsccesccscceeceseseeceusceeeees 179 Get country-dependent data (p_getctd) ...........ccccccccccsscssssseesvessecevevesees 180 Set country-dependent data (p_ WSCtCtd Face R Stee Ls whe 180 SWITCHING ‘OM! ANG OFF scsces cotadicencctesss xn doe oecnsd ceed veddaiasiee bets soueweeeversee «cdveden ioe 180 SWITCHOPfa(PLOth) assis cc dives cos caveueds Wediavwesdea canes oeseu cecves bareuous uasdesvedaesevcs 181 Get the auto-switch-off period (p_getauto) ...........cssccssecsseccensseeesseeeeeeee 181 Set the auto-switch-off period (Pp S@tauto) ...........cccceceecsscesecestaeeescuscnes 181 CONTENTS A en Get switch-off state when mains is present (p_getautomains) ................ 181 Disable/enable switch-off if mains is present (p_setautomains) ............... 181 Allow-auto switch off (p. allowoff)et... %sc..c.50c8e.ceesteceuedDsosececedtecessesees 181 Enabie/disable the ON key event (p_setonevent) .............scccsscesesesseeseeees 182 POWErESLIDDIYV feist tits rn Rite. Aine stcerc ce son ien Oe aeeacc tiet saclatade Cote eee vied ae’ 182 Get power supply status (p_SUpPply)..............ccscsecescccescesenscssusccesonsecens 182 Get additional power supply data (p_supplyinfo)...............ccscseseeseseseeeees 183 Get battery warning and maximum levels (p_wsupply) ..........c.sessessseveves 184 Get the: battery type (pgetbat) «orice csssr once cccce s+ concecceeseedancsseaccas eee eness 185 Set-the*battery type'(prsetbatys tei. Me sort tects caveucted ua cieecevawsss oveota's 185 Keyboard eet cc ccce cicnc sc cvaes tees eee ees es ee aaa Saeed diccosokes 185 Get the state of all keys (p_getsCancodes) ..............ccseccessseescesscesseuceeses 185 DiSpl ayci.8 225, hectesc sees caee ve Mises zac boven eanes odes dee eeeetss oxaeeeaneus bd sade ebualduvacteateen 186 Get the system display type (p_getlcd)...............ccsccsescccsceceseesesesseussaves 186 Change the LCD contrast (p_Icdcontrastdelta) ..............ccscsssceesseessenseeees 186 Get the current LCD contrast (p_geticdcontrast) ............csccessssesessenseeeee 186 Switch the backlight on or off (p_backlight) ................ccssesesscssccessseeenscs 186 Set the backlight control value (p_setbacklight) ...............cssccssecessseeceeees 187 Get the backlight enablement (p_getbacklight)..............ccsscssssesccesseecceees 187 SOUNGp i coisitsvdectertineesdee.asisacsd Mather steven eve ste dees sezc skies cacades sacauns vce ee a 187 Make a sound with the piezo (Pp SOUMNG) .............ccceseccevencessesscuseeusceanses 187 Get the sound flags (p_Qetsnd) .............ccccseecsccseteesecstsvcesseuseeeeeusoneeees 188 Set the sound flags (p_ Setsnd)............scccsscsecseeseconsccscneneeseussesceesonaneees 188 SOUN ON the SerieS 3a.........ccscsscecscsscncscaresssensevcscecsesscaseessceserstencerecsnsces 188 SOUMGO THES 5c fcu se. Sez vat cc taxes tcast eas ceeeeteglaxecaneace ros Soy ck zantac etnias Somes a ewite 188 The A-Law encoding SChEME...........cccsccocsoeteccesuscessecevccscsccecsectectcsceees 189 SerieS 3a SOUNd SYSTEM SEFVICES ...........ccceecccssnccccecsccsceseccesecscacsescucnecesens 192 Record a sound asynchronously (p_recordSOUunda) .............ssccesscevseeceece 192 Cancel sound recording (p_recordsoundcancel) ............scccsscssecsneseeseeeces 192 Record a sound synchronously (p_recordSOUundw)..........sscsscsseseceescecesees 193 Play back a sound asynchronously (p_playSOUNdA) ..............ccsscesseeeecesceesees 193 Cancel sound playback (p_playsoundcancel) ..............ccsecssccecsenesceeeenecs 193 Play back a sound synchronously (p_playSOuNda) ..............ccssccseceneerensenseuce 193 EXitsthe*SyStOM wisscccscssveeestsacsceivsccnevescebvesecs canstictent acsaedigue ster tialence caeubes 194 EXIE TODOS: (p:MWEXIC) casters ag trsescttcrccutcenec tamed eon dvaeteen tttanes secawinlvenic 194 gy T4 Database Fil€S vicc.ccascess caseveeervosGeasecuteecsescadatescecenceccdeceeaicsdes vecerece accra booecace cas 195 Overview of database fileS................scoscscscoseessccccucscescecuceseecesecscansecseecenss 195 WING THE HO AGOF :, cveee ve wccewedechaeecevcsescuuicsstatecdwackewea tes cocdadeg devcolePecceve ted eced 196 PRECOMOS ane siwccceactesis shin ssasen ve onds tases vaucesenamsuan tirana staostocdfdusnettens patterns 196 String stieldS) ss. LR vas eee cc acsus oescavec eve ssa hee ecavecn sees sauecavees covsdvescensueys 197 Number, ofsrecords ®... See. 8 00 see te ac decd ese os Re oes 198 ENG*Ofatilesr6COr \s. case sczicdecssscacteeteucsuvee eter boc ea fete eee nia Fede ek Datos 198 Database: filessand'OPL satin ei hose ek ase Se kn osecs 198 DBF TUNCTIONS s. cocesescesweucDiy scar sue ddteetros de civsh cectcaytess iedivevessunacueee cuca hectices 198 Open a database file (DbDfOpen) .............cccecceseccsscescusccccvevecsssscuccsancnees 198 Open a database file (DbfQuickOpen)..............cescssecoercesrescuscuscessesccecess 200 Close a database file (DbfClose) ................ccccecccsecvceseeserccseessraeaessaeaces 200 Flush a database file (DbfFlUSh) ................cccssecscescecsscoescecsesuarseasscscecs 200 Notify that the DBF buffer has been overwritten (DbfTrash) ................0 200 Copy down a DBF record (DbfCopyDown) ...........:sscescsscsssscuscsseccceceaenes 201 Compress a database file (DbfCompress)..............ccsccccscceceseecaseescscnceres 201 Copy a database file (DbfCopyFile) ...............ccseseccsecsecsccccescecceccecucncnens 201 Find the size of a database file (DbfFileSize)................c.cccccecscsccscececsceus 202 Read a DBF extended header (DbfExtHeaderRead) ..............ccscececcesscseuce 202 Write a DBF extended header (DbfExtHeaderWrite) ................cccecescecsceee 203 Read a DBF descriptive record (DbfDescRecordRead) ..............cceceseveceeee 203 Write a DBF descriptive record (DbfDescRecordWrite) ..............2..cceceeeeee 203 Get the DBF version number (DbfVersion) .............ccecsscececscececcececscsevens 203 Read a specific DBF record (DbfAbsRead)................ccccecsccecescseesceecsceses 204 PLIB REFERENCE A Read and sense a specific DBF record (DbfAbsReadSense) ................... 204 Read the next DBF record (DbfNextRead) ...........ccsccscossecsscevescccerecacecece 204 Read the previous DBF record (DbfBackRead) ............c..ccscsccecsececoveereees 204 Read the first DBF record (DbfFirstRead) ............ccccececccaccececececcccesecscecs 205 Read the last DBF record (DbfLastRead).............scccsscsscsssccececescccccncseess 205 Append a DBF record (DbfAppend) .............:ccccccaccsevecceescecseecsavsecuceens 205 Erase a DBF record (DbfEraseRead) ..........ccceccccssssccscscccececscucececscscscencs 206 Update a DBF record (DbfUpdate) .............csccecsccscsccscnvcesecesovssscseseecncs 206 Find a DBF record (DbfFindReadField) ............ccccccscsescccnscscecsctcsccocesscecs 207 Find a DBF record (DbfFindRead) ............ccccccscceccucsccccccscessscccesesceucceers 208 Sense the current DBF record number (DbfSemse) ...........cccsceccesececececces 209 Count the number of DBF records (DbfCount)...........ccesececcscsecerecececeves 209 A 15 Object Oriented Programming ..............sccssccescosscnsacecvercecuccsersecsscstevccecceseuaverce 211 CIASS OS rics Succ cos otvveou sin cata bos sarees Rega Ses see Pee UI alfa c 211 Class: CESChPLOM «ices ccoues fee ce vas ee esc dasnack Tews olbbce bee PIN ce Mees: 211 Object-IMsStances. wi....cesecdeedeecs. ce cche ell b ees cc SP ee iia 212 ODject AEStrU ction asi cee cscs sc celeceecs ova leaex Pee ee lis MUM EO ee es codecs 212 Categories wiser tecccccd ccccieleawsya gadis caedeaya Deva bedanab use cones veo bareees Hide coveted: 212 CAaLeGOry NalidleS svsacnsa cose se caavbavvadete staderec Mes. elb tied gs MRE bs bas 213 Category Numbers. ccsccecssscess ibe. cascerstecs tere vsveteen ere Rta saws 213 DY MAMIC INK AGE... cc. Scdecssesceecsscdasce soles letouna ce cteevees Peeaccle Stes Meo veai saad 213 Referencing by category handle...........ccsccscsccscscessuscescusscsssccscsenseensass 214 DY US ac cae vedere ein Rtie candor avds cola aaei dR avec nade aaa g cose ee een eens 214 The structure of a loaded and linked Category........c.ccccesccscovevcovessesceears 216 The structure of an unlinked Category ...........cescssescossescescencscescesecseccees 216 What happens during dynamic linkage..............ccsccecscssceseecsesseecseerencees 216 MESSAGE PASSING ...........cecececcecsecescectecsstesceceerssstusussesevcesseeesecuecesesneseses 216 Calling conventions for method fUNCTIONS...........cscceseesnccceceecescuceessceeues 217 Performance of Message SENGING........cccescscevsscesscecscescustsccsccvccscecceceses 218 DUS ieee aS esata Stas Dacia seein case aioe el nc ae eter ee eta aaa ie RSME chet Bid eval « 219 CateQOry FUNCTIONS sis csc vecceveviosanussseciesneeived senna cddooesandeodeoeeuieresaveroedasdends 220 Load’ a: DYL(p oO adlib) a. 25 Scie ciSccett totes ctaicace at the beginning of your C source file. The plib.h header file collects a default set of header files that are sufficient for the functions described in this manual. For most source files, plib.h will include more than you actually need. Once you are familiar with PLIB and if you can be bothered, you may wish to browse around the header files in the \sibosdk\include directory to work out which files you need to include in a particular source file. In all the structs in the PLIB headers, we have been careful to organise the members so that 16-bit (or wider) variables are on even address boundaries - since the 8086 processor can fetch a 16-bit word in a single cycle rather than two if the word is at an even address. 3The code, written in 8086 assembler, that provides a C function interface to a ROM-based service is sometimes called a C shell. 4 1 INTRODUCTION p_std.h Because Psion has had ten years of working with C using tens of different C compilers to develop for tens of different target computers, we have of necessity developed a fairly defensive approach to our C sources. Rather than using the C variable types and declarations directly, we define our own to give us the opportunity of re-defining their meaning, depending on the compiler. All these definitions are in p_std.h that is included first by plib.h. The following extract from the Clarion version of p_std.h is for declaring functions and data: #define GLREF_D extern #define GLDEF_D #define LOCAL_D static #define GLREF_C extern #define LOCAL_C static #define GLDEF_C where the _c and _D refer to code and data respectively and where: LOCAL_C are used to declare local functions and local static variables LOCAL_D GLDEF_C are used to declare globaj functions and global variables GLDEF_D GLREF_C are used to declare function prototypes and external global variables GLREF_D The following extract from p_std.h declares operand types: #define VOID void typedef int INT,HANDLE; typedef unsigned int UINT; typedef char BYTE; typedef unsigned char UBYTE; typedef short int WORD; typedef unsigned short int UWORD; typedef long int LONG; typedef unsigned long int ULONG; typedef double DOUBLE; typedef char TEXT; where the meanings of BYTE, UBYTE, WORD, UWORD, INT, UINT, LONG, ULONG and DOUBLE are as suggested by their names (and where a leading u means unsigned). By convention, we favour the signed variant in cases where it does not matter whether the signed or the unsigned variant is used. The TEXT type is used to indicate character data as in, for example: TEXT *str; where str points to a character string. The HANDLE typedef is used to refer to an instance of something - such as a process or a memory segment. In many cases, a HANDLE is actually the offset into the operating system's data space. This manual uses the above declarations in function descriptions and examples so you do need to know about them to be able to understand this manual. However, this does not mean that you have to use our declarations. For example, using our declarations, you can write: #include GLDEF_C INT main¢{VOID) € p_printf("Hello world'); p_getch(); return(0); > PLIB REFERENCE a es, SE, or, not using our declarations, you can write: #include int main(void) € p_printf¢("Hello world"); p_getch(); return(0); > It is entirely up to you. Calling conventions The content of this section is quite technical. Provided you: =" include plib.h = declare local functions before calling them = use prototypes when calling your own global functions you don't need to be particularly aware of the calling convention used and you don't have to understand this section (although you may feel more comfortable if you do). The only exception is for functions which take as a parameter the address of (and subsequently call) a second function. In such a case you must declare an explicit calling convention for the second function. In PLIB this occurs when using p_enter, used to handle errors, or when using an object-oriented programming message-sending function such as p_send. In these two cases the descriptions of the relevant functions include full guidance. For a greater understanding of calling conventions and the #pragma call declaration, refer to the TopSpeed documentation. With the TopSpeed C compiler you can change the calling convention using: #pragma save to save the current calling convention #pragma call to set a new current calling convention as defined by parameters that follow call #pragma restore to restore a previously saved calling convention You can also declare functions to use a stack-based calling convention using cDECL. The C header files containing the prototypes for the PLIB functions (and automatically included when you include plib.h) also contain #pragma statements that declare calling conventions on a function by function basis. For example, the prototype for p_bepy is effectively: #pragma save #pragma call(reg_param =>(di,si,cx),reg_saved =>(bx,cx,dx,si,di,ds,st1,st2)) GLREF_C UBYTE *p_bcpy(VOID *,VOID *,UINT); #pragma restore where the three parameters to p_bcpy are passed in the registers DI SI and CX as required by the BufferCopy interrupt (described in the EPOC O/S System Services reference manual). When defining a sequence of prototypes, you only need to bracket the sequence with #pragma save and #pragma restore - not each individual prototype. Prototypes for PLIB functions that take a variable number of parameters must use a stack-based calling convention and are declared using cbEcL as in, for example: GLREF_C INT CDECL p_iow(VOID *,INT,...); 1 INTRODUCTION In many cases (where there is a limit to the number of parameters), the same function is also offered in fixed parameter versions as in, for example: INT p_iow2(VOID *pcb, UINT func); INT p_iow3(VOID *peb, UINT func, VOID *a1); INT p_jow4(VOID *peb, UINT func, VOID *a1, VOID *a2); where you can use the appropriate fixed parameter variant to take advantage of a more efficient register calling convention. When calling your own functions, you don't need to worry about setting a calling convention since the default calling convention will apply. The default (register) calling convention is: #pragma call(reg_saved =>(ax,bx,cx,dx,di,si,ds,st1,st2),reg_param =>(ax,bx,cx,dx),¢_conv=>off) When the compiler comes across a function that is declared as taking a variable number of arguments, such as: LOCAL_C VOID PrintToLog(TEXT *str, ...) it ignores the current calling convention and uses a stack-based calling convention. Small programs Because the body of nearly all PLIB functions is provided by code in the ROM, C programs built for EPOC are typically significantly smaller than, say, the standard C library on a PC. The difference is at its most extreme when there is such a small amount of program-specific code that the size of the program is dominated by the code that is brought in from the library. For example, the following program: #include GLDEF_C INT mainCVOID) € p_printf¢("Hello world); p_getch(); return(0); > when compiled and linked for EPOC produces a program that contains fewer than 500 bytes of code. Depending on the compiler, upwards of 10K is typical on a PC. The amount of memory required to run an EPOC program (sometimes called the "working set") typically ranges from 10K to 100K bytes. For example, when using the Spreadsheet on the MC400 (a large program by any standard), you can load about 70K of code and open and manipulate a 10K spreadsheet with less than 100K of free system memory. In practice, it is quite possible to take advantage of the multi-tasking and run several programs at the same time and especially (given that the code is only loaded once) to run more than one process of the same program. The PLIB C startup modules When producing an executable using the linker, a C startup module is automatically linked in before the program-specific modules and the libraries. A number of C startup modules is supplied with PLIB where each module sets set up a different stack size (typically ranging from 2K to 8K). Except for the different stack size, the different PLIB startup modules are identical - they all declare the same basic structure for the process data segment - as described in the chapter Memory Allocation. The code in the PLIB startup modules is minimal - it just connects to the file server (using a FilConnect interrupt) before jumping to main (most applications need the services of the file server and the overhead to connecting to the file server is modest). Note that the PLIB startup modules do not set up the standard argv, arge parameters to main because PLIB is not particularly designed for command line user interfaces (although there is a mechanism for passing parameters when starting a process - see the chapter Processes and Inter-Process Messaging). Not all the stack that is declared in the PLIB startup module may be used by the program and you should subtract: 0x100 for any program PLIB REFERENCE See 0x300 for programs that use the floating point emulator (as described in the Floating Point chapter) SS EE ee ee ee ee ae Es Related reference manuals The ROM contains more system code than is directly accessed by the functions described in this manual. In addition, a standard C library (CLIB) is provided. TopSpeed C library reference CLIB is a version of the TopSpeed C library for the EPOC operating system. The functions in CLIB are described in the TopSpeed C Library Reference manual. Additional notes, including a list of the TopSpeed C library functions that are not implemented, may be found in \sibosdk\doc\clib.doc. The EPOC version of the TopSpeed C library supports the ANSI functions and most of the portable functions that are commonly supported by MSDOS C libraries such as Microsoft C and Borland's Turbo C. The less portable functions such as those that access the BIOS and graphics functions are not included. The benefits of using CLIB are: = portability (existing C programs may easily be converted) = — less to learn for programmers already familiar with standard C libraries Although the EPOC system services (and hence PLIB) has comprehensive support for floating point operations, it does not provide this support in a way that supports the floating point C as generated by the TopSpeed C compiler. This is described more fully in the chapter Floating Point in this manual. As you might expect, using CLIB in place of PLIB makes less efficient use of the SIBO architecture. In particular: = many of the EPOC system services are not available from CLIB (eg asynchronous I/O, inter- process messaging, the window server graphics functions) =" executables are larger and the process takes a larger data segment The executables are larger because, although CLIB uses the ROM-based system services wherever possible and fares better than the PC library, it is still a much "thicker" library than PLIB. The data segments also tend to be larger because the various CLIB subsystems typically require large static buffers and tables. For example, the following CLIB program: #include int mainCvoid) € printf("Hello world"); getchar(); return(0); > when compiled and linked for EPOC produces a program that contains 6K bytes of code (as compared with 0.5K for the equivalent PLIB program). Unless you are using the in-built user interface object dynamic libraries (accessed using object-oriented programming) described below, you can freely mix PLIB calls with CLIB. We expect most experienced C programmers to use CLIB and regard PLIB and WLIB (the window server library, described below) as they would regard non-portable components of any C library. It is worth converting completely to PLIB and WLIB when the desirability of making efficient use of memory outweighs the benefits of portability and familiarity. Window server reference The window server is a system process that provides shared access to the screen and keyboard (and also a pointing device, if present). The PLIB library contains only primitive console functions to input typed lines (with simple backspace editing) and to output lines of mono-spaced characters. The con: device driver provides row and column positioning and printing of mono-spaced characters. 8 1 INTRODUCTION Although the PLIB console functions and the con: device driver ultimately call on the window server for both user input and screen drawing, the window server is capable of far more than can be accessed via these interfaces. In particular, the window server can be used to implement graphical user interfaces and to display bitmap images such as maps and diagrams. The WLIB library contains a set of C functions that can access all the services of the window server. These functions are described in the Window Server Reference manual. The window server supports the following features: = a hierarchical system of overlapping windows where all drawing is clipped to visible areas = redraw events informing the client of areas of windows that need to be redrawn, with redrawing clipped to the invalid areas = multi-font (proportional and mono-spaced) pixel addressable text drawing in a variety of text modes and styles = fast bitmap operations ® drawing of lines, boxes and pattern-filled areas in a variety of modes = optional double drawing to a background bitmap as well as the window such that the window server automatically redraws windows as necessary Like PLIB, the WLIB library is a library of thin C shell functions that contain software interrupts to ROM-based code. I/O devices reference This Z/O System chapter in this manual describes the EPOC I/O system in general and the PLIB C functions that are used to access I/O devices. A device driver may be built into the ROM or it may be loaded from an external source (such as an SSD). To use a particular device you need to read a description of the device driver. The files device driver and the asynchronous timer device driver are described in this manual - in the chapters Files and Time, Timers and Dates respectively. All other device drivers are described in the I/O Devices Reference manual. The I/O Devices Reference manual describes device drivers that have been written by Psion - many of which are commonly supplied in the ROM. The device drivers described include the following: = Parallel port (PAR:) = Serial port (TTY:) = Console device (CON:) = Sound driver (SND:) Descriptions of additional device drivers will be added to the I/O Devices Reference manual from time to time. EPOC O/S System Services reference manual The EPOC O/S System Services reference manual describes the software interrupt interface to the ROM- based system services. Nearly all of the services are available from C using the functions in PLIB. The CLIB library also uses the system services where possible (the source for CLIB may be found in \sibosdk\src). Because the EPOC O/S System Services reference manual is intended to be used in conjunction with the PLIB Reference manual, the descriptions in the EPOC O/S System Services reference manual are comparatively brief. You would refer to the EPOC O/S System Services reference manual if you were writing in 8086 assembler or accessing a system service from OPL. In this case, you should refer to the corresponding function in the PLIB manual for a fuller description of the service (there is a list of the corresponding PLIB functions in an appendix of the EPOC O/S System Services reference manual). Being able to refer to the description of a software interrupt can be useful when debugging C programs. The EPOC O/S System Services reference manual also contains information on: = writing device drivers PLIB REFERENCE = writing an installable file system s hardware interfacing Object dynamic libraries The Object-Oriented Programming chapter of this manual describes a set of functions that provide run- time support for object-oriented programming where object classes are constructed by: = using a proprietary tool to define class property structures and to declare methods =" using regular TopSpeed C to implement the declared method functions for each class Object-oriented programming techniques are well-suited to implementation of graphic user interfaces and multi-threaded application control. The object dynamic libraries (DYLs) contain classes that may be used and/or subclassed to construct applications with a consistent graphical user interface. The classes supplied in the libraries include the following: = dynamic variable length arrays and large character buffers for building complex in-memory data structures ™ active objects that represent a variety of event sources for controlling multi-threaded programs = an extensive window class tree supporting such graphics user interface components as menus, dialog boxes and edit boxes The classes that are used to build user interfaces may vary for different SIBO machines. At the time of writing, a user interface object library had not been constructed for the HC range (since there is no requirement for a consistent user interface on a machine of this type). The object classes are organised into dynamic libraries (DYLs) in the ROM. For example, the MC400 has: OLIB.DYL containing classes that are independent of the user interface WIMP .DYL containing classes that implement the graphical user interface The object-oriented message passing mechanism also serves as a means of calling far code (such as the code in ROM-based DYLs). Large applications may be split into multiple DYLs to reduce their working set and to overcome the 64K code segment limit. The object classes are described in a manual per DYL. For example, the OLIB Reference Manual describes variable arrays and active objects. 10 CHAPTER 2 CHARACTERS, STRINGS AND BUFFERS SSS ee ee a ee aa General string and buffer functions PLIB contains the following general string and buffer functions: p_bepy to copy a buffer p_slen returns the length of a string p_scpy, p_scpym to copy a string or multiple strings p_scat, p_scatm to concatenate a string or multiple strings p_brep, p_srep to fill a buffer or string with a repeated sequence p_bswap to swap the contents of two buffers p_bfil to fill a buffer with a repeated character p_jtob to left, right or centre align a buffer in a (normally wider) buffer with a fill character p_ere to generate the CRC number of a buffer UBYTE *p_bcpy(VOID “target, VOID *source, UINT len); Copy len bytes of data from source to target and return the address following the last byte written (ie target+len). The data is copied correctly when source and target overlap. For example: p_bcpy(str+1,str,p_slen(str)+1); *str='Al; inserts 'A‘ at the beginning of str. g length UINT p_slen(TEXT *str); Return the length of the zero terminated string str, not including the terminating zero. BSCPY a epy 2 string TEXT *p_scpy(TEXT *target, TEXT *source); Copy the zero terminated string source, producing a zero terminated string at target and return the address of the terminating zero of target. 11 PLIB REFERENCE eee The strings should not overlap. For example: p_scpy(buf,"hello"); writes "hello" to buf. TEXT *p_scpym(TEXT *target, ...); Copy and concatenate a list of zero terminated strings to target creating a zero terminated string at target. Returns the address of the zero that terminates the string at target. The first string is copied to target and the following strings are concatenated to it. The list of strings should be terminated by a NULL argument. For example: P_scpym(buf ,"The cat"," jumped", NULL); writes "The cat jumped" to buf. TEXT *p_scat(TEXT *(str, TEXT *rstr); Concatenate the zero terminated string rstr to the zero terminated string \str and return the address of the zero that terminates the new string at (str. For example: P_scpy(buf ,""hello"); p_scat(buf," fred"); writes “hello fred" to buf. TEXT *p_scatm(TEXT *(str, ...); Concatenate a list of zero terminated strings to the zero terminated string \str. Returns the address of the zero that terminates the new string at (str. The list of strings should be terminated by a NULL argument. For example: p_scpy(buf,"The"); p_scatm(buf," cat"," jumped",NULL); writes "The cat jumped" to buf. UBYTE *p_brep(VOID *buf, INT buf_len, VOID *pattern, INT pat_len); Replicate pattern as many times as necessary to exactly fill the buffer buf of length buf_len and return the address of the byte following the last byte written (ie buf+buf_Len). If buf_lenrbuf then return is greater than 0 If tbuf==rbuf then return equals 0 The result of the comparison is based on the difference of the first two unsigned bytes to disagree. The strings are equal if they have the same length and content. Where two strings have different lengths and the shorter string matches the first part of the longer string, the shorter string is considered to be less than the longer string. For example: p_bemp("abe"",3, "abcd" ,4) returns less than 0 p_bemp("abed",4,"abe",3) returns greater than 0 p_bemp("abe",3,"abe",3) returns 0. 18 2 CHARACTERS, STRINGS AND BUFFERS INT p_scmp(TEXT *lstr, TEXT *rstr); Compare two zero terminated strings by comparing corresponding characters, returning \str-rstr, that is: If lstrrstr then return is greater than 0 If \str==rstr then return equals 0 The result of the comparison is based on the difference between the first two characters to disagree. The strings are equal if they have the same length and content. For example: p_scmp("abc", "abed") returns less than 0 p_scmp("abcd", "abc") returns greater than 0 p_scmp(“abc", "abc") returns 0 INT p_bempi(TEXT *lbuf, INT Lbuf_len, TEXT *rbuf, INT rbuf_len); Performs a case independent comparison of the two buffers by effectively folding the characters in both buffers before comparing them using p_bemp. Returns as for p_bemp, described above. For example: p_bempi("abc",3,"abed",4) returns less than 0 p_bempi("abed",4,"abc",3) returns greater than 0 p_bempi("ABC",3,"abc",3) returns 0 INT p_scmpi(TEXT *lstr, TEXT *rstr); Performs a case independent comparison of the two zero terminated strings (str and rstr by effectively folding the characters in each string before comparing them using p_scmp. Returns as for p_scmp, described above. For example: p_scmpi("abc","abed't) returns less than 0 p_scmpi(“abed", "abc") returns greater than 0 p_scmpi("ABC", “abc") returns 0 EE ee ee ee] String searching The PLIB string searching functions are: p_blec, p_sloc, to search for a character p_bloci, p_sloci, p_slocr, p_slocri p_bsub, p_ssub, to search for a sequence of characters p_bsubi, p_ssubi p_bmatch, p_smatch, to search for a sequence of characters that matches a wildcard specification p_bmatchi, p_smatchi INT p_bloc(VOID *buf, INT buf_len, INT ch); Locate the byte ch in the buffer at buf of length buf_len returning the index of the first matching byte or -1 if ch is not in the buffer. 19 PLIB REFERENCE a For example: p_bloc("abcde",5,'f') returns -1 p_bloc("abcde",5,'a') returns 0 p_bloc("abcde",5,'c') returns 2 INT p_sloc(TEXT *str, INT ch); Locate the first occurrence of the character ch in the zero terminated string str, returning the index of the first matching character or -1 if ch is not in str. For example: p_sloc("abcde", 'f') returns -1 p_sloc("abede", 'a') returns 0 p_sloc("abcde", 'c') returns 2 INT p_bloci(TEXT *buf, INT buf_len, INT ch); Perform a case independent locate of ch in the buffer buf by effectively folding ch and the characters in buf before using p_bloc to locate the folded character. Returns as for p_bloc, described above. For example: p_bloci("abcde",5,'f*) returns -1 p_bloci("abcde",5,'A') returns 0 p_bloci("abcde",5,'c') returns 2 INT p_sloci(TEXT *str, INT ch); Perform a case independent locate of ch in the zero terminated string str by effectively folding ch and the characters in str before using p_sloc to locate the folded character. Returns as for p_sloc, described above. For example: p_sloci("abede", 'f') returns -1 p_sloci("abede", 'a') returns 0 p_sloci("abcde", ‘c') returns 2 INT p_slocr(TEXT *str, INT ch); Locate the last occurrence of the character ch in the zero terminated string str, returning the index of the matching character or -1 if ch is not in str. For example: p_slocr("abcabe", 'A') returns -1 p_slocr("abcabe", 'a') returns 3 INT p_slocri(TEXT *str, INT ch); Locate the last occurrence of ch in the zero terminated string str by effectively folding ch and the characters in str before using p_slocr to locate the folded character. Returns as for p_stocr, described above. For example: p_slocri("abcde",'f') returns -1 p_slocri("abcabe", 'A') returns 3 20 2 CHARACTERS, STRINGS AND BUFFERS SS INT p_bsub(VOID *buf, INT buf_len, VOID *sbuf, INT sbuf_len); Locate sub-buffer sbuf, sbuf_len in buf, buf_len returning the index of the start of sbuf in buf or -1 if sbuf does not exist in buf (which must be the case if buf_len0 *pmatch belongs after record number *pmid If two or more array elements exactly match the record at pmatch, the return value will be zero and *pmid will contain the index of any one of the matching elements. Each time p_bsrch needs to compare a record with the record at pmatch it calls: compf(n,pmatch); where n is the index of the record to be compared. This user-supplied comparison routine should return: 0 *pmatch is equal to record number n <0 *pmatch is before record number n >0 *pmatch is after record number n As with any binary search, the array must be ordered. For p_bsrch, it should be ordered with respect to compf, the user-supplied comparison routine. In most cases the set of records will be a fixed array, but any arrangement which allows the routine to identify a record by index (e.g. hashing) will be suitable. Likewise, pmatch may be any address suitable for interpretation by compf, since pmatch is not used inside p_bsrch, other than being passed to compf. This allows code using p_bsrch to be re-entrant. If the whole table is created before being searched it is, in general, quicker to build the table unordered and then sort it (see p_qsort) than to build the table in sequence by insertion. 23 PLIB REFERENCE SSS Example LOCAL_D INT array[]=(5,8,13,19,25,30,41,48,51,62, 70, 76,80,90,98); LOCAL_C intcompareCINT n, INT *pmatch) { INT f,m; f=array([nl; m=*pmatch; if (f==m) return(0); return(m>f?1:-1); > VOID find¢INT match) € INT nrec; /* number of INTs in the array */ INT result; INT mid; TEXT *pstr; nrec=sizeof(array)/sizeof (INT); result=p_bsrch(nrec, (INT (*)())intcompare, &mid, &match); if (!result) P_printf("%d matches record number %d" ,match,mid); else { pstr=result<0?"before":"after"; p_printf("%d belongs %s %d"',match,pstr, array [midi ); INT p_qsort(INT nrec, INT (*ordf)(), VOID (*excf)(), UBYTE *base) Sort a set of records into ascending order, using the quicksort algorithm. There are nrec records to sort. Each time the routine needs to compare two of these records it calls: (*ordf)(n,m, base); where n and m are the indexes of the two records to compare (starting at zero), and base is a parameter that may be used or ignored by the ordering function. The ordering function, ordf, should return: 0 record number n is equal to record number m <0 record number n is less than record number m >0 record number n is greater than record number m Each time the routine needs to exchange two records it calls: (*excf)(n,m,base); where n and m are the indexes (starting at zero) of the two records to exchange. The user-supplied exchange routine should exchange the records indexed by n and m. Again, the user-supplied exchange function is free to use or ignore base, but its use should be consistent between the ordering and exchange functions. Normally, base will represent the address of a fixed length array, but any method of storing records may be used, as long as the records can be accessed using the indices. The value of base is not used inside p_qsort, but is passed down to both the ordering and exchange functions to enable the writing of re- entrant code that uses p_qsort. The routine uses its own stack, declared locally, to avoid the need to be called recursively. This stack is 150*sizeof (INT) in length and, as such, could cause the main stack to overflow if the routine is called from too deep inside a program. The function p_qsort returns zero if successful, else a negative error. 24 3 ARRAYS AND QUEUES An error return value of E_GEN_FAIL is returned if the internal stack overflows. This is, however, unlikely as an experiment to sort 50000 elements used only 80 stack elements. If this error is returned, the routine can be called again, as it is likely that the array has been rearranged enough to permit the successful completion of a second attempt. Example LOCAL_D INT array[]={98,90,80,76,70,62,51,48,41,30,25,19,13,8,5); LOCAL_C intcompare(INT first,INT second, INT *array) € INT f,s; f=*(arrayt+first); s=*(array+second); if (s==f) return(0); return(f>s?1:-1); > LOCAL_C VOID intexchangeCINT first, INT second, INT *array) € INT r; r=*(arrayt+first); *(array+first)=*(array+second); *(arraytsecond)=r; > LOCAL_C VOID sort(VOID) £ INT n; /* number of INTs in the array */ n=sizeof(array)/sizeof(INT): if (p_qsort(n, intcompare, intexchange, &array [0] )) p_panic("Too many partitions"); > er ee ee ee er a ae Doubly linked queues This section describes functions for inserting and deleting entries from doubly linked queues. Each entry in the queue contains a P_aue structure, defined in p_que.h as: typedef struct p_que ¢€ struct p_que *next; /* pointer to next item */ struct p_que *prev; /* pointer to previous item */ > P_QUE; A special header entry consisting only of a p_aue data structure provides a single address by which the queue may be accessed. The empty queue consists only of the p_que header with both next and prev pointing to itself. A queue with n entries contains n+1 p_que structures - one for the header and one for each entry. The queue is built such that the next pointer of the last entry points to the header and the prev pointer of the header points to the last entry such that the n+1 p_que structures form a doubly linked circular queue. The following extract from p_que.h: #define P_INITQ(q) (q)->next=(q)->prev=(q) #define P_DECLAREQ(q) P_QUE q = {&q,&q) #define P_ISEMPTYQ(q) ((q)==(q)->next) defines 3 macros where: P_INITQ may be used to initialise a header that represents an empty queue P_DECLAREQ may be used to declare a header that represents an empty queue 25 PLIB REFERENCE P_ISEMPTYQ evaluates to TRUE if the queue header represents an empty queue Entries are inserted into a queue using p_enque and removed using p_deque. Neither of these functions set aside memory for entries or free memory - they merely make and break the links between entries. The following example illustrates the use of p_enque and p_deque to set up a queue of zero terminated strings that are allocated and freed from the heap (see the chapter Memory Allocation for a description of f_alloc and p free). #include #include typedef struct € P_QUE pig; TEXT name[1]; 3 QUEVE_ENTRY; LOCAL_D P_DECLAREQ(headq); GLDEF_C VOID AddNameToEnd(TEXT *name) { QUEUE_ENTRY *p; p=f_alloc(p_slen(name)+sizeof(QUEUE_ENTRY)); p_scpy(&p->name [0] ,name); p_enque(&p->piq, &headq); > GLDEF_C TEXT *FirstName(VOID) € TEXT *name; name=&( ((QUEUE_ENTRY *)headq.next)->name [0] ); if (P_ISEMPTYQ(&headq) ) name=NULL; return(name); > GLDEF_C VOID DeleteFirstName(VOID) € QUEUE_ENTRY *p; P=(QUEUE_ENTRY *)headq.next; p_deque(&p->piq); p_free(p); > Names are allocated and added to the end of the queue using AddNameToEnd. The code that processes the items in the queue uses FirstName to get the first item in the queue (which returns NULL if the queue is empty). After processing the first name, calling DeleteFirstName removes it from the queue and frees the memory used by it. VOID p_enque(P_QUE *pNew, P_QUE *pEntry); Insert entry pNew before pEntry (ie between pEntry->prev and pEntry) into the doubly linked queue that contains pEntry. If P_QUE hdq is the queue header: p_enque(pNew, &hdq) adds pNew to the end of the queue (since queues are circular the previous entry to the header is the entry at the end of the queue) p_enque(pNew,hdq.next) adds pNew to the start of the queue 26 3 ARRAYS AND QUEUES VOID p_deque(P_QUE *pEntry); Remove queue entry pEntry by linking the entries on either side of pEntry to each other, excluding pEntry from the queue. If P_QUE hdq is the queue header: p_deque(hdq.next) removes the first entry from the queue p_deque(hdq.prev) removes the last entry from the queue (since queues are circular the previous entry to the header is the entry at the end of the queue) If hdq is empty then p_deque(&hdq) will have no effect. Calling p_deque(&hdq) of a non-empty queue is not a good idea as there will then be no way to get into the queue. ES SSS SSS ey Delta queues A delta queue builds on doubly linked queues, described in the previous section, to store entries ordered OD @ LONG key. A delta queue consists of a P_que header and doubly linked entries, each containing a p_DELTA structure, defined in p_que.h as: typedef struct { P_QUE q; LONG key; /* Delta key */ > P_DELTA; where (except for the first entry in the queue) key contains the offset relative to the previous entry (the delta). The value of an entry's key is determined by accumulating the deltas of its predecessors. For the first entry, the key is just the delta. The EPOC operating system uses a delta queue to implement timers. Each entry represents a timer where the key is the relative time in system ticks to the expiry of that timer and the delta is then the number of system ticks after its predecessor. This design minimises the system effort to maintain the timers - on each tick the system only has to decrement the head of the queue. See the chapters Asynchronous Requests and Semaphores and Time, Timers and Dates for more about the time delta queue and timers. A delta list is set up much as a regular queue. A header is declared and initialised using (P_INITQ or P_DECLAREQ) giving an empty queue. Entries containing a P_DELTA structure are added to the delta queue using p_enqued and are removed using p_dequed. Neither function allocates or frees memory for entries - all they do is maintain the links and calculate the deltas. _—=—seaCisCSt—i—C_OCCOOC®COUN a queue P_DELTA *p_enqued(P_QUE *pHead, P_DELTA *pEntry, LONG key); Inserts entry pEntry into the delta queue headed by ptead according to the key key and returns the address of the first entry in the queue. The queue is scanned, accumulating the key from the deltas, until an entry is found with a key that is greater than key. The new entry pEntry is inserted (with an appropriate delta) before that entry and the delta of that entry is recalculated. P_DELTA *p_dequed(P_QUE *pHead, P_DELTA *pEntry); Remove pEntry from the delta queue headed by pHead and return the address of the first entry or NULL if p_dequed leaves the queue empty. The delta of any following entry is updated to keep the accumulated key of each remaining entry constant. 27 PLIB REFERENCE If P_QUE hdq is the queue header: p_dequed(&hdq, (P_DELTA *)hdq.next); removes the first entry from the queue. 28 CHAPTER 4 INTEGER CONVERSION AND RECTANGLE FUNCTIONS This chapter describes functions for converting integer numbers to a textual representation (eg to print a number) and vice versa (eg to input a number). The chapter ends with a section that describes a set of functions that operate on rectangle data structures. These are useful, for example, when organizing a screen display. eee es ee ee ee ny Converting integers to text PLIB contains the following functions to convert various types of integers to a textual representation: p_itob to convert an INT to a signed decimal number p_(tob to convert a LONG to a signed decimal number p_gtob to convert a UINT to an unsigned number in any radix p_gltob to convert a ULONG to an unsigned number in any radix p_atob, p_atos for general purpose conversion and formatting of multiple arguments The functions that handle "any radix" are typically used to handle a radix of 2 (binary), 8 (octal), 10 (decimal) or 16 (hexadecimal). Note that it is up to the caller to ensure that there is sufficient space in the target buffers for the output of the conversion. UINT p_itob(TEXT *buf, INT value); Write a signed decimal representation of value to buf and return the number of characters written. If value is negative, a leading '-' is written. For example: buf [p_itob(&buf [0] ,-24)]="\0'; writes "-24" to buf. INT p_Ltob(TEXT *buf, LONG value); Write a signed decimal representation of the LONG value to buf and return the number of characters written. If value is negative, a leading '-' is written. For example: buf [p_ltob(&buf [0] ,-240000L)]='\0'; 29 PLIB REFERENCE writes "-240000" to buf. | __ _ Convert a UINT to | INT p_gtob(TEXT *buf, UINT value, INT radix); Write an unsigned base radix representation of value to buf and return the number of characters written. For example: buf [p_gtob(buf ,Oxaa55,16)]='"\0'; writes "AA55" to buf. INT p_gltob(TEXT *buf, ULONG value, INT radix); Write an unsigned base radix representation of the ULONG value to buf and return the number of characters written. For example: buf fp_gi tob(buf ,0xaa5577,16)]='\0'; writes "AA5577" to buf. INT p_atob(TEXT *buf, TEXT *fstr, VOID *parg); Write formatted text to buf as controlled by the format zero terminated string fstr and the argument list parg and return the number of characters written. The format string fstr contains literal text, embedded with commands for converting the arguments at parg. The embedded commands are prefixed with the '%' character (two successive '%' characters count as one literal '%'). The literal text is simply copied to buf and the % commands convert successive arguments (which may be integers, longs or strings) at parg. An embedded command takes one of the following forms: % for output (with no padding) of the converted data type that is one of b, c, d, f, m, 0, s, u, W or X (as described below). Where appropriate, the type may be widened to a long by preceding the type letter with an 1 or an L or by providing the type letter in upper case. u for right-aligned space-filled output in width where is either a positive decimal number or a * to take the width as a UINT from the argument list. If more than characters is generated by the conversion, the output is truncated. %0 for right-aligned zero-filled output in width . % for left, right or centre aligned output in width with fill character where is either -, + or =. If is a*, the code of the fill character is taken as a UINT from the argument list. (If you want to fill with *'s, you have to supply it through the argument list). A common requirement is for space-filled output. It is therefore worth enumerating special cases, using the last of the above four forms of embedded command. Note that, in all cases, there is a space (the fill character) immediately preceding . %- for left-aligned space-filled output in width ut for right-aligned space-filled output in width %= for centre-aligned space-filled output in width The specifies the type of argument conversion to be performed, as follows: b convert the uINT to a binary text representation c convert the UINT to a single character corresponding to its code 30 4 INTEGER CONVERSION AND RECTANGLE FUNCTIONS d convert the INT to a signed decimal text representation f just output fill characters (does not use up an argument) m_ convert the UINT to a two byte binary numeric representation, with the most significant byte first (only available in EPOC version 2.17 or later) o convert the UINT to an octal text representation Ss copy the TEXT * zero terminated string to the output, excluding the terminating zero. u—_ convert the UINT to an unsigned decimal text representation w convert the UINT to a two byte binary numeric representation, with the least significant byte first (only available in EPOC version 2.17 or later) Xx convert the UINT to a hexadecimal text representation The type may be widened to a long by preceding the type letter with an 1 or an L or by providing the type letter in upper case (ignored if is s or f). Output for types m and w will then occupy four bytes. This function is normally used indirectly by the more immediately useful p atos, described below. However, p_atob is useful for constructing p_printf-like text output functions (p_printf itself is described in the chapter I/O System). For example, if file is static variable that contains the channel of an opened text file, the following function behaves like p_printf. GLDEF_C CDECL VOID PrintToFile(TEXT *fmt,UINT arg,...) € UINT Len; UBYTE buf [256]; len=p_atob(&buf [0] , fmt ,&arg); p_write(file,&buf [0], len); > If the INT variable a contains 65: PrintToFile("[%b %c %d %o Xu %x]",a,a,a,a,a,a) writes [1000001 A 65 101 65 41] PrintToFilec"[%04x]",a) writes [0041] PrintToFile("(%*x]",3,a) writes [ 41] PrintToF ile" (%+$4d.00 %s]",a,"over") writes [$$65.00 over] PrintToFile("(%0*s]",10,"fred') writes [000000fred] PrintToFile("[4=*4x]",'*',a) writes [*41*] PrintToFilec"(%-**d)",'.',10,a) Writes [65........ ] PrintToFilec"([%-A4f]",a) writes [AAAA} and makes no use of the value of a. VOID p_atos(TEXT *str, TEXT *fstr, ...); Convert multiple arguments to a zero terminated string at str under control of the format string fstr. The content of fstr is described under p_atob, above. For example, if the variable a is a UINT that contains 65: p_atos(str,"%b Xe Ad Xo “u “x",a,a,a,a,a,a) writes "1000001 A 65 101 65 41" to str p_atos(str,"%04x",a) writes "0041" to str p_atos(str,"%*x",2,a) writes "41" to str 31 PLIB REFERENCE LESS SS a ee a | Converting text to integers PLIB contains the following functions to convert text to various types of numbers: p_stoi to convert a signed decimal number string to a WORD p_stol to convert a signed decimal number string to a LONG p_stog to convert an unsigned number in any radix to a UWORD p_stogl to convert an unsigned number in any radix to a ULONG p_stoa to convert multiple fields in a string to a series of arguments INT p_stoi(TEXT **pstr, WORD *pval); Attempt to convert the signed decimal string at *pstr to a 16 bit number and, if a valid number is recognised, write the number to pval, update *pstr to point to the terminating character and return zero. Otherwise, neither *pstr nor *pval is written to and the function returns one of the following negative error numbers (defined in p_gen.h): E_GEN_OVER the number is too large (greater than 32767 or less than -32768) E_GEN_FAIL the text could not be recognised as a number Conversion continues until a non-decimal digit is found in the string, or the number overflows. For a number to be recognised, the string must contain at least one decimal digit. The string may be preceded by a '-' or a '+' (whitespace is significant and will terminate the conversion but leading zeros are ignored). To convert unsigned data up to 65535, use p_stog. For example, after: INT ret; WORD val; TEXT *ptr="-123abc"; ret=p_stoi(&ptr,&val); val contains -123, ptr points to the 'a' and ret is zero. INT p_stol(TEXT **pstr, LONG *pval); Converts the signed decimal string at *pstr to write a 32 bit number to pval. Behaves and returns as for p_stoi above except that it can handle decimal numbers in the range -2147483648 to 2147483647 inclusive. To convert unsigned data up to 4294967295, use p_stogl For example, after: INT ret; LONG val; TEXT *ptr="'-123456abc"; ret=p_stol(&ptr,&val); val contains -123456, ptr points to the 'a' and ret is zero. INT p_stog(TEXT **pstr, UWORD *pval, INT radix); Attempt to convert the unsigned number base radix (typically 2, 8, 10 or 16) at *pstr to a 16 bit number and, if a valid number is recognised, write the number to pval, update *pstr to point to the terminating character and return zero. Otherwise, neither *pstr nor *pval is written to and the function returns one of the following negative error numbers (defined in p_gen.h): E_GEN_OVER the number is too large (greater than 65535) E_GEN_FAIL the text could not be recognised as a number 32 4 INTEGER CONVERSION AND RECTANGLE FUNCTIONS Conversion continues until a character that is invalid for the radix is found in the string or the number overflows. For a number to be recognised, the string must contain at least one digit in the radix. Whitespace is significant and will terminate the conversion but leading zeros are ignored. For example, given: UWORD val TEXT *ptr="£123"; p_stog(éptr,&val,10) returms E_GEN_FAIL p_stog(&ptr,&val, 16) returns 0 and writes Oxf123 to val p_stogE =f INT p_stogl(TEXT **pstr, ULONG *pval, INT radix); Converts the unsigned decimal number base radix at *pstr to write a 32 bit number to pval. Behaves and retums as for p_stog above except that it can handle numbers up to 4294967295. For example, after: INT ret; ULONG val; TEXT *ptr="123abz2"; ret=p_stogl (&ptr, &val, 16); vat contains Oxf123ab, ptr points to the 'z' and ret is zero. INT p_stoa(TEXT **pstr, TEXT *fstr, ...) Convert multiple fields within the zero terminated string *pstr to a series of arguments ... as controlled by the format zero terminated string fstr. Returns zero if successful and the text pointer *pstr is updated to point to the terminating character of the last field converted. If an error occurred, *pstr points to the start of the field that caused the error and one of the following negative error numbers is returned: E_GEN_OVER the result is too large E_GEN_FAIL fails to recognise a number E_GEN_ARG the supplied buffer does not contain enough items The format string fstr contains literal text, embedded with commands for converting the fields in *pstr to the passed arguments. Any excess leading whitespace (as defined by p_iswhite) before a field in *pstr is automatically skipped. The embedded commands in fstr are prefixed with the '%* character (two successive '%' characters count as one literal '%"). Any non whitespace literal text causes characters in the *pstr to be scanned until a match is made or until the end of the string is encountered (any whitespace characters in fstr are discarded). If a match is not found in *pstr, the function returns. The general form of an embedded command is: #(*] [] [] :=a positive decimal number :|L :=(B|b|C|c|D|d[N|n]olo[a]q]s|sjulu[x|x) where the square brackets indicate optional fields and '|* separates choices. The mandatory parameter (optionally qualified by ) indicates the data type to be converted. If the asterisk is present, conversion is performed but the result is not be stored. There should be no corresponding value pointer in the argument list for a suppressed conversion. The parameter is a positive decimal number that specifies the maximum input field width when is s or q (in either upper or lower case) - if is other than s or q, it is ignored. Note that the 33 PLIB REFERENCE corresponding storage buffer must be large enough to hold characters plus 1 more for the terminating zero (eg %19s requires a buffer of 20 bytes). The specifies the type of argument conversion to be performed, as follows: b convert an unsigned binary number to the uworp convert a character to its code, written to the UwoRD convert a signed decimal number (optionally preceded by a '~' or a '+') to the worD write the number of characters consumed so far to the uwoRD convert an unsigned octal number to the uworp © Oo BF a O convert quote delimited text, copying the data in between the quotes (the delimiting quotes are discarded) to produce a zero terminated string at the Text * buffer. Although often used to convert text delimited by quote (") characters, the first non whitespace character encountered is taken to be the delimiter. $ convert contiguous non whitespace text to a zero terminated string at the TEXT * buffer. The string is determined from the first non whitespace character to the first whitespace character (or a zero terminator). u_—_—s convert an unsigned decimal number to the uworo X convert an unsigned hexadecimal number to the uworp When performing a numeric conversion, for a number to be recognised the string must contain at least one digit in the radix. If is present, it should be L or 1 to specify that the corresponding argument address points to either a LONG Or a ULONG (ignored if is s, f or q). A long parameter can also be indicated by specifying the conversion type in upper case. For example, after: INT ret; WORD d1,d2; TEXT *ptr="111,-33"; ret=p_stoa(&ptr,"%b, Ad", &d1, &d2); di is 7 and d2 is -33, ptr points to the terminating zero, ret is zero. After: INT ret; WORD d1,d2; TEXT buf [16] ptr="xxx da “def ghi” #44a!': ret=p_stoa(ptr,"a X%c %15q # Ad", &d1, buf ,&d2)- di contains 'a', "def ghi" is written to buf and d2 is 44, ptr points to ‘a’ and ret is zero. pS a es tir | Rectangle functions This section describes a set of functions that operate on P_RECT structs. A P_RECT struct describes a rectangle in terms of its: = top left coordinates (internal) s bottom right coordinates (external) where the units of the coordinates depend upon the application. Typically, the coordinates count pixels (for graphics displays) or monospaced character columns and rows (for character-oriented displays). For example, a character display would normally be mapped to an (x,y) coordinate system as follows: = (0,0) corresponds to the character in the top left corner =X increases to the right and counts the character columns = y increases downwards and counts character rows 34 4 INTEGER CONVERSION AND RECTANGLE FUNCTIONS -_— eee Using the above coordinates, the rectangle of as in the following character display: eee ++AAAt++ ++AAA+++ eet Pie is described by the coordinates of the top left a (internal) and the bottom right z (external) - that is, (2,1) and (5,3). Subtracting corresponding coordinates gives the correct dimensions of the rectangle - (3,2). The P_RECT struct is defined in terms of two P_point structs. The definitions, in p_graf.h, are: typedef struct € WORD x; /* Horizontal coordinate */ WORD y; /* Vertical coordinate */ } P_POINT; typedef struct € P_POINT tl; /* Top left point (internal) */ P_POINT br; /* Bottom right point (external) */ } P_RECT; An empty rectangle is a rectangle that has one or both of its sides zero or negative. The rectangle functions are as follows: p_offrec moves a rectangle by an offset p_insrec shrinks or expands a rectangle about its centre p_unirec calculates the union of two rectangles (the smallest rectangle that encloses both of them) p_intrec calculates the intersection of two rectangles p_pinrec determines whether a point is inside a rectangle p_emprec determines whether a rectangle is empty p_absrec converts any negative sides of a rectangle to their positive equivalents VOID p_offrec(P_RECT *rect, INT xoffset, INT yoffset); Displace rect by (xoffset,yoffset), without changing its size. VOID p_insrec(P_RECT *rect, INT xinset, INT yinset); Adjust the width and height of rect by xinset and yinset respectively, in such a way as to produce a rectangle concentric with the original. A negative inset makes the rectangle bigger. For example: p_insrec(&rect,2,-1); decrease the width of rect by 4 and increases the height by 2. 35 PLIB REFERENCE VOID p_unirec(P_RECT *rect1, P_RECT *rect2, P_RECT *result); Write the union of the two rectangles rect1 and rect2 (the smallest rectangle that encloses both of them) to result - as illustrated by the following diagram: Result The parameter result may point to the same address as either rect! or rect2. For example: LOCAL_D P_RECT rect1=({10,20}, (30,403); LOCAL_D P_RECT rect2={{50,50},{100, 12033; LOCAL_D P_RECT result; p_unirec(&rect1,&rect2,&result); writes ({10,20},{100,120}} to result. INT p_intrec(P_RECT *rect1, P_RECT *rect2, P_RECT *result); Write the intersection of the two rectangles rect1 and rect2 (the largest rectangle that is contained in both of them) to result. Recti — Intersection iz Rect2 If the rectangles do not intersect or if either rectangle is empty, the result is an empty rectangle. The parameter result may point to the same address as either rect1 or rect2. Returns TRUE if the rectangles intersect, FALSE if the rectangles do not intersect (or if either rectangle is empty). For example, after: LOCAL_D P_RECT rect1={{0,0),{7,20}}; LOCAL_D P_RECT rect2={{4,4},{100,120}); LOCAL_D P_RECT result; ret=p_unirec(&rect1,&rect2,&result); ret is TRUE and result contains ((4,4},{7,20)). INT p_pinrec(P_POINT “point, P_RECT *rect); Return TRUE if the point point is within the rectangle rect or FALSE if point is outside rect (or if rect is empty). INT p_emprec(P_RECT *rect); Return TRUE if the rectangle rect is empty (that is, if it has zero or negative width or height). 36 4 INTEGER CONVERSION AND RECTANGLE FUNCTIONS —_—_— OS SSFSFSSSSSSSSSSFSSSSSSSSSSSmsFeFeFsFsFFSMMeeF VOID p_absrec(P_RECT *rect, P_RECT *result); Write the absolute of the rectangle rect (where any negative sides are converted to their positive equivalents) to result. The parameters rect and result may point to the same address. For example: LOCAL_D P_RECT rect={{7,20},{4,4}}; LOCAL_D P_RECT result; p_absrec(&rect,&result); writes {(4,4},{7,20)) to result. 37 CHAPTER 5 FLOATING POINT SE SSE ES eee eee Floating point C The 8087 emulator The code that is generated from floating point C requires that the 8087 floating point coprocessor emulator be present and loaded. The 8087 emulator is implemented as an external logical device driver (LDD), loaded from sys$8087.ldd. The PLIB (or CLIB) Startup module (that is, the code which calls main) automatically loads sys$8087.ldd if the application program contains any floating point code. The startup module searches for sys$8087.idd in the following directories (in order of precedence): = as specified by the zero terminated string in the environment variable with name “Ems” (if such an environment variable exists) = in the same directory that contained the program being executed The startup module will fail with panic 80 the search for sys$8087. Idd fails. See the chapter on Error Handling for an explanation of the panic mechanism. The Ems environment variable may be set using p_setenv - as described in the chapter Memory Allocation. For example, the following installation program causes the startup module to look for SyS$8087. Idd in the a:\sys\ directory: #include GLDEF_C main(VOID) € p_setenv("EMS", "A:\\SYS\\"): > Note that environment variables names are case-sensitive - setting up (say) "ems" will not have the desired effect. Only one copy of sys$8087.ldd is loaded however many floating point processes are started. The LDD is deleted (freeing the memory) when the last floating point process exits gracefully. If the last process panics or is stopped by another process, the LDD will remain loaded - but it will be deleted bya subsequent normal exit of a floating point process. The LDD has the device name Ems, so you can find out if the device is loaded using: LOCAL_C INT Is8087Loaded(VOID) € TEXT bb[E_MAX_NAME+2] ; return(p_devfnd(0, "EM$",E_LDD,&bb[0)] )>0); > which returns true if the LDD is loaded. If required, a clean-up program can delete the loaded LDD with: p_devdel("EMS",E_LDD); which will only successfully delete the LDD if there are no floating point processes using it. The functions p_devfnd and p_devdel are described along with LDDs in the 1/O System chapter. eee 39 PLIB REFERENCE If sys$8087. Idd is not present in the ROM, it must be loaded into RAM at a cost of approximately 8K bytes (but this does not detract from the code segment limit of the application). If the LDD is present in the ROM, it still has to be loaded but at a reduced cost of RAM. You can find out if sys$8087.idd is present in the ROM using: LOCAL_C INT Is8087ImROM(VOID) € P_INFO info; return(p_finfo("ROM: :SYS$80B87.LDD",&info)>=0); > which returns TRUE if sys$8087.ldd is present in the ROM. Avoiding the 8087 emulator It is possible to perform floating point operations without using floating point C and without loading the emulator. For example, a program that contains the following function (to evaluate sin(x)/x): LOCAL_C INT Sinc(DOUBLE *pret,DOUBLE *parg) { if (*parg==0.0) € *pret=1.0; return(0); > if (Cret=p_sin(pret,parg))<0) return(ret); *pret=*pret/*parg; return(0); > will load the emulator because the expressions *parg==0.0, *pret=1.0 and *pret=*pret/*parg all generate calls to the emulator. However, if the function is implemented without using floating point C as in: LOCAL_C INT Sinc(DOUBLE *pret ,DOUBLE *parg) { WORD x; DOUBLE zero; x=0; p_itof(&zero,&x); if (!p_femp(&zero, parg)) € x=1; p_itof(pret,&x); return(0); > if ((ret=p_sin(pret,parg))<0) return(ret); p_fdiv(pret,parg); return(0); > then sys$8087.ldd is not loaded. Furthermore, the code that is generated is smaller and runs faster. The benefits of avoiding the emulator are: = you do not need to worry about the presence of sys$8087. Idd = the program is smaller and runs faster The benefits of using the emulator are: = you can use floating point C and use 32-bit float variables (as well as 64-bit doubles) = the emulator works with 80-bit numbers internally and therefore gives more precise results 40 5 FLOATING POINT —— SS SSS Macros The following macros, defined in p_math.h #define ABS(x) (¢€x)(b) ? (a) : (b)) #define MINCa,b) ((a)<(b) ? (a) : (b)) can be used with integer or floating point expressions (although the code that is generated might be quite lengthy). Using any of these macros with a floating point expression will cause the 8087 emulator to be loaded. The remaining functions in this chapter, with the exception of p_rand and p_randl, are implemented independently of the emulator. Using them will not cause the emulator to be loaded. If the emulator is loaded, they may still be used. REESE SS eS ee a ee ee eT Ee ee eT] Converting doubles to and from text © string INT p_dtob(TEXT *pbuf, DOUBLE *pval, P_DTOB *pformat); Convert the double floating point number *pval to text at pbuf using the pformat format specification. The P_pTos structure is defined in p_math.h as: typedef structure € UBYTE type; /* conversion type */ UBYTE width; /* width of representation in characters */ UBYTE ndec; /* number of decimal places */ TEXT point; /* decimal point character */ TEXT triad; /* triad separator character */ UBYTE trilen; /* threshold for triad character use */ > P_DTOB; where: type specifies the numeric format as being fixed, scientific or general. (Integer format is obtained as a special case of fixed point format where the number of decimal places ndec is zero.) width specifies the maximum number of characters allowed to represent the number (it must be in the range 1 to 255 inclusive). You must reserve width bytes at pbuf. If the formatted string would be wider than width then E_GEN_FAIL is returned. (If you want the output aligned and filled, post-process pbuf with p_jtob). ndec specifies the number of digits following the decimal point when type is P_DTOB_FIXED Or P_DTOB_EXPONENT where ndec must be in the range zero to P_FLT_PREC (15) inclusive. point specifies the character used for the decimal point. It would normally be either ","(dot) or ','(comma). triad specifies the triad separator character used to delimit groups of 3 digits in the integer part of a fixed point number. It would normally be one of '.'(dot) or ",'(comma) or ' (space). trilen is either zero to disable triad insertion, or a threshold number of digits above which triad insertion takes place. In practice, trilen is set to 1 for normal conventions and 4 to conform to the French convention. In all cases, negative numbers are represented by the insertion of a leading '-' sign (positive numbers do not have a leading '+' sign). To obtain a bracketed representation of negative numbers you must post- process the output string. 41 PLIB REFERENCE There can never be more than P_FLT_PREC(15) significant digits. Where there are less than P_FLT_PREC significant digits, the number is rounded to the number of significant digits displayed. The detailed formatting details as a function of type is as follows: P_DTOB_FIXED the number is represented with ndec decimal places, where ndec may be zero to represent an integer (in which case no decimal point character is displayed). If the ASCII form exceeds width (usually due to the number being large and having too many digits before the decimal point), E_GEN_FAIL is returned. A zero is displayed in the form "0.000" where there are ndec zeros following the decimal point or as just "0" if ndec is zero. P_DTOB_EXPONENT the number is represented in scientific notation with one non-zero digit before the decimal point and ndec digits beyond the decimal point followed by 'E', a sign ('+' or '-') and the exponent as two digits (with leading zero if necessary). If ndec is zero, the number is rounded to one digit of precision and no decimal point is displayed. A zero is displayed in the form "0.000E+-00" where there are ndec zeros following the decimal point or as "OE+00" if ndec is zero. Triad separation is not available and triad separation parameters are ignored. P_DTOB_GENERAL converts either as fixed format (with no triad separator) or scientific format, making best use of width. Here, "making best use" is defined as showing the greater number of significant digits and preferring fixed format when the number of significant digits shown is the same. The number of decimal places displayed depends on width (ndec is ignored). A zero is displayed as just "0". Triad separation is not available and triad separation parameters are ignored. P_DTOB_GEN_LIM as for P_DTOB_GENERAL, except that output is limited to not exceed 12 significant digits. Returns the number of characters written to pbuf if successful, or one of the following negative error numbers: E_GEN_UNDER the number is too smal] to represent (less than approximately 1E-99). If you would prefer not to fail in this case, you can always write your own zero or re- call p_dtob with a zero double. E_GEN_OVER the number is too large to represent (greater than approximately 1E99). E_GEN_FAIL the representation exceeds width characters. E_GEN_ARG either the double is illegal, or type is not one of P_DTOB_FIXED, P_DTOB_EXPONENT OY P_DTOB_GENERAL. INT p_stod(TEXT **pstr, DOUBLE *pval, INT point); Scan the zero terminated string *pstr for a floating point number where point is the code of the decimal point character (normally either '.' or ',") and write the value as a double float to *pval. For a number to be recognised, the string must contain at least one decimal digit. If p_stod is successful, it updates *pstr to point to the terminating character and returns zero. If it fails, *pstr is not changed and it returns one of the following negative error numbers: £_GEN_UNDER the number is too small (less than approximately 1E-99). The value zero is written to *pval. E_GEN_OVER the number is too large (greater than approximately 1E99). &_GEN_FAIL fails to recognise a number. The supplied string at *pstr should take the form: [+|-3. LE |e} [+|-] where: = the leading '+' sign may be omitted for positive numbers # and are optional but at least one should be present 42 5 FLOATING POINT = leading zeros in are legal but have no effect = trailing zeros in are legal but have no effect = there is no reasonable limit to the number of significant digits, but digits that are beyond the precision of the floating point representation will not be reflected in the mantissa of the number which is produced = the exponent field (which starts with 'E' or 'e') is optional = the leading '+' sign in the exponent field may be omitted for positive exponents = the resulting number should be in the range approximately 1E-99 to approximately 1E+99 Example LOCAL_D TEXT buf {]="-134.43735abe"; FAST INT ret; DOUBLE val; TEXT *ptr; ptr=&buf [0] ; After ret=p_stod(&ptr,&val,'."); vat will be -134.43735, ptr will be pointing to 'a' and ret will be zero. p_9E VOID p_getctd(E_CONFIG *pcfg); Write a copy of the system E_CONFIG struct to pcfg. The E_CONFIG struct is defined in p_config.h as: typedef struct € UWORD countryCode; WORD gmtOffset; UBYTE dateType; UBYTE timeType; UBYTE currencySymbol Position; UBYTE currencySpaceRequired; UBYTE currencyDecimalPlaces; UBYTE currencyNegativelnBrackets; UBYTE currencyTriadsAl lowed: UBYTE thousandsSeparator; UBYTE decimalSeparator; UBYTE dateSeparator; UBYTE timeSeparator; UBYTE currencySymbol [9] ; UBYTE startOfWeek; UBYTE summerTime; UBYTE clockType; UBYTE dayAbbreviation; UBYTE monthAbbreviation; UBYTE workDays; UBYTE units; UBYTE spare [9}; > E_CONFIG; In the context of this chapter we are interested in: currencySymbol a zero terminated string containing the currency symbol currencySymbolPosition which should contain either E_CURRENCY_BEFORE or E_CURRENCY_AFTER currencySpaceRequired | which should contain either E_NOSPACE_BETWEEN Or E_SPACE_BETWEEN currencyDecimalPlaces the number of decimal places for displaying currency figures 43 PLIB REFERENCE currency- TRUE if a negative currency should be displayed in brackets rather than with a NegativelnBrackets minus sign currencyTriadsAl lowed zero to disable triad separator insertion, or a threshold number of digits above which triad separator insertion takes place. In practice a value of 1 is used for normal conventions and 4 for the French convention. Note that, despite the name of this element, triad separators are not restricted to currency fields; they may be inserted in any numeric field. thousandsSeparator the character code of the triad (thousands) separator decimalSeparator the character code of the decimal separator (normally either ',' or '.') units either E IMPERIAL or E_ METRIC to indicate a preference for imperial or metric units (for example, to show page dimensions in inches or centimetres) SS SS SS ns ieee Long integer functions ULONG p_randl(ULONG *pseed); Return the next pseudo random number and updates *pseed. Used to generate a sequence of pseudo random numbers from an initial value of *pseed. Any given seed will always produce the same sequence of random numbers. The numbers generated may be any value between 0 and 4294967295 (oxffffffff) or, if considered as a signed result, between -2147483648 (0x80000000) and +2147483647 +(ox7ffffftf). For example, to print reproducibly 100 random longs: ULONG seed; UINT i; seed=01; for (i=0;1<100; i++) p_printf("4ld",p randl (&seed)); To generate a different set of numbers each time, seed the number with the system time, as in: seed=p_date(); i SS ES Se ee ee ae el Scientific functions For all floating point functions that transform a single input parameter it is permissible to use the same address for both parg and pret. All trigonometric functions assume angles are measured in radians. ‘cranes = Sine INT p_sin(DOUBLE “pret, DOUBLE “parg); Write the sine of *parg to “pret. Returns zero if successful or £_GEN_ARG if *parg was an invalid double. pcos Cosine INT p_cos(DOUBLE “pret, DOUBLE *parg); Write the cosine of *parg to *pret. Returns zero if successful or E_GEN_ARG if *parg was an invalid double. 4a 53 FLOATING POINT INT p_tan(DOUBLE *pret, DOUBLE *parg); Write the tangent of a *parg to *pret. Returns zero if successful or £_GEN_ARG if *parg was an invalid double or if it was greater than 149078413. INT p_asin(DOUBLE “pret, DOUBLE *parg); Write the angle whose sine is *parg to *pret. Returns zero if successful or E_GEN_ARG if *parg was an invalid double or ABS(*parg)>1. Sa pa INT p_acos(DOUBLE “pret, DOUBLE *parg); Write the angle whose cosine is *parg to *pret. Returns zero if successful or &_GEN_ARG if *parg was an invalid double or a8s(*parg)>1. INT p_atan(DOUBLE *pret, DOUBLE *parg); Write the angle whose tangent is *parg to *pret. Retums zero if successful or E_GEN_ARG if *parg was an invalid double. INT p_ln(DOUBLE *pret, DOUBLE *parg); Write the natural (base e) logarithm of *parg to *pret. Returns zero if successful or £_GEN_ARG if *parg was less than or equal to zero or if *parg was an invalid double. INT p_exp(DOUBLE *pret, DOUBLE *parg); Write the value of the arithmetic constant e (2.71828...) raised to the power of *parg to *pret. Returns zero if successful or one of the following negative error numbers: E_GEN_ARG *parg is not a valid double. E_GEN_UNDER underflow has occurred (the magnitude of the result is less than 1E-307), zero is written to “pret. E_GEN_OVER overflow has occurred (the magnitude of the result is greater than 1E307). INT p_log(DOUBLE “pret, DOUBLE *parg); Write the base 10 logarithm of *parg to “pret. Returns zero if successful or &_GEN_ARG if *parg was less than or equal to zero or if *parg was an invalid double. 45 PLIB REFERENCE INT p_sqrt(DOUBLE *pret, DOUBLE *parg ); Write the square root of *parg to *pret. Retums zero if successful or E_GEN_ARG if *parg was negative or an invalid double. INT p (DOUBLE “pret, DOUBLE *parg1, DOUBLE *parg2); 1_POwW Write *parg1 raised to the power of *parg2 to *pret. Retums zero if successful or one of the following negative error numbers: E_GEN_ARG the arguments are invalid (if *pargi <0, *parg2 must be integral), or at least one argument is not a valid double. E_GEN_UNDER underflow has occurred (the magnitude of the result is less than 1E-307), zero is written to *pret. E_GEN_OVER overflow has occurred (the magnitude of the result is greater than 1E307). DOUBLE p_rand(ULONG *pseed); Return a DOUBLE random number in the range zero (inclusive) to one (exclusive). As in the case of p_randl, the value pointed to by pseed is used to seed the random number generation and is updated for the next call of p_rand. This function requires the 8087 floating point emulator. VOID p_frand(DOUBLE “pret, ULONG *pseed); Write a random number in the range zero (inclusive) to one (exclusive) to *pret. As in the case of p_randl, the value pointed to by pseed is used to seed the random number generation and is updated for the next call of p_frand. Fe OT ee Floating point arithmetic without the 8087 emulator This section describes the PLIB functions that would normally be used to perform floating point arithmetic without having to load the 8087 floating point emulator sys$8087.ldd. For all floating point functions with one parameter it is permissible to use the same address for both parg and pret. INT p_fld(DOUBLE *pret, DOUBLE “parg); Write the value of *parg to *pret (the 'Id' stands for load). Returns zero if successful or E_GEN_ARG if *parg was an invalid double. INT p_fadd(DOUBLE “pret, DOUBLE *parg); Write the sum of *parg and *pret to *pret. Returns zero if successful or one of the following negative error numbers: E_GEN_ARG *pret OF *parg was not a valid double. Ee ee ee ee 46 5 FLOATING POINT E_GEN_OVER overflow has occurred (the magnitude of the result is greater than 1E307). INT p_fsub(DOUBLE *pret, DOUBLE *parg); Write *pret minus *parg to *pret. Returns zero if successful or one of the following negative error numbers: E_GEN_ARG *pret OF *parg was not a valid double. E_GEN_OVER overflow has occurred (the magnitude of the result is greater than 1E307). INT p_fmul (DOUBLE “pret, DOUBLE *parg); Write the product of *parg and *pret to *pret. Returns zero if successful or one of the following negative error numbers: E_GEN_ARG *pret OF *parg was not a valid double. E_GEN_UNDER underflow has occurred (the magnitude of the result is less than 1E-307), zero is written to *pret. E_GEN_OVER overflow has occurred (the magnitude of the result is greater than 1E307). p_Tean de INT p_fdiv(DOUBLE *pret, DOUBLE *parg); Write *pret divided by *parg to *pret. Returns zero if successful or one of the following negative error numbers: E_GEN_ARG *pret OF *parg was not a valid double. E_GEN_UNDER underflow has occurred (the magnitude of the result is less than 1E-307), zero is written to “pret. E_GEN_OVER overflow has occurred (the magnitude of the result is greater than 1E307). INT p_fcmp(DOUBLE “parg1, DOUBLE *parg2); Compare *parg! to *parg2, returning: 1 if *parg1 > *parg2 0 if *parg1 == *parg2 -1 if *parg1 < *parg2 The function only returns zero if *pargi and *parg2 are strictly equal. In some cases it may be more appropriate to test for equality by evaluating the difference using p_fsub and then comparing the difference with a suitably small number (such as, for example, 1E-10). The return value is undefined if either *parg1 or *parg2 is not a valid double. Note that it is bad practice to compare floating point numbers for equality, since rounding errors may make the result meaningless. INT p_fneg(DOUBLE *parg); Negate *parg. Returns Zero if successful, or £_GEN_ARG if *parg was an invalid double. a ee SE eS 47 INT p_mod(DOUBLE *pret, DOUBLE “pargi, DOUBLE *parg2); Write the remainder of *parg1 divided by *parg2 to *pret. Returns zero if successful, or €_GEN_ARG if either *parg1 or *parg2 was an invalid double. INT p_int(DOUBLE *pret, DOUBLE *parg); Write the integer part of *parg to *pret. Negative numbers are rounded towards zero. Returns zero if successful or E_GEN_ARG if *parg was an invalid double. INT p_inti(WORD *pret, DOUBLE *parg); Write the integer part of *parg to *pret if *parg is in the range -32768 to +32767 inclusive. Negative numbers are rounded towards zero. Returns zero if successful or one of the following negative error numbers: E_GEN_ARG *parg is not a valid double E_GEN_OVER *parg is outside the range -32768 to +32767 INT p_intl(LONG *pret, DOUBLE *parg); Write the integer part of *parg to *pret, if it is in the range -2147483648 (0x80000000) to +2147483647 (Ox7f fff fff) inclusive. Negative numbers are rounded towards zero. Returns zero if successful, or one of the following negative error numbers: E_GEN_ARG *parg is not a valid double E_GEN_OVER *parg is outside the range -2147483648 to +2147483647 VOID p_itof(DOUBLE “pret, WORD *parg); Convert *parg to a double and write it to *pret. VOID p_longtof(DOUBLE *pret, LONG *parg); Convert *parg to a double and write it to *pret. 48 CHAPTER 6 ERROR HANDLING SSE en ee eee ee ee ee ee Process termination A process can terminate itself or it can be terminated by another process. By whatever means a process terminates, one or more other processes may be interested in being notified that a process has terminated. Terminating this process. A process terminates itself by calling: p_exit for normal graceful termination P_panic for abnormal termination, normally as a result of defective code C programs that fall off the end of main effectively call p_exit with the return from main. That is: GLDEF_C INT main(VOID) € p_printf¢"Hello world"); p_sleep(50L); /* wait 5 seconds */ return(0); > is equivalent to: GLDEF_C VOID main¢(VOID) € P_printfC"Hello world"); p_sleep(50l); /* wait 5 seconds */ p_exit(0); > It is poor practice to fall off the end of a voip main since this is equivalent to calling p_exit with a random number (whatever happens to be in the ax register at the time). If there is a system component (such as the shell) reporting process terminations to the user, it will give a misleading report. Terminating another process A process can terminate another process by calling: p_pterminate or to terminate another process (typically in response to a user request) p_pkill P_ppanic to panic another process (normally following unreasonable behaviour from the process being terminated) To terminate a process, it is recommended that p_pterminate is used in preference to p_pkill as the former allows the process being terminated to run any cleanup code before exiting gracefully. Applications wishing to run cleanup code call p_onterminate to elect to be sent an inter-process message in response to the p_pterminate request. 49 PLIB REFERENCE Finding out when other processes terminate A process can request to be notified of the termination of another process by calling: p_logona to be signalled when the specified process terminates p_logon to receive an inter-process message when the specified process terminates (convenient for server processes to keep track of their clients) p_watchal to receive an inter-process message when any process terminates (normally used by the Shell to monitor the termination of all processes) See the chapter Asynchronous Requests and Semaphores for the meaning of the term "signalled". See the chapter Processes and Inter-Process Messaging for more about inter-process messaging and servers. The process termination word Regardless of which one of p_logona, p_logon or p_watchall is used, the system delivers a single 16-bit process termination word giving information on how the process terminated. The most significant byte of the process termination word is one of the following (defined in epoc.h) E_NORMAL_EXIT the process terminated itself by calling p_exit or it was terminated by another process calling p_pterminate or p_pkill. The least significant byte of the process termination word contains the nReason parameter to p_exit, p_pterminate Or p_pkill. By convention, an nReason of zero indicates a normal error-free graceful termination. E_PANIC_EXIT the process terminated itself by calling p_panic or it was terminated by another process calling p_ppanic. The least significant byte contains the nPanic ("panic number") parameter to p_panic or p_ppanic. E_TASK_PANIC_EXIT the process terminated because it owned a task that terminated with E_PANIC_EX1T (tasks are lightweight processes and are described in the chapter Processes and Inter-Process Messaging). Panics When the system detects a condition that it believes could only arise from a bug in a the application program, the system terminates the process with a "panic number" in the range 0 to 255 inclusive (where the system is said to "panic the process”). A panic is a fatal exception that causes the process to terminate immediately. There is no way for applications to avoid being terminated when a panic has been started (cf p_leave, described later in this chapter). Panicking a process is more economical in the use of system code and application code than returning an error, since the latter relies on application code to properly process the error return. As well as protecting the system from defective applications, the panic system enforces a greater discipline on application code by terminating a process as soon as the condition is detected. This does not mean that the system always prefers to use panics rather than error returns. Error returns are still used where appropriate; the system certainly never panics a process on a condition that could arise from user action. For example, the p_alloc function to allocate a memory cell returns NULL if there is insufficient free system memory to satisfy the request, but calls p_panic if it detects that the heap has been corrupted. In the majority of cases, such as in the above example, it is quite clear where a panic or an error return is appropriate, but occasionally it is not so clear. From the point of view of an application programmer, however, it is clear that panic conditions should be avoided. In consequence the function descriptions in this manual include any conditions that result in the caller being panicked. If the panic condition is detected within the code of a system function, the function calls p_panic as in the example above. If the condition is detected in a system process (for example, the supervisor or the file server) the application process is terminated by the system process calling p_ppanic. Application programmers can include their own calls to p_panic to catch conditions indicating a bug in their program which might otherwise go unnoticed (until the software is used by a customer!). A common example would be to put a call to p_panic on the default case of a switch statement, to catch an invalid parameter. System panic numbers The following lists the panic numbersthat are used by the operating system: 00 Used by test code when a test fails 01 Invalid function number (semaphore manager) 50 6 ERROR HANDLING eee 02 Invalid semaphore handle 03 Semaphore not allocated 04 Initial semaphore count is negative OS Signal count is negative 06 Invalid function number for process manager 07 Invalid process ID 08 Task tried to create a task 09 Invalid function number for time manager 10 Invalid function number for segment manager 11 Segment size was negative 12 Type was not one of E_SEGMENT_LOW, E_SEGMENT_HIGH, E_SEGMENT DEVICE or E_SEGMENT_LOCKED 13 Invalid segment handle 14 Segment copy is out of range 15 Invalid function number for heap manager 16 Heap not initialised 17 A heap cell is being reduced by more than its size 18 Attempt to set heap granularity greater than &_MAX_GROWBY 19 A heap cell address is outside the boundaries of the heap (the heap has probably been corrupted - try calling p_al\chk to catch the corruption sooner) 20 Invalid function number for inter-process message manager 21 Inter-process messaging has already been initialised (ie p_minit has been called twice) 22 Inter-process messaging has not been initialised (ie p_minit has not been called) 23 Cannot initialise with zero messages in the queue 24 Invalid function number for I/O manager 25 Invalid I/O channel (possibly because you did not test that the previous p_open succeeded or you have closed the channel or you overwrote the variable containing the channel) 26 Device requested panic 27 Invalid wait handler handle (possibly nothing to do with wait handlers and just indicative of a low address overwrite of the 4 bytes at address 2, as a result of an uninitialised pointer) 28 Key and pointing device already hooked 29 Key and pointing device requesting process is not a task 30 Invalid function number for device manager 31 Invalid device handle 32 Invalid function number for file manager 33 Process already connected to file server 34 Reserved for future use 35 Invalid function number for library manager 36 Invalid library handle 37 Invalid function number for library 38 Invalid LIB file channel 39 Invalid DYL index number = eS a eh ee ee ee 51 PLIB REFERENCE 40 4] Invalid message to file server Process has not connected to file server Invalid function number for conversion manager Invalid function number for general manager Attempt to unhook from notify when not already hooked Invalid revector address Invalid function number for conversion manager Leave called before a call to enter (possibly because you were unaware that the function you were calling could call p_leave) No method available to handle message (OOP) Invalid reclass attempted (OOP) Unknown category in LibHandle (OOP) Unknown class in LibCreate (OOP) Supersend called from outside a method (OOP) Attempt to get a handle before being linked (OOP) Missing external categories in LibLink (OOP) Object does not point to a valid class (OOP) Invalid link layer completion code Invalid function number for window server Invalid function number for hardware manager Unexpected interrupt Attempted to write outside of process data segment (possibly because of an uninitialised pointer or a corrupted data structure) Interrupts have been disabled for too long Reserved for future use Divide by zero interrupt Overflow interrupt Invalid function number for Dbf manager Invalid DBF I/O channel Invalid parameter for DBF function Address zero overwrite (possibly because of an uninitialised pointer) The operating system detected less than 0x100 bytes of remaining stack (this amount is reserved for hardware interrupts to run). You probably have declared large data structures as automatics. Consider making them static variables or allocate them from the heap. Environment name size > EnvMaxNameSize Single step interrupt (INT 1) Break point interrupt (INT 3) A request was made while an asynchronous request of the same type and on the same channel was already pending Invalid function number for serial I/O manager Cail to an ASIC1 function on an ASIC9 machine Attempt to find a DYL not in a visible bank 52 6 ERROR HANDLING en 77 Floating point emulator exception 78 Semaphore count exceeds 0x7fff 80 Library fatal error, preceded by a notification of the specific error 255 The function p_al\chk detected a corrupted heap Some of the panics, especially those described by "Invalid function number for ...", are unlikely to indicate a specific bug; it is almost impossible to create code that would produce such a panic by a coding error. This type of panic could, however, easily arise from trashing a return address on the stack, with the instruction pointer wandering into arbitrary code. In the above list, those panics that are likely to indicate a specific coding problem are described more fully. If you get a panic 79, or a panic in the range 81 to 254, it may be due to some other system component. See the Window Server manual for panics in the range 81-110 and, when using object oriented programming, see the OLIB manual for panics in the range 130-158. VOID p_exit(INT nReason); Terminate this process with reason nReason in the range -127 to 128 without returning to the caller. Applications that exit normally should terminate with nReason equal to zero. Applications that fail during their initialisation may wish to pass on the error number (in the range -127 to -1 inclusive) that caused the initialisation to fail. Programs that are run as subprocesses can use a positive nReason to pass back an exit status to their parent process. VOID p_panic(INT nPanic); Terminate this process with panic number nPanic in the range 0 to 255 without returning to the caller. To avoid system panic numbers, application specific panic numbers should start from 254 downwards. INT p_pkill(HANDLE pld, INT nReason); Terminate process pid for reason nReason (in the range -127 to 128 inclusive) without giving the process being terminated an opportunity to run any cleanup code before exiting. It is recommended that p_pterminate (described below) is used in preference to p_pkil\ as it gives the process being terminated a chance to run any cleanup code. Returns zero if successful or one of the following negative error numbers: E_FILE_NXIST the process does not exist E_GEN_FAIL pid is the null process, the supervisor or the file server INT p_pterminate(HANDLE pid, INT nReason); Terminate process pid for reason nReason in the range -127 to 128 inclusive. If process pid has called p_onterminate (described next) the call to p ) pterminate will simply send the specified message number to process pid. Otherwise the effect is the same as with p pkill. It is recommended that p_pterminate is used in preference to p_pkill. Returns zero if successful or one of the following negative error numbers: E_FILE_NXIST the process does not exist E_GEN_FAIL pid is the null process, the supervisor or the file server 53 PLIB REFERENCE lessage VOID p_onterminate(INT nMessage); Elect to receive (non-zero) message number nMessage, rather than being summarily terminated, when another process requests termination by calling p pterminate. On receipt of the termination message a process should execute its cleanup code and must then terminate itself, normally by calling p_exit. The process calling p_onterminate must guarantee to respond promptly when the termination message is sent to it. It should not perform any lengthy, uninterrupted, processing. A process which calls p_onterminate and then (presumably in error) enters an infinite loop will not be terminated by a call to p_terminate. It is recommended that a process should not call p_onterminate unless there is an explicit reason for it to do so. The election may be cancelled by calling p_onterminate(0). Messaging must have been initialised with p_minit prior to the call to p_onterminate, otherwise p ) panic will be called. INT p_ppanic(HANDLE pld, INT mPanic); Terminate process pid with panic number nPanic in the range 0 to 255 (used for example by server processes that receive a garbage message from a client process to panic the client). Returns zero if successful or one of the following negative error numbers: E_FILE_NXIST the process does not exist E_GEN_FAIL pid is the null process, the supervisor or the file server INT p_logonaCHANDLE pld, WORD *pStatus); Make an asynchronous request to be notified of the termination of process pid by having this process I/O semaphore signalled when pid terminates. See the chapter Asynchronous Requests and Semaphores for a description of asynchronous requests and the I/O semaphore. Returns zero if successful or &_FILE_NXIST if pId does not exist. After a successful request and before pid has terminated, *pstatus contains E_FILE_PENDING. When pid has terminated, *pstatus contains the (non-negative) process termination word (as described under the heading The Process Termination Word at the beginning of this chapter). The caller can cancel the asynchronous request by calling p_logoffa. A server process that responds to inter-process messages from client processes will probably find it more convenient to use p_logon, described below. For example, the following function: GLDEF_C INT RunSubProcessWait(TEXT *name, BYTE *pReason) { HANDLE pid; WORD stat; if (Cpid=p_exece(name,NULL,0))<0) return(pid); p_logona(pid,&stat); p_presume(pid); p_waitstat(&stat); *pReason=(BYTE)stat; return(stat>>8); > loads the executable name, resumes the process, waits for it to terminate, writes the p_exit to “pReason and Teturns E_NORMAL_EXIT. It returns a negative error number if it fails to load name and €_PANIC_EXIT if the sub-process panics. gr 54 6 ERROR HANDLING _ Cancel notificatio INT p_logoffa(HANDLE pld); Cancel a previously requested notification of the termination of process pid (as passed to the p_logona being cancelled). Returns zero if successful or &_FILE_NxIST if no request is pending. If the cancel gets through before pid terminates, *pstatus will contain —_FILE_CANCEL. In either case, the process I/O semaphore is signalled and p_logoffa would normally be followed by a call to p_waitstat(pStatus). message on process termination INT p_logon(HANDLE pid, INT mType); Request to be notified of the termination of process ptd by receiving an inter-process message of type mType when pid terminates. See the chapter Processes and Inter-Process Messaging for a description of inter-process messaging. Returns zero if successful or E_FILE_NXIST if pfd does not exist. When process pid terminates the Supervisor process sends the caller a message of type mlype and whose first word in the message buffer is the pid of the terminating process and whose second word is the process termination word giving information on how that process terminated (as described under the heading The Process Termination Word at the beginning of this chapter). The caller can cancel the request by calling p_logoff or p_logoffx. The process must have messages initialised by calling p_minit - the function calls p_panic if messages have not been initialised. The p_togon, p_logoff and p_logoffx services were designed for server processes that respond to inter- process messages from client processes to clean up client specific resources should a client process terminate without disconnecting from the server. Processes that are not server process and that do not normally respond to inter-process messages will probably find it more convenient to use p_logona, described above. INT p_logoff(CHANDLE pid, INT mType); Cancel a previous p_logon request to be sent an inter-process message when process pid terminates. The value of pId should be as passed to p_logon, and mtype is ignored. This form is suitable for applications that do not make no more than one p_logon request to any particular process. Applications that make two or more p_logon requests with the same value of pid (but, presumably, different values of mType) should cancel them by means of p_logoffx, described below. Returns zero if successful or £_FILE_NXIST if pid does not exist. The function calls p panic if messages have not been initialised. INT p_logoffx(HANDLE pid, INT mType); This function is only available in EPOC version 3.18 or later. Cancel a previous p_logon request to be sent an inter-process message of type mlype when pld terminates (pid and mType should be the values that were passed to the p_logon request that is being cancelled). This function must be used in preference to p_logoff in cases where two or more p_logon requests are made with the same value of pid. Returns zero if successful or E_FILE_NXIST if pId does not exist. The function calls p_panic if messages have not been initialised. 55 PLIB REFERENCE INT p_watchall(UINT mType); Request to be notified of the termination of any process by receiving an inter-process message of type mlype when a process terminates. See the chapter Processes and Inter-Process Messaging for a description of inter-process messaging. Only one process at a time can request this service and it is usually reserved for use by a system process (normally the Shell process) to monitor the termination of all processes. The function returns zero if successful or &_GEN_FAIL if a watch is already active. The format of the received message is as for p_logon, described above. Calling p_watchall with an mType of zero cancels the request. The process must have messages initialised by calling p_minit - the function calls p_panic if messages have not been initialised. FOS Rt a oS a a oe il a ae | Error returns System functions that can fail must somehow indicate success or failure and, where appropriate, elaborate on the failure. Where there is no elaboration of the error: = Functions that return an address typically return a NULL (zero) address to indicate failure (this is often used when a function can fail to allocate memory). = Otherwise functions return zero or positive to indicate success and -1 to indicate failure (the constant E_GEN_FAIL is defined as -1 although you can just test for the sign of the returned value). Where the error is elaborated, the system function returns a system error number in the range -1 to -128, allocated as follows: -1 to -31 Reserved for general errors of the form £_GEN_xxx; (defined in p_gen.h) -32 to -63 Reserved for I/O device errors of the form E_FILE_xxx (defined in p_file.h) -64 to -95 Reserved for future use -96 to -128 Reserved for OPL run-time errors The system errors include both generic error numbers (eg E_GEN_NOMEMORY) and specific error numbers (eg E_FILE_PARITY indicating a parity error in a byte received via a serial port). Application programmers may wish to use the generic error numbers in their own code - see the contents of p_gen.h and p ) file.h. The system stores a language dependent description for each of the system error numbers that may be retrieved by calling p errs. VOID p_errs(TEXT *str, INT errno); Convert a system error number to a language dependent zero terminated text string in str. There should be at least E_MAX_ERROR_TEXT_SIZE (64) bytes at address str. If the error is an unknown error the string "Unknown error [xx]" (or a suitable translation if not an English ROM) is returned, where xx is the value of errno. SSS SS ee ee ee ee er ear Notifier services The notifier services are used to inform the user of a condition - particularly an error condition - and to present the user with up to three options on how to proceed. There are two variants of the notify service: 56 6 ERROR HANDLING SSeS p_notifyerr which presents the user with a system error message (converted to text from the error number using p_errs) and a contextual message p_notify which presents the user with two messages These services can be used by any application and are particularly useful for processes that do not otherwise have a user interface (for example, the file server). Investigative calls to p_notify may be temporarily inserted into code when debugging programs. At the level of the services described in this manual and except for the simple console functions such as p_printf, the operating system does not define any user interface components and the notifier services rely on a higher level system process! (the "notifier") taking responsibility for implementing a user interface. The notifier "hooks" the user interface (normally at system start up) by calling p_noti fyhook. The notifier should pre-allocate any memory it requires so that the call will not fail. The notifier service is used by the file server process to give the user the opportunity to rectify a problem that would otherwise result in a file service request failing (eg to replace an SSD pack that has inadvertently been removed). This scheme works well when the requesting process is an interactive application but poorly if the requesting process is designed to run unattended (eg a communications program) or is itself a server process (eg the window server trying to read a font file). Such processes can use p_setnotify(FALSE) to stop servers such as the file server from using the notify service to give the user the opportunity to rectify an error condition. INT p_notify(TEXT *pT1, TEXT *pT2, TEXT *pO1, TEXT *pO2, TEXT *p03); Present the two zero terminated messages pT‘ and pT2 to the user, where pot, po2 and po3 are either NULL or zero terminated strings, offering up to three options for the user to select. The function waits for the user to select an option and returns zero if the po1 option was chosen, 1 if the po2 option was chosen and 2 if the po3 option was chosen. The message pT1 is presented before pt2. So pti would typically contain a contextual message (eg "Failed to save notes.tpd") with pt2 containing a more detailed message (eg "Disk full"). If pt2 is NULL the second message line is blank. To offer the user 2 options rather than 3, pass po3 as NULL. To offer the user no option (that is, just to wait until the user has acknowledged the message) pass both p02 and po3 as NULL. Alternatively, pass all 3 as NULL as in: p_notify(msg1,msg2,NULL,NULL ,NULL); which is equivalent (on an English machine) to: p_notify(msg1,msg2, "CONTINUE", NULL,NULL); Each message string pt1 and pT2 can be up to €_MAX_NOTIFY_TEXT_SIZE (64) in length including the zero terminator. Each option string po1, po2 and po3 can be up to E_MAX_OPTION_TEXT_SIZE (16) in length including the zero terminator. This function sends an inter-process message to the process that has "hooked" the notify interface and thereby has taken on the responsibility of presenting the error notification user interface. If no process has hooked the notifier, or if the notifier process has terminated, p_notify returns E_GEN_FAIL. The notifier process may, in some environments, take special action if any of the strings passed to p_notify contain a leading zero. Programmers should therefore ensure that such a string is not passed as a parameter to p_notify. Either pass an explicit NuLt (as in the above examples) or intercept the string, as in the following example, where it is assumed that only msg2 may contain a leading zero: LOCAL_C INT NotifyError(TEXT *msg1,TEXT *msg2) { if (msg2 && !*msg2) msg2=NULL; return(p_noti fy(msg1,msg2,NULL ,NULL ,NULL)); > 1When a specialised process hooks the notifier, it is, by convention, called sys$ntryY. ee ee eee eee 57 INT p_notifyerr(INT nError, TEXT *pT2, TEXT *pO1, TEXT *pO2, TEXT *p03); Equivalent to calling p_errs(nerror) followed by calling p_notify with the resulting error string as the second parameter and with pt2 as the first parameter (ie the first two parameters are the other way around compared to p_notify) with the three option parameters being passed on to p_notify. The message pT2 is presented before the error text corresponding to nError and pt2 would typically contain a contextual message. Only system error numbers (which include all errors retumed by the functions described in this manual) should be notified using this service. VOID p_setnotify(UINT nState); Set the notify state of this process. If nstate is FALSE, the system will not automatically present the notifier as a result of an error in a service requested by this process (the error will be returned directly). If nState is TRUE (the default state after process creation) the notifier may be called. INT p_getnotify(VOID); Return the notify state for this process. INT p_notifyhook(INT mType); Hook the notifier interface such that this process will get an inter-process message from the notifying process of type mType as a result of the notifying process calling p_notify or p_notifyerr. Returns zero if successful or €_GEN_FAIL if the notify interface has already been hooked. The mtype message contains an array of 5 string pointers into the notifying process data space corresponding to the parameters to p_nctify in the order pt1, pT2, p01, po2 and po3. A process that hooks the notify interface should initialise messaging using p_minit with a value of at least 10 for the message size. The text strings pointed to by the 5 parameters can be fetched from the notifying process using p_pcpyfr (say using the maximum sizes E_MAX_NOTIFY_TEXT_SIZE and E_MAX_OPTION_TEXT_SIZE). The parameter to p_mfree gives the result of the notification. A process that has hooked the notify interface should not call either p_notify or p_notifyerr as it would then try and send itself a message, resulting in deadlock. If the process that has hooked the notify interface terminates, the system automatically frees the notify interface so that another process can hook the interface. VOID p_notifyunhook(VOID); Release the notify interface. Calls p_panic if the caller does not have the notifier interface hooked. ESSE ee ee Enter and leave The functions p_enter and p_leave work together. You use p enter to call or “enter” a function. If p_leave(err) is called before the entered function returns, the stack is unwound and the call to p_enter 58 6 ERROR HANDLING oe SSeS returns err. By convention, a negative value of err indicates an error and a zero (or positive) value signifies an error-free exit. The call to p_leave (or to a variant such as #_leave) may occur in the entered function or in a sub- function and so on. The p_teave performs what is sometimes called a "non-local goto" where the address of the goto is defined by the last call to p_enter. The enter and leave mechanism is commonly used in medium to large interactive applications to implement structured error recovery. When an error occurs, the application has to do the following to recover: = free any dangling resources (eg free memory cells, close open channels, close screen windows) = inform the user of the error ®* continue Handling all this with ad hoc conditionals in the code can double the size of a program and makes the code hard to follow. An alternative is to set error state variables, driving centralised clean-up code that is invoked by a negative return from a call to p_enter on an error condition. The call to p_enter is placed at an appropriate place to continue after the error. The return value indicates the nature of the error (eg no system memory) and a state variable could give a contextual message (eg “while attempting to open file xxx"). Other error state variables would give the handles of resources that should be freed. INT p_enter(VOID *pfunc,...); INT p_enter1(VOID *pfunc); INT p_enter2(VOID *pfunc, VOID *a1); INT p_enter3(VOID *pfunc, VOID *al, VOID *a2); INT p_enter4(VOID *pfunc, VOID *a1, VOID *a2, VOID *a3); INT p_enter5(VOID *pfunc, VOID *a1, VOID *a2, VOID *a3, VOID *a4); INT plenter6(VOID *pfunc, VOID *a1, VOID *a2, VOID *a3, VOID *a4, VOID *a6); Call function *pfunc where the remaining (up to 5) arguments to p_enter are passed as arguments to *pfunc. You can either use p_enter, which presents the cpect calling convention, or one of the p_enter? variants, which use a more efficient register calling convention. Once a function has been called using p_enter, that function (or any function that is called before *pfunc returns) may Call p_leave(ret) to unwind the stack and return prematurely from the call to p_enter with the return value of ret. If p_leave is not called, p_enter returns the value returned by *pfunc. Functions called with p_enter should return an INT or a UINT (since this is assumed by p_leave). A zero or positive return value is normally taken to indicate that the function completed successfully. Calls to p_enter may be nested, in which case p_leave returns from the last active p_enter on the stack. When p_enter calls *pfunc, it passes parameters to it both on the stack and in registers. Neither calling convention? is compatible with the default calling convention (which passes parameters in registers) as specified in p_std.def. Because of the above, the target of a p_enter must declare the function as using one of the following calling conventions: CDECL where the generated code will take the parameters off the stack ENTER_CALL where the generated code will take the parameters from the registers (which is more efficient) If you also call the entered function directly (without a p_enter) you must ensure that the prototype for the function declares the calling convention consistently. 2Note that the calling convention applied to the function called by p_enter has nothing to do with the calling convention of p_enter (or p_enter1 etc) as discussed above. 59 PLIB REFERENCE SS eee For example, to declare the target for a p_enter using CDECL: LOCAL_C INT CDECL RunTestProgram(TEXT *name) € ----return (0); > or, more efficiently, using ENTER_CALL: #pragma save, ENTER_CALL LOCAL_C INT RunTestProgram(TEXT *name) { -.--return(0); > #pragma restore where, in either case, you would enter the function using, say: ret=p_enter((VOID *)RunTestProgram, "fred"): or, more efficiently: ret=p_enter2(RunTestProgram, "fred"); where ret contains zero if RunTestProgram returned or the parameter passed to p_leave, if p_teave was called. Since it is impossible to construct a general prototype to cover all cases, pfunc is prototyped as a volD *. As in the above example, you have to cast the first parameter to p_enter to a (VOID *) to avoid compilation warnings. The header files are organised in such a way that the cast is automatically done when you use one of the fixed parameter p_enter? variants. Note that the requirement for the target of a p_enter to have one of the two special calling conventions described above means that no PLIB or WLIB function may be the direct target of a p_enter. VOID p_leaveCINT err); Unwind the processor stack and return from the most recently called p_enter, returning the value err. The function does not return to the caller. Although in practice err is often a negative error number it need not be and p_leave can legitimately be used to unwind the stack on any kind of condition. The function calls p_panic if there is no call to p_enter on the stack. INT f_leaveCINT err); Similar to p_teave except that it simply returns err if err>=0. That is, it is equivalent to: GLDEF_C INT f_leaveCINT err) € if (err<0) p_leave(err); return(err); > Although modest in its function, using f_leave rather than p_leave produces smaller executables and makes code more readable. For example, compare: pid=f_leave(p_execc(name, NULL ,0)); 60 6 ERROR HANDLING with: pid=p_exece(name,NULL,0); if (pid<0) p_leave(pid); You cannot use f_leave on functions that return an address and fail by returning NULL. However, the more commonly used functions of this type have corresponding f_ variants. For example, p_alloc and p_realloc have the corresponding f_alloc and f_realloc which internally call p_leave(E_GEN_NOMEMORY) rather than return NULL. Example LOCAL_C INT RunSubProcessWait(TEXT *name) { HANDLE pid; WORD stat; pid=f_leave(p_execc(name,NULL,O)); p_logona(pid,&stat) p_presume(pid); p_waitstat(&stat); if ((stat>>8)!=E_NORMAL_EXIT) p_panic(stat); return(CINT)((BYTE)(stat&0xff))); 3 LOCAL_C INT CDECL RunTestProgram(TEXT *name) € ret=RunSubProcessWai t (name); if (ret) p_printf("Program %s failed with reason %d",name,ret); return(0): > LOCAL_C VOID RunTestPrograms(TEXT *list) € TEXT *p; INT ret; TEXT name [32]; p=p_scpy(&name [32] ,""testx")-1; while (*p=*List++) € ret=p_enter((VOID *)RunTestProgram, &name [0] ); if (ret<0) € p_atos(&msg[0],"Failed to run %s",&name[01); ret=p_notifyerr(ret,&msg[0] , "CONTINUE", "ABANDON" , NULL); if (ret==2) p_exit(0); > 3 d GLDEF_C INT main¢(VOID) € RunTestPrograms("abed"); /* runs testa.img, testb.img, ... */ return(0); } Note the use of cbEct in the declaration of RuntestProgram. There are more examples of the use of p_enter and p_teave in the Files chapter. 61 CHAPTER 7 MEMORY ALLOCATION LESS en ee ee Overview of system memory usage The EPOC operating system runs on the 8086 processor (and also the 80286, 80386 or 80486) where up to 1Mb of memory may be addressed. On SIBO machines and depending on the model, all or part of this address range may be used where the available memory is allocated (from address zero to Oxfffff) as follows: = 1K bytes of interrupt vectors (required by the 8086 architecture) m the screen bit-map (small display models) = the operating system data space = allocated memory segments (including application code segments, process data segments and device driver segments) « unallocated memory = the internal RAM drive (LOC::M:) = environment variables (up to 4K bytes) = any portion of the 1Mb that is not used = the screen bit-map (large display models) = the system ROM (typically 256K bytes) The contents of the internal RAM drive and the environment variables survive a system reset (unless the ESC key is held down) and most system crashes. See p_getres in the chapter General System Services for more on system resets. If we consider only that (greater) part of memory that is dynamically allocated, it is organised into four main sections: The system maintains all the unallocated memory in a single chunk - between the allocated memory segments and the memory used by LOC::M:. This means that memory segments have to be moved as a result of other segments being created, deleted or having their size changed. Although application code segments and process data segments can and do move at any time while an application process is running, the operating system automatically adjusts the 8086 segment registers (CS, DS, SS and ES) to follow any movement without explicit support from the application - as discussed in the first chapter of this manual. 63 PLIB REFERENCE As well as giving some background on memory usage, this chapter describes functions that allocate and access: ™" memory cells from the heap in the process data segment (which is a dynamic segment) = memory segments (either device or dynamic) = environment variables Memory segments The allocated memory segments contain both device and dynamic memory segments. Device segments are created when an external device is installed and are deleted when the device is removed. Once created, a device segment does not normally change its size. The first two device segments are special and are created at system startup for the process data segments of the first two processes to be created - the null process (SYS$NULL) and the supervisor (SYS$SMANG). Neither of these two process data segments changes size. Dynamic segments are much more volatile. Code and data segments are created and deleted as processes are created and terminated. Process data segments change their size to accommodate heap allocations. Device segments are allocated with a lower address than the dynamic segments so that they are not disturbed by any activity with respect to the more volatile dynamic segments. A device driver normally has to stop working while its segment is moving - which could lead to loss of data (say when receiving data via the serial port). Allocated memory segments are described by: = asegment name = asegment handle = asegment size = a segment address Segment names Segment names are zero terminated strings of up to eight characters followed by an optional period and up to three further characters (the same rules as for file names). Examples of valid names are as follows: NOTES NUMBERS .DAT DATASEG.01 As with file names, the segment name extension normally indicates the usage of the segment. The system has the following conventions: .LDD and .PDD indicate device segments that contain a Logical Device Driver and a Physical Device Driver respectively. Device drivers are normally written in 8086 assembler. See the EPOC O/S System Services reference manual for more about device drivers. -5SC indicates the primary shared code segment that is associated with one or more processes. Running say myprog.img will cause the code to be loaded into a segment called myprog.$sc (provided that myprog.$sc isn't already loaded). -DYL indicates a dynamic library code segment (DYL) that is associated with one or more processes. See the chapter Object-Oriented Programming for more about .$nn indicate process data segments where nn consists of two decimal digits (01, 02, ..-) according to the process number. The name of a process data segment is normally the same as the process name although they are in fact independently held (the process name can be changed using p_prename). The process data segment is described further below. Segment handle, address and size The segment handle is actually the relative address of the segment table index entry in the operating system data space. This 16-byte entry contains the address of the segment, the segment usage count and the segment name. 64 7 MEMORY ALLOCATION — SEES The usage count normally indicates the number of processes that are using the segment - for example, when there are 2 processes of the same application, the usage count of the application code segment is 2 whereas the usage count of each process data segment is 1. When the usage count drops to zero, the memory segment may be (and normally is) deleted. The order of the segments follows the order of the segment table index entries and the segment size is calculated by subtracting the start addresses of adjacent segments. The segment index table has a fixed total capacity with fixed sub-capacities for device and dynamic segments (typical limits are 96 total segments as 32 device segments and 64 dynamic segments). A segment allocation will fail if one of these limits is reached (which is unlikely unless there is a bugged application that fails to free segments). Although the allocated segments themselves are contiguous, the segment index table may have "holes" in it as a result of segment deletion. When a new segment is allocated, it will tend to fill any holes in the index table first and, in this case, the created segment will be inserted before other segments causing them to be moved. The address and size of memory segments is expressed in 16-byte paragraphs (as used in setting the value of the 8086 segment registers CS, DS, ES and SS). It follows that segments start on 16-byte boundaries and are a multiple of 16 bytes long. From the point of view of the segment allocator, the maximum segment size is 512K which makes it possible to specify a segment size (in 16-byte paragraphs) within a signed 16-bit word. Process data segments When a process is created (normally by loading an image using p_execc or p_execcasync), a process data segment is also created. When that process is running application code, the 8086 DS, SS and ES registers point to the start of the data segment which can not be greater than oxffed! bytes long (this is called the small model on PCs). The data segment contains (from low to high address): = the reserved static variables (0x40 bytes) = the floating point emulator data space (0x200 bytes from offset 0x100) = the processor stack ® initialised static variables =" wuninitialised static variables @ the process heap The size of the processor stack depends upon the C startup module that is being used but is typically in the range 2K to 8K. Note that the stack size declared by the startup module includes the 64 bytes of reserved static variables and the floating point emulator data space. The word at address zero is initialised to OxpEaD and is otherwise unused. If it is not OxDEAD, it is probably because of a write using an uninitialised pointer that happened to have a zero in it. In the absence of any further initialisation (such as the 0xDEAD above), the reserved statics variables are initialised to zero. The bytes in the stack area are initialised to oxf#. The number of oxff bytes from address 0x40 in the process data segment measures the number of spare bytes on the stack provided the program is not using the floating point emulator. If the program is using the floating point emulator, the lowest point the stack should legitimately reach is 0x300. Despite their name, the uninitialised static variables are all initialised to zero. The process heap contains dynamic data structures that are created and destroyed within the lifetime of the process. The heap is placed at the end of the process data segment so that it can be expanded by expanding the process data segment. Since the data segment is limited to oxffe0 bytes, the maximum heap size is somewhat smaller than 64K, depending on the size of the stack and the space taken by the static variables. The process heap and the allocator are described in detail next 1The segment size is limited to 32 bytes less than the maximum 64K so that a stack underflow will always cause an address trap. 65 PLIB REFERENCE SS a ee eR | The heap allocator The allocator is used to allocate, resize and free variable length memory cells (which typically range from 10s of bytes to a few kilobytes in length) from the process heap. Allocated cells are referenced directly by their address; they do not move to compact free space left by freed cells. The allocator functions are: p_alloc, f_alloc allocates a cell, returning its address. p_free frees a cell, which is returned to the heap. p_realloc, f_realloc changes the size of the cell, returning its new address. p_adjust opens or closes a gap in the middle of the cell (useful for deletion and insertion of cell content), changing the size of the cell as appropriate. p_alen returns the size of the cell. p_hgran sets the heap granularity. p_allwalk walks all cells in the heap calling a supplied function (used for heap diagnosis). p_alichk uses p_allwalk to check the integrity of the heap. p_atlspe gets the start address and free space in the heap. The functions f_alloc and f_reatloc are identical to p_alloc and p_realtoc respectively except that they call p_leave(E_GEN_NOMEMORY) if the memory could not be found rather than returning a NULL address. See the function p_leave for more details. The allocator functions are used internally by many other PLIB functions. Heap structure After a number of allocate and free calls the heap typically consists of ranges of adjacent allocated cells separated by single free cells (which are linked). Each cell (whether free or allocated) is prefixed by a hidden 16-bit word that gives the size of the cell in bytes. This leading length word is hidden since the address returned by p_alloc, p_realloc and p_adjust skips this header and the length returned by p_aten does not include it. Writing beyond a cell's limits will corrupt a cell length word (and possibly also a free space cell pointer) which destroys the heap's integrity. Such errors are difficult to debug because there is no immediate effect - the corruption is a "time bomb”. It will eventually be detected (resulting in a call to p_panic) bya subsequent allocator call (such as p_free). The p_allchk function is provided as a debugging tool to force a call to p_panic sooner rather than later when it is suspected that the heap's integrity has been damaged. Growing and shrinking the heap In EPOC, a process heap is not fixed in size - the system can grow and shrink the heap (and the process data segment that contains it). The heap is grown to satisfy allocation requests that would otherwise fail, up to the Oxffed process data segment limit. The heap may be shrunk to release memory to the system when it is required. When the heap is grown, it is normally grown by 2K bytes more than is strictly needed to satisfy the request - see p_hgran for more details. When an application is executed to create a process, the initial size of the heap is taken from a value that is stored in the executable (by default, 2K bytes). This same value also specifies the minimum size of the heap. Allocation is based on "walking" the free space list to find a free cell that is big enough to satisfy the request (using the "first fit" algorithm). If no free cell is big enough, the system will attempt to grow the data segment to add more free space at the end of the heap. If there is no memory in the system to accommodate growth or if the data segment has reached its maximum Oxffe0 byte value, the allocate request fails. There are few circumstances when an allocate Tequest can be assumed to succeed and calls to p_alloc, p_realloc and p_adjust should have recovery code to handle a failure to allocate. From the point of view of the user of an application, the two causes of an allocation failure produce quite different situations”. If there is no memory left in the system, this can normally be remedied by the user taking some action to release memory (such as exiting a task). If the 64K limit has been reached, this is 2Unfortunately, the system does not distinguish between the two causes of allocation failure. 66 7 MEMORY ALLOCATION Ce ee ee presumably as a result of a heap-based data structure reaching its design limit (rather than a bug as in, for example, “alloc heaven", described below). Applications that contain indefinitely growing heap-based data structures (as in, say, a spreadsheet) should not allow the data structure to grow until the allocation fails when the oxffed data segment limit is reached because, at this limit, there may not be sufficient memory in the free space list to perform other tasks (such as saving the data to file!). The growth of such data structures should be monitored by the application and limited to leave sufficient heap capacity for tasks that the user would reasonably expect to be able to perform. The segment allocator can reclaim excess space from a heap if the last cell in the heap is a free cell, reducing the size of the last free cell to a small value. However, the heap is never shrunk below its initial size (as specified by the value in the executable). Alloc heaven There are cases in which programs allocate a sequence of cells which must either exist as a whole or not at all. If during the allocate sequence one of the later allocations fail, the previously allocated cells must be freed. If this is not done, the heap will contain unreferenced cells that consume memory to no purpose. At Psion we say that these cells have gone to “alloc heaven". When designing how to physically organise data structures into alloc cells you should be mindful of the recovery code that must be written to free partially built multi-cell structures. The fewer the cells in a structure, the easier the recovery code. Internal fragmentation The free space in a heap is normally fragmented where the largest cell that may be allocated is substantially smaller than the total free space. Excessive fragmentation, where the free space is distributed over a large number of cells (and where by implication many of the free cells are small) should be avoided because it results in an inefficient use of memory and reduces the speed with which cells are allocated and freed. Practical design hints for limiting internal fragmentation are: = Avoid using the heap for small highly transient data structures that can be placed on the stack (as an automatic). High frequency cycling through allocate and free pairs "churns" the heap and leads to a long free space list. = When you have a large number of variable length data structures (particularly when they are frequently resized), "granularise” them (i.e. round the allocate up to a multiple of some reasonable value) so that you decrease the chance of leaving small unusable free space cells. = Use heap analysis tools to give you a feel for what is going on. You may find that by changing the way you do something you get a better heap and you may even discover some alloc heaven. Don't go too far - there are diminishing returns to heap usage tuning. VOID *p_alloc(UINT size); VOID *f_allocC(UINT size); Allocate a memory cell of at least size bytes long from the heap and return the address of the allocated cell or NULL if there is insufficient memory. You should always test the result for NULL and take recovery action. The size actually allocated may be a few bytes more than that requested (see p_alen). The maximum size is 64K minus the combined size of the machine stack and the space taken by static variables. If the heap is corrupt, calling p_alloc may or may not detect it - but if it does it will call p_panic. Use p_altchk to check the integrity of the heap thoroughly. The function f_alloc is identical except that it calls p_leave(E_GEN_NOMEMORY) rather than return NULL. 67 PLIB REFERENCE Ss eee Example GLDEF_C TEXT *AllocString(TEXT *str) /* Allocate and copy in a zero terminated string. Ay/ € TEXT *p; if (p=p_alloc(p_slen(str)+1)) P_scpy(p,str); /* Copy fin the string */ return(p); > This example assumes that any more specific error recovery is handled by the caller. VOID p_free(VOID *pcell); Free the allocated memory cell at address pcelt, returning the cell to the free memory list. Does nothing if pcell is zero - this is sometimes useful in error clean-up situations. If pcell is non-zero, it should contain the address of a cell as returned by, for example, p_alloc. Passing a value that is not the address of an allocated cell (eg by freeing a cell twice) will corrupt the heap. There is a chance that p_free will detect a bad address and call p panic. VOID *p_realloc(VOID “pcell, UINT size); VOID *f_realloc(VOID *pcell, UINT size); Change the size of the allocated cell pcetl to be size bytes and return the address of the new cell or NULL if there was insufficient space for the size change. If NULL is returned, the original cell is unaffected. The limits on size are as for p_alloc. Calling p_realloc(pcell,size) when pcell is zero is equivalent to calling p atloc(size) - this can be useful in start up situations. The cell retains its original content which is truncated if the cell size is reduced. The cell start address does not change when the cell size is reduced or stays the same. If the cell size is increased, p_realloc will use any trailing free space of sufficient size but, more likely, it will allocate a new cell, copy the data across and free the old cell where, in this case, the returned address is different from pcelt. If pcelt is neither zero nor the address of an allocated cell, the heap will either be corrupted or p_panic will be called. The function f_realtoc is identical except that it calls p_leave(E_GEN_NOMEMORY) rather than return NULL. VOID *p_adjust(VOID *pcell, UINT offset, INT amount); Open or close a gap at offset offset within the allocated cell pcetl, using p_realloc to make the appropriate change to the cell size. As for p_realloc, p_adjust returns the address of the new cell or NULL if there was insufficient memory. If amount is positive, a gap of amount bytes is opened. If amount is negative -amount bytes are deleted. If amount is zero, the function has no effect and returns pcellt. Unlike p_realloc, pcetl may not be passed as NULL. If pcetl is not the address of an existing allocated cell, the heap will either be corrupted or p_panic will be called. If amount is negative, -amount bytes is deleted by shifting the trailing contents left, closing the gap. old cell XXAANXAXAXXZZZZZZZZZZYVYVVYVVVVVVVY < offset >< amount > new cell XXAXXAXXXXYVVVVVVVVVYVVY < old size - amount > 68 7 MEMORY ALLOCATION The cell size is decreased by the same amount. There is no data deletion if the offset is greater than or equal to the original cell size. The minimum cell size actually allocated is as for p_alloc. If the new size would be negative, p_panic is called. If amount is positive, the cell size is increased and a gap is then inserted starting from the specified offset by shifting the trailing contents right. old cell XXXXXXXAXXKYVVVVVVVVVVYVY < offset >< amount > new cell XXXXXXXXXXZZZZZZZZZZYVYVYVVVVVVVVVYY < old size + amount > If offset is greater than or equal to the original cell size, no shifting takes place. A typical use is to insert or delete a record (which may be of fixed or variable length) in a contiguous sequence of records contained in an alloc cell. ell length UINT p_alen(VOID *pcell); Return the length in bytes of the allocated cell pcett. The returned cell length will be equal to or slightly larger than that requested using p_alloc or p_realloc because (1) sizes are rounded up to an even size, (2) there is a minimum cell size and (3) the allocation can't leave a trailing free space cell below the minimum size. If the passed address is not the address of a cell, there is a chance that p_alen will detect it and call p_panic. VOID p_hgran(UINT nparas); Set the heap granularity to nparas (measured in paragraphs where a paragraph is 16 bytes). The maximum value for nparas is €_MAX_GROWBY (16K bytes) - p_hgran calls p . panic if this is violated. Processes are created with heap granularity €_GROWBY_DEFAULT (2K bytes). €_MAX_GROWBY and E_GROWBY_DEFAULT are defined in epoc.h. The heap granularity controls the increment by which the heap grows to satisfy an allocation request that cannot be met from the existing free space list. The granularity is added to the amount that is just sufficient to satisfy the request. Growing the heap can be computationally expensive because many other segments may have to be shifted by the growth of the process data segment. Applications that can rapidly create large data structures (such as when loading a large file in the text processor) can improve their performance by setting a larger heap granularity. Setting the heap granularity has no affect on the way the system shrinks the heap to release memory. VOID p_allwalkCVOID (*fptr)(VOID *fpar, INT isalloc,UINT len), VOID *fpar); Walk through every cell in the heap (whether allocated or free) in sequence from low to high address and, if fptr is not NULL, call fptr for each cell. The heap is checked for consistency and p_panic is called if an inconsistency (for example an overlap between a free cell and an allocated cell) is detected. In the call to #ptr, the parameter isalloc is TRUE if the cell is an allocated cell and FALSE if it is a free cell. The parameter len is the total length of the cell in bytes (ten includes the size of cell header information and is 2 greater than that returned by p_alen). Cell addresses can be calculated from the cell lengths and the heap start address. The heap start address may be found by calling p_allspc. This function is provided for heap diagnosis and is called, for example, by p_al tchk. 69 PLIB REFERENCE In the following example, NumAl locCel ls returns the number of cells allocated: LOCAL_C VOID CountIfAlloc(UINT *pn, INT isalloc,UINT len) € if Cisalloc) *pnt=1; } GLDEF_C UINT NumAl locCells() { UINT n; n=0; p_allwalk((VOID (*)(VOID *,INT,UINT))Count! fAlloc,&n); return(n); > The call to p_allwalk calls back Count! fAlloc (which simply increments the allocated cell count if the cell is an allocated cell) for each cell in the heap. VOID p_allchkCINT num); Walk through all the allocated cells checking for consistency with the free space list. Call p_panic(Oxff) if there is something wrong. Used as a debugging aid to detect errors from freeing a cell twice or overwriting the boundaries of an allocated cell. Otherwise, such errors can remain undetected for some time. If it does discover something wrong, and before calling p_panic, it writes 3 words of diagnostic information to reserved statics, as follows: DatApp1 (0x28) a reason code, described below DatApp2 (Ox2a) the address at which the corruption was discovered DatApp3 (Ox2c) the passed parameter num, to enable identification of the offending call The reason code is one of: 5 a free cell pointer was probably overwritten 6 a cell length is too small or odd (possibly because it was overwritten) 7 an allocated cell length is too large (possibly because it was overwritten) This information is only useful in conjunction with a debugging tool that allows the process data segment to be examined after a call to p_panic. UINT p_allspc(VOID **pheap); Return the potential free space in the heap in bytes and writes the start address of the heap to *pheap. The potential free space is calculated as the sum of the free cells in a process data segment that has been expanded to its full size of Oxffe0 bytes. In practice, the amount that can be allocated will certainly be less than this and depends upon how fragmented the heap is and on the amount of free memory in the system. This function should not be used to predict a successful allocation but it can be used to predict an unsuccessful one. The heap start address *pheap can be used to turn the cell lengths produced by p_allwalk into addresses. 70 7 MEMORY ALLOCATION ee a ee ES ee ee System memory usage UINT p_getram(VOID); Return the size of the addressable system RAM in paragraph (16 byte) units. Since the memory in machines containing more than 512 kilobytes of RAM is bank-switched, this function will never return a value larger than 32768 (corresponding to 512 kilobytes of RAM). UINT p_totalK(VOID); This function is only available in EPOC version 3.50 or later. Return the total amount of memory in the machine in kilobytes. This function reports the total amount of RAM present in the machine, irrespective of bank-switching. Thus, on a machine containing 1 megabyte of RAM, a call to p_totalk will return the value 1024, whereas a call to p_getram on the same machine will return 32768 (corresponding to 512 kilobytes). UINT p_sgfree(VOID); Returns the amount of available (i.e. currently unused) addressable segmented memory in paragraph (16 byte) units. Since the memory in machines containing more than 512 kilobytes of RAM is bank-switched, this function will never return a value larger than 32768 (corresponding to 512 kilobytes of RAM). The value returned should be treated with some caution as the amount of available memory in a multi- tasking environment is a dynamic function of the memory requests of all the currently running processes. UINT p_sgramdisk(VOID); Returns the number of 16-byte paragraphs in the addressable RAM that are currently used by the internal RAM disk (LOC::M:). On machines containing more than 512 kilobytes of RAM, the RAM disk will be created in an upper bank. In this case, unless the RAM disk overflows into the addressable RAM, p_sgramdisk will generally retum zero. On machines containing not more than 512 kilobytes of RAM, the return value will be larger than that obtained from p_dinfo, which measures storage capacity rather than the total number of bytes used. re ee a er ee rr ee EET] Memory segments Applications that need more memory to store data, can allocate one or more external memory segments. Each segment can be up to 512K bytes long, subject to the availability of free system memory. The most common use of the functions described in this section is to implement a potentially large data structure without being constrained by the 64K (or less) limit of the heap. To create and access a data structure in an external data segment, you would use: p_sgcreate to create an external data segment of a specified initial size P_sgcopyto to write to the external segment p_sgcopyfr to read from the external segment 71 PLIB REFERENCE p_sgadjust to adjust the size of the data segment (say to increase its size to accommodate additional content) p_sgdelete to delete the data segment after it is no longer required Memory segments can be used to implement a data structure that is accessed by more than one process. For example, a "pipe" in which one process creates a data segment and writes to it and where a second process reads from it. In this case, the second process would use: p_sgfind to locate the pipe by its memory segment name p_sgopen to open the segment p_sgcopyfr to read from the segment p_sgclose after finishing with the segment You would need to use an associated semaphore to synchronise access to the segment (as described in the chapter Asynchronous Requests and Semaphores). If a memory segment is locked by a process calling p_sglock, the segment will survive the demise of the creating process HANDLE p_sgcreate(TEXT *pName, INT nParas, INT uMode); Creates a memory segment with the zero terminated name pName and size nParas (in 16-byte paragraphs) and, if successful, return the positive handle to the created segment. Otherwise, it returns one of the following negative error numbers: E_GEN_NOMEMORY Not enough memory to satisfy the request E_GEN_NOSEGMENTS No memory segment handles are available E_FILE_EXIST A memory segment of the requested name already exists E_FILE_NAME The requested name is invalid The memory segment created is not initialized in any way and will contain random data. The returned handle allows access to the contents of the segment using p_sgcopyto and p_sgcopyfr. After being created, the memory segment is automatically opened and given a usage count of 1. It should be closed when no longer required by calling p_sgclose. When a process terminates, any open memory segment is automatically closed, decrementing the segment usage count. If the access count become zero or negative, the segment is deleted. The initial size nParas (in 16-byte paragraphs) must be positive (so the maximum segment size is 512K bytes). The parameter uMode should be one of the following: E_SEGMENT_HIGH to create a dynamic memory segment. E_SEGMENT_LOW is provided for future expansion and currently has the same effect as E_SEGMENT_HIGH. E_SEGMENT_DEVICE to create a device segment. This will result in all devices being held while memory is moved and then resumed. All dynamic segments will be moved up in memory to make room. This service is called, for example, by the File Server when loading external device drivers and should not be used by applications. E_SEGMENT_LOCKED is the same as £_SEGMENT_HIGH except that no process owns the created segment. Like E_SEGMENT_DEVICE this mode is used internally by the operating system and should not be used by applications. The function calls p_panic if the requested size was negative or if uMode was not one of E_SEGMENT_LOW, E_SEGMENT_HIGH, E_SEGMENT_DEVICE or &_SEGMENT_LOCKED. 72 7 MEMORY ALLOCATION 9) INT p_sgdelete(TEXT *pName); Delete the memory segment identified by the zero terminated name pName. Returns zero if successful or one of the following negative error numbers: E_GEN_INUSE the segment usage count is greater than zero (eg because it is opened by another process) E_FILE_NXIST the memory segment does not exist £ FILE _NAME the memory segment name is invalid HANDLE p_sgopen(TEXT *pName); Open the memory segment identified by the zero terminated name pName and, if successful, return the handle to the opened memory segment. Otherwise, it returns one of the following error numbers: &_FILE_NXIST the memory segment does not exist E_GEN_OPEN the memory segment is already open to this process E_FILE_NAME the memory segment name is invalid The returned handle allows access to the contents of the segment using p_sgcpto and p_sgepfr. Opening a segment increments the segment usage count. When a process terminates, any open memory segment is automatically closed, decrementing the segment usage count. If the access count becomes zero or negative, the segment is deleted. There is no limit on the number of memory segments that may be opened by a process. INT p_sgcopyto(HANDLE nHandle, LONG pos, VOID *source, UINT len); Copy len bytes from source in the current process data segment to offset pos in the open memory segment nHandle (as returned from p_sgcreate or p_sgopen). The system ensures that the copy is not interrupted by another process. Address trapping is automatically switched off for the duration of the copy. The return value has no significance. The function calls p_panic if pos+ien is greater than the size of the memory segment or if nHandle is not a valid memory segment handle. A program can check that the segment is still in existence before a call to p_sgcopyto by calling p_sgopen. If you want to write to a process data segment, it is more convenient to use p_pepyto, which takes a process ID rather than a segment handle. P_sgcopytr | INT p_sgcopyfr(CHANDLE nHandle, LONG pos, VOID *target, UINT len); Copy len bytes from offset pos in the open memory segment nHandle (as returned from p_sgcreate or p_sgopen) to target in the current process data segment. The system ensures that the copy is not interrupted by another process. The return value has no significance. The function calls p_panic if pos+len is greater than the size of the memory segment or if nHandle is not a valid memory segment handle. A program can check that the segment is still in existence before a call to p_sgcopyfr by calling p_sgopen. 73 PLIB REFERENCE If you want to copy from a process data segment, it is more convenient to use p_pcpyfr, which takes a process ID rather than a segment handle. UINT p_sgsize(HANDLE nHandle); Returns the size (in 16-byte paragraphs) of the open memory segment nHandle (as returned from p_sgcreate OF p_sgopen). The function calls p_panic if nHandle is not a valid memory segment handle. INT p_sgadjust(HANDLE nHandle, INT nParas); Adjust the size of the open memory segment nHandle (as returned from p_sgcreate Or p_sgopen) to nParas 16-byte paragraphs. Return zero if successful or the negative E_GEN_NOMEMoRY if there is not enough memory to satisfy the request. The new segment size nParas must be positive (it follows that the maximum segment size is 512K bytes). Setting nParas to zero discards all the memory allocated to the memory segment but does not delete the segment. The function calls p_panic if nParas is negative or if nHandle is not a valid memory segment handle. HANDLE p_sgfind(HANDLE fHandle, TEXT *pMatch, TEXT *pName) Write the next segment name that matches the zero terminated match string pMatch as a zero terminated string to pName where fHandle is NULL for the first call and is subsequently the positive return value from the previous call. When there are no further segments matching pMatch, it returns E_FILE_NXIST. Used repeatedly to find all the segments that match the wild card string pointed to by pMatch. The buffer at pName should be big enough to receive E_MAX_NAME+2 bytes. The wild card string pMatch should remain the same between successive calls. No memory is used by this service and it can be abandoned at any time without taking any further action. The function calls p_panic if fHandle is not a valid memory segment handle. Example GLDEF_C VOID ListCodeSegments(VOID) { HANDLE fH; TEXT buf [ E_MAX_NAME+2] ; fH=NULL; while ((fH=p_sgfind(fH,"*.$SC", &buf [0] ))>0) p_puts(&buf [0] ); > INT p_sgclose(HANDLE nHandle); Close the open memory segment nHandle (as returned from p_sgcreate or p_sgopen) decrementing the usage count. Returns zero if successful or the negative E_GEN_NOTOPEN if the memory segment is not open to this process. Should the access count become zero or negative, the segment is deleted. The function calls p_panic if nHandle is not a valid memory segment handle. 74 7 MEMORY ALLOCATION VOID p_sglock(HANDLE nHandle); Lock the open memory segment nHandle (as returned from p_sgcreate or p_sgopen) by incrementing the segment usage count. If p_sglock is called after creating a segment, the segment will not be deleted when the process terminates. The function calls p_panic if nHandle is not a valid memory segment handle. VOID p_sgunlock(HANDLE nHandle); Unlock the open memory segment nHandle (as returned from p_sgcreate or p_sgopen) by decrementing the segment usage count. There is no harm in unlocking a segment that is already unlocked, although if this done inadvertently the segment could be deleted by another process. The function calls p_panic if ntandle is not a valid memory segment handle. SSS eee eee SS eee eee ae Environment variables The system allocates up to 4K bytes for environment variables at the high address end of the system RAM. Environment variables are a scarce resource and should be used sparingly. An environment variable consists of: a name of up to E_MAX_ENV_SIZE (16) bytes containing any byte except '*' or '7! a value of up to P_ENVMAX-1 (256) bytes with no restriction on the content Each environment variable is stored as two successive leading byte count strings (see the example in the description of p_findenviron, below). If you are dealing with environment variables where both the names and the values are character strings you can use: p_getenv to get the value of an environment variable p_setenv to replace the value of an existing environment variable or to create one if necessary p_delenv to delete an environment variable p_fndenv to get a list of environment variables and their values A more general but less convenient set of environment variable functions (which can be used, for example, when the values are binary) are: p_getenviron to get the value of an environment variable p_setenviron to replace the value of an existing environment variable or to create one if necessary p_delenviron to delete an environment variable p_findenviron to get a list of environment variables and their values P_geteny INT p_getenv(TEXT *pMatch, TEXT *pValue); Copy the value of the environment variable that matches the zero terminated name pMatch to pValue and add a zero terminator to the end of the copied value. The name pMatch may include the wild card characters '?’ and '*' in which case the value of the first matching name is copied. Retums zero if successful or the negative &_FILE_NxIsT if no matching environment variable exists. i rte 75 PLIB REFERENCE The maximum length of an environment variable value is P_ENVMAX-1 bytes so up to P_ENVMAX bytes can be written to pValue (including the zero terminator). INT p_getenviron(TEXT *pMatch, INT mLength, VOID *pValue); Copy the value of the environment variable that matches the name péatch of length mLength to pValue and return the number of bytes copied. Return the negative €_FILE_Nx1sT if no matching environment variable exists. The name pMatch may include the wild card characters '?' and '*' in which case the value of the first matching name is copied. The maximum length of an environment variable value is P_ENVMAX-1 bytes. INT p_setenv(TEXT *pName, TEXT *pValue); Copy the content of the zero terminated string pvalue (excluding the terminating zero) into the value of the environment variable with the zero terminated name pName, replacing any previous value. If the environment variable does not exist it is created with the specified name and value. The name pName may not include wild cards and must not exceed E_MAX_ENV_SIZE in length. The length of pValue should not exceed P_ENVMAX-1 (255) - any non-zero value in the high byte of the length of pvalue is ignored. Returns zero if successful or one of the following negative error numbers: E_GEN_NOMEMORY the system was unable to allocate room to store the environment variable and its value either because there is not enough free system memory or because of the 4K limit on the environment variable space E_GEN_FAIL if pName contains a wild card character The function calls p_panic if the length of pName exceeds E_MAX_ENV_SIZE. Note that you can't delete an environment variable by giving it a null value - use either p_delenv or p_delenviron. INT p_setenviron(TEXT *pName, INT nLength, VOID *pValue, INT vLength); Copy vLength bytes from pValue into the value of the environment variable with name pName of length nLength, replacing any previous value. If the environment variable does not exist it is created with the specified name and value. The name pName may not include wild cards, ntength must not exceed E_MAX_ENV_SIZE and vLength should not exceed P_ENVMAX-1 (255) - any non-zero value in the high byte of vLength is ignored. Returns zero if successful or one of the following negative error numbers: E_GEN_NOMEMORY the system was unable to allocate room to store the environment variable and its value either because there is not enough free system memory or because of the 4K limit on the environment variable space E_GEN_FAIL if pName contains a wild card character The function calls p_panic if nLength exceeds E_MAX_ENV_SIZE. _ Delete en INT p_delenv(TEXT *pMatch); Delete the environment variable that matches the zero terminated name pMatch. The name pMatch may include the wild card characters '?' and '*' in which case the first matching environment variable is deleted. Retums zero if successful or the negative E_FILE_NXIST if no matching environment variable exists. 76 7 MEMORY ALLOCATION = INT p_delenviron(TEXT *pMatch, INT mLength); Delete the environment variable that matches name patch of length mength. The name pMatch may include the wild card characters *?' and '*' in which case the first matching environment variable is deleted. Returns zero if successful or the negative E_FILE_NXIST if no matching environment variable exists. INT p_fndenv(TEXT *pMatch, TEXT *pName, TEXT *pValue, HANDLE *pHandle); Called repeatedly to find the name and value of all environment variables that match the zero terminated wild card name pMatch. On the first call *pHandle should contain zero and subsequent calls pass the value that is written by the previous call. The function returns zero if a matching environment variable was found or the negative &_FILE_EOF when there are no more matching names. The wild card match string patch should remain the same on successive calls. Each successful call writes the name as a zero terminated string to pName (which should have room for E_MAX_ENV_SIZE+1 bytes) and its value followed by a zero terminator to pValue (which should have room for P_ENVMAX bytes). A wild card name of "*" will match all the environment variables. INT p_findenviron(TEXT *pMatch, INT mLength, UBYTE *pBuf, HANDLE *pHandle); Called repeatedly to find the name and value of all environment variables that match the wild card name pMatch of length mlength. On the first call *pHandie should contain zero and subsequent calls pass the value that is written by the previous call. The function returns zero if a matching environment variable was found or the negative —_FILE_EOF when there are no more matching names. The wild card match string should remain the same on successive calls. Each successful call writes the name and value to p8uf as two successive leading byte count strings giving the environment variable name followed by its value. The maximum length of an environment variable name is E_MAX_ENV_SIZE and the maximum length of a value is P_ENVMAX-1 bytes so pBuf should have room for E_MAX_ENV_SIZE+P_ENVMAX+1 bytes. A wild card name of "*" will match all the environment variables. In the following example, Printallenv prints the name and (potentially binary) value of all the environment variables: LOCAL_C VOID PrintData(TEXT *p,UINT ten) { UBYTE *pe; P_print("%d [", len); for (pe=p+len;p",*p); p_printf¢"]"); > 77 PLIB REFERENCE _—_—_—_—_—_———_ nh ee = =>seeeVOeoomno GLDEF_C VOID PrintALlEnv(voID) € UBYTE *p; HANDLE h; UBYTE bIE_MAX_ENV_SIZE+P_ENVMAX+1); for (h=0;p_findenviron("*",1,&b[0) ,&h)>=0;) € p=&b [0]; PrintData(pt1,*p); /* print name */ pt=*ptl; PrintData(pt1,*p); /* print value */ > 78 CHAPTER 8 ASYNCHRONOUS REQUESTS AND SEMAPHORES This chapter describes asynchronous requests, semaphores, the I/O semaphore and wait handlers. SSS eee ee ee ee er Semaphores Semaphores are provided to synchronise cooperating processes (where, in this context, a process includes a hardware interrupt). There are three common uses: = Synchronising access to a shared resource = Synchronising supplier-consumer relationships = Synchronising the completion of asynchronous requests The first two are described briefly in this section. The third use is far more important and is discussed more extensively in the following section. The semaphores in EPOC are counting semaphores, having a signed value that is incremented by calling p_signal and decremented by calling p_wait. A semaphore with a negative value implies that a process must wait for the completion of some other event, such as the freeing of a shared resource. The mechanism by which a process waits on a semaphore is part of the overall management of process scheduling. Process scheduling In EPOC, if a process is not the currently running process, it is either suspended or in a queue. A process remains suspended until it is resumed by another process (typically its creator) by that process calling p_presume. If a process is not suspended, it is in one of the following three types of queues: The ready queue The ready queue contains processes ordered by process priority. On a reschedule, the process at the high priority end of the ready queue runs. If there is more than one ready process at the highest priority, they take it in turns to run every 4 system ticks. The time delta queue There is a single (possibly empty) time delta queue, effectively containing both processes and timer device entries. The processes are waiting for a relative or an absolute time as a result of calling p_sleep, p_steept or p_steepa. The timer device entries are associated with processes that have requested an asynchronous timer. The head of the queue has its delta time decremented every system tick and is removed when it reaches zero or negative. If it is a process, it is inserted into the ready queue. If it is a timer device entry, the associated process I/O semaphore is signalled. The semaphore queues _ There is a (possibly empty) queue of processes for each created semaphore in the system. If, on calling p_wait, the decremented semaphore is negative, the calling process is placed at the end of the appropriate semaphore queue. When that semaphore is subsequently incremented by a call to p_signal, the process at the head of the semaphore queue is either made current (if it has the highest priority) or it is inserted into the ready queue (after any processes of equal priority). 79 PLIB REFERENCE —_— SS eee See also the description of Preemptive scheduling in the chapter Processes and Inter-process Messaging. Shared access A mutual exclusion semaphore may be used to serialise access to a shared resource (for example, a shared memory segment). The creator of the shared resource uses p_semert to create an associated mutual exclusion semaphore with an initial value of one. Any process wishing to access the resource first calls p wait on the resource semaphore and then calls p_signal after completing its access. Because the semaphore was created with an initial value of one, the first process to call p_wait will return immediately but any other processes that call p_wait will wait in the semaphore queue. Waiting processes are released on a first-in first-out basis when the process currently accessing the resource calls p_signal. The nature of the shared resource should be such that any access completes in a relatively short time so that processes do not wait for extended periods on the mutual exclusion semaphore. Another consideration is that whereas the resource may survive the demise of its creating process, semaphores are automatically deleted when the creating process terminates. In many cases, you may find that EPOC is better suited to supporting the use of server processes for serialising access to a shared resource (as in, for example, the file server and the window server) rather than using a mutual exclusion semaphore. The design of EPOC's inter process messaging was largely driven by the requirements of server processes and their clients. Supplier-consumer In this usage, the semaphore is associated with a pool of data (say a circular list in a shared data segment) and is created with an initial value equal to the number of elements in the pool. The consumer calls p_wait when it is ready to extract an item from the pool and the supplier calls p_signal after it inserted an item into the pool. If there are one or more elements in the pool, the consumer's call to p_wait returns immediately. Otherwise, the call to p_wait returns when the supplier inserts an element and calls p_signal. a ae et ee FE RS ay Asynchronous requests Many system services are implemented in two steps: = make the service request = wait for the requested operation to complete In most cases, as well as providing functions for each step, the system provides a function containing both the above steps. Such functions are called synchronous because they automatically synchronise the requesting process by waiting until the operation has completed. The internal function that makes the request without waiting for completion is called an asynchronous function. Examples of asynchronous request functions are: p_ioa, p_ioc for requests on an open I/O channel p_mreceive to receive an inter process message p_execcasync for loading images p_logona for being informed of a process termination There are also synchronous versions of all the above functions except p_logona (but see the example below). Applications use asynchronous requests in situations like the following: = make request A = make request B = wait for either of the requested operations to complete Processes wait for the completion of asynchronous requests by waiting on their J/O semaphore where each request is associated with a status word. 80 8 ASYNCHRONOUS REQUESTS AND SEMAPHORES —_—_— eS The I/O semaphore When a process is created, the system automatically creates an J/O semaphore on its behalf (a more accurately descriptive name would have been the asynchronous request semaphore). After making one or more asynchronous requests, a process calls p_iowait to wait on the I/O semaphore for one of the requests to complete. A typical application process spends most of its time waiting on its 1/O semaphore. For example, an interactive application process that is waiting for user input from the window server is waiting on the I/O semaphore. The process or the hardware interrupt handler that implements the requested operation typically uses p_iosignalbypid to indicate that the operation has completed. If one or more wait handlers have been installed (wait handlers are described below), they may process the signal and re-signal using p_iosignal. In some cases, it is convenient for the requestor to use p_iosignal to signal itself and to subsequently process that signal in a central call to p_iowait. Status words Although the parameters to asynchronous request functions vary, they all take the address of a signed 16- bit status word which subsequently contains the status of the requested operation. All asynchronous requests exhibit the following behaviour: = While the request is pending, the status word contains the negative E_FILE_PENDING (defined in p_file.h) = When the operation has completed, a value other than E_FILE_PENDING is written to the status word. This value should be zero or positive to indicate success or a negative error number to indicate failure. = The requesting process's I/O semaphore is signalled (after the status word has been written). Making a request while a previous request on the same status word is still pending will normally result in a call to p_panic. When there are multiple requests, each request is associated with a different status word. After returning from p_iowait, the caller typically polls each status word until one is found that contains other than E_FILE_PENOING. That completion is then processed (which might include renewing the asynchronous request) and p_iowait is called again to process the next completion. Every p_iosignal should be matched by a call to p_iowait (or a function that calls p_iowait). The status word associated with the p_fowait must have completed (ie must contain a value other than £_FILE_PENDING) at the time that p_iosignal is called. A common programming error is to introduce a p_iosignal without correctly associating it with a status word, such that the poll after the p_iowait cannot find a completed status word. At Psion this is known as a "stray signal" and programmers should detect this as early as possible by (say) calling p_panic when the poll is unable to find a completed status word. For Series 3, Series 3a and Workabout developers, using the Spy application supplied with this SDK may help identify an accumulation of such unused signals. In the following example, the opened asynchronous timer TimerChannel is used to construct a synchronous function which attempts to write the passed string to the opened serial channel SerialChannel. If it takes more that 5 seconds to complete the write, the function calls p_leave(SERIAL_TIMEQUT). For simplicity it is assumed that there are no other outstanding events which could complete. Thus it is certain that on return from the call to p_iowait, one of the two asynchronous requests has completed. 81 PLIB REFERENCE SEE LOCAL_C VOID StringToSerial (TEXT *str) C UWORD len; WORD TimerStatus: WORD SerialStatus; ULONG timeout; len=p_slen(str); p_ioc(SerialChannel ,P_FWRITE,&SerialStatus,str,&len); timeout=50; /* 5 second timeout */ p_ioc(TimerChannel ,P_FRELATIVE,&TimerStatus,&timeout); p_iowait(); if (SerialStatuss=E_FILE_PENDING) {€ /* must have timed out */ p_iow(SerialChannel ,P_FCANCEL); p_waitstat(&SerialStatus); p_leave(SERIAL_TIMEQUT); /* never returns */ > p_iow(TimerChannel ,P_FCANCEL); p_waitstat(&TimerStatus); > The functions p_ioc and p_iow are described in the next chapter: J/O System. Cancelling an asynchronous request In the above example, the asynchronous request which does not complete is cancelled by making a P_FCANCEL request using p_iow (the synchronous version of p_ioc or p_ioa). Most asynchronous request functions have an associated cancel function; the P_FCANCEL, to cancel I/O requests on a device channel, is one example. Other examples are: p_meancel to cancel a call to p_mreceive to receive an inter process message p_logoffa to cancel a call to p_togona for being informed of a process termination The following general principles apply to all functions that cancel an asynchronous request: = — the cancel precipitates the completion of the operation (it does not stop the operation from completing) = the cancel may or not be effective (that is, the operation may complete naturally before the cancel is processed) = after a cancel, you must still process the completion of the asynchronous request (typically by immediately calling p_waitstat to "use up” the signal) Waiting for a particular completion When waiting for the completion of a particular asynchronous request, the wait on the I/O semaphore must be sure that it is not fooled into a premature return by the completion of any other pending asynchronous request. This is done by calling p_waitstat which behaves in a similar way to p_jowait except that it only returns when the associated status word is other than &_FILE_PENDING. In general, p_waitstat is a safer option than p_iowait to "use up” the signal resulting from the cancelled operation. If the cancel is not immediately effective and another completion causes p_iowait to return, the program could continue and make another request before the cancelled operation completes (which would result in p_panic being called). The above example illustrates the technique although, in this case, it is not strictly necessary since there can be no confusion as to which event has completed. Constructing synchronous functions General purpose functions that provide a synchronous interface must use p_waitstat rather than p_iowait since they can not assume that there are no other pending asynchronous requests. 82 8 ASYNCHRONOUS REQUESTS AND SEMAPHORES OO SSS The use of p_waitstat is illustrated by the following example of a synchronous function: GLDEF_C INT ResumeWait(HANDLE pid) € WORD stat; INT ret; if (!(ret=p_logona(pid,&stat))) € P_presume(pid); p_waitstat(&stat); ret=stat: > return(ret); > This is intended to be used in place of p_presume and behaves like p_presume except that it returns only when the resumed process has terminated. It effectively implements a synchronous version of p_logona. Wait handlers Wait handlers are functions that handle the completion of asynchronous requests from within p_iowait (or p_waitstat). Active wait handler functions are called just before p_iowait would have otherwise returned. Many I/O devices install a device wait handler when the device is opened (see the System Services reference manual for information on writing device drivers and I/O device wait handlers). An application can install any number of application wait handlers using p_svecadd (p_svecrem removes an application wait handler). Before p_iowait will call an installed wait handler, it has to be activated using p_sveccall (p_sveccal lt can also be used to deactivate a wait handler). An installed wait handler is automatically deactivated when called (which stops it being called recursively since a wait handler often itself calls p_iowait, directly or indirectly). In the following example (which does not check for errors), SetupHeapChecker installs the wait handler CheckHeap which then calls p_allchk every 2 seconds or so without any cooperation from the rest of the program. typedef struct € UBYTE *Channel; WORD Status; ULONG Timeout; > HEAP_TIMER; LOCAL_C INT CheckHeapC(HEAP_TIMER *pTimer) { if (pTimer->Status==E_FILE_PENDING) return(P_SIGNAL_UNUSED ); p_allchk(0); p_ioc(pTimer->Channel ,P_FREAD ,&pTimer->Status,&pT imer->T imeout); return(P_SIGNAL_ENABLE) > GLDEF_C VOID SetupHeapChecker(VOID) € HEAP_TIMER *pHeapTimer; pHeapT imer=p_alloc(sizeof(HEAP_TIMER)); p_open(&pHeapT imer->Channel ,"TIM:",-1)> pHeapTimer->Status=0; pHeapTimer->Timeout=20; /* 2 second tick */ p_sveccal | (p_svecadd(CheckHeap, pHeapT imer) , TRUE); > Wait handlers are only called when the process calls p_iowait (or a function such as p_waitstat that calls p_iowait). While an application performs a computationally intensive task that takes an extended time, it should consider calling p_ioyietd (which effectively calls p_iosignal followed by p_iowait) to allow any installed wait handlers to be called. Application programs must never assume that no wait handlers have been installed. Installed device wait handlers and application wait handlers are represented as a doubly linked queue of data structures allocated out of the process heap. The queue is built from a 4-byte queue header in a 83 PLIB REFERENCE reserved static at address 2 in the process data segment. If this reserved static is corrupted (say because of a write using an uninitialised pointer that happens to have a low value), p_iowait will almost certainly detect an invalid wait handler and call p_panic(27) although the cause of the panic may have nothing to do with wait handlers. Polling rather than waiting In a multi-tasking operating system it is extremely anti-social to wait for an operation to complete by polling the status word in a tight loop rather than call p_iowait (because the polling will "hog" the processor to no benefit). However, when there is useful work to be done between each poll, it can be appropriate to poll - for example, to check periodically for user input while performing an extended calculation. If this approach is used, you should be aware that in some cases asynchronous requests are completed by a wait handler and it is necessary to call p_ioyietd before each poll to give any wait handlers a chance to run. This case occurs when making requests on a device driver that services hardware interrupts. For example, the serial port driver services the hardware interrupt generated by the receipt of a serial frame. A hardware interrupt handler cannot write directly to the data segment of the requesting process because that data segment may be moving when the interrupt occurs. Instead, the interrupt handler must write first to a fixed memory location (either in the operating system variables if it is an in-built driver or ina device segment if it is an external driver) and then signal the I/O semaphore of the requesting process. When the requesting process next calls p_iowait (directly or indirectly through say p_ioyield), the device driver's wait handler is called to copy the data safely to the process data segment. Device drivers that are implemented by a server process (for example, the window server) do not require a wait handler to complete operations. When a poll detects a completed status word it is still obligatory to "use up" the signal by calling p_iowait (otherwise you will get a "stray signal” later). Attached I/O devices Wait handlers are often used in attached I/O drivers that layer over an existing driver (which may be another attached driver or a hardware device driver) to extend or modify its services. The attached driver typically installs a wait handler to handle the completion of requests on the underlying driver. Although attached drivers can be written in C, the interface between the I/O system and the functions that are called requires some assembly language programming. See the J/O System chapter for more information on attached drivers. a ae a IY Sf EE Se el Primitive semaphore functions HANDLE p_semecrtCINT nCount); Create a semaphore with an initial positive count ncount. Returns the handle of the semaphore if successful, or E_GEN_NOSEM if no semaphores are available. The created semaphore is owned by the calling process and, in version 3 and later of EPOC, may be deleted using p_semdet before exiting. However, the semaphore will always be automatically deleted on termination of the process. Calls p_panic if nCount is negative. VOID p_semdel (HANDLE sHandle); Delete semaphore sHandle. Any processes waiting on the semaphore are automatically signalled. Calls p_panic if sHandle is not the handle of a previously created semaphore. Prior to version 3 of EPOC the recommended action is not to delete a semaphore, but to let the operating system perform any necessary clean-up actions on termination of the application. This is an acceptable solution for all versions of EPOC. Except in extreme cases, where large numbers of semaphores are created, there is no need for an application ever to call p_semdel. 84 8 ASYNCHRONOUS REQUESTS AND SEMAPHORES eee VOID p_wait(HANDLE sHandle); Decrement semaphore sHandle by one and return immediately if it is zero or positive. If sHandte is negative after being decremented, it waits for semaphore sHandle to be signalled by another process or by an interrupt handler (or for sHandte to be deleted). More than one process can be waiting on a particular semaphore at a time. When there are moultiple processes waiting on a semaphore, they are released on a first-in first-out basis. If a semaphore is deleted, all processes waiting on that semaphore are released. Calls p_panic if sHandle is not the handle of a previously created semaphore. VOID p_signal (HANDLE sHandle); Signal semaphore sHandle, incrementing it by one. If sHandle was less than zero, the first process waiting on it is released and a reschedule takes place (before p_signal returns). Calls p_panic if sHandle is not the handle of a previously created semaphore. VOID p_signaln(HANDLE sHandle, INT nTimes); Equivalent to calling p_signal(sHandle) nTimes times. Calls p_panic if sHandLe is not the handle of a previously created semaphore cr if ntimes is not greater than or equal to 1. VOID p_signalnrC(HANDLE sHandle); Behaves as for p_signal except that the re-schedule does not take place. In the absence of any other cause, a re-schedule will not occur until the next system tick. A re-schedule can always be forced by calling p_sleept¢OL). Calls p_panic if sHandle is not the handle of a previously created semaphore. LSE EES a Sa ee Ey The I/O semaphore Although the I/O semaphore is indeed associated with I/O operations, it is not used exclusively for I/O operations. In retrospect, a more accurate name would have been the "asynchronous request semaphore". VOID p_iosignal(VOID); Increment the process I/O semaphore. Used in wait handlers to signal the completion of an asynchronous request after writing the completion Status to the associated status word. It can also be used outside wait handlers to generate "internal events" where the call to p_iosignal necessarily precedes the call to p_iowait. VOID p_iosignalbypid(HANDLE pid); Signal the I/O semaphore of process pid. 85 PLIB REFERENCE Used to signal the completion of a request from process pid (normally a different process but it still works if it is the same process). Before calling p_iosignalbypid, the process should already have set the pid's status word using say p_pcpyto. VOID p_iowait(VOID); Wait for the I/O semaphore to be signalled (of course, it returns immediately if the I/O semaphore has already been signalled). When the I/O semaphore is signalled, any active wait handlers are called. Only when all the active wait handlers indicate that the signal has nothing to do with them (ie they all return p_SIGNAL_UNUSED) will the p_iowait call return. When it does return, the signal must be associated with an external (ie outside any wait handler) status word. The application should then poll the request status words to determine which operation has completed. VOID p_ioyield(VOID); Give an opportunity for any active wait handler to run. Equivalent to calling p_iosignal followed by a p_iowait. Any application that polls a status word for the completion of an I/O operation (presumably in between performing chunks of a computationally intensive task) should call p_ioyietd before polling to give any wait handlers (which are commonly required to complete an asynchronous request) a chance to run. VOID p_waitstat(WORD *“pstat); Wait for the particular asynchronous request associated with *pstat to complete. It is similar to p_iowait except that rather than just wait for the I/O semaphore to be signalled, it also waits until *pstat is not E_FILE_PENDING. It correctly adjusts the I/O semaphore if any other I/O requests completes in the meantime, as illustrated in the following code: GLDEF_C VOID p_waitstat(WORD *pstat) /* Wait for *pstat!=E_FILE_PENDING */ € INT i; 1=(-1); do £ P_iowait(); i++; } while (*pstat==E_FILE PENDING); while (i--) p_iosignal(); y The principle may be extended to wait for the completion of more than one asynchronous event. This is illustrated in the following code, which waits until both of two status words are not equal to E_FILE_PENDING: 86 8 ASYNCHRONOUS REQUESTS AND SEMAPHORES —_—_— SK eeSSSSSSSSSSSSSSSFSSSSSSsees GLDEF_C VOID waitstat2(WORD *pstat1,WORD *pstat2) /* Wait until both *pstat! and *pstat2 are not E_FILE_PENDING ef { INT i; i=(-1); do { p_iowait(); i++; } while ((*pstat1==E_FILE_PENDING) && (*pstat2==E_FILE_PENDING)); if (*pstat2==E_FILE_PENDING) pstatil=pstat2; P_waitstat(pstat!); while (i--) p_iosignal(); > SS a a a ee ey Wait handlers HANDLE p_svecadd(INT (*vec)(VOID *), VOID *pcb); Add function vec to the I/O semaphore wait handler list and return the non-zero handle of the wait handler if successful or zero if there is insufficient memory. The returned handle is subsequently used to activate (by calling p_sveccat) and remove the wait handler (by calling p_svecrem). Initially the wait handler is inactive. The installed wait handler is normally activated to process the completion of one or more asynchronous requests. When the wait handler is active, the wait handler function is called from within p_iowait when the I/O semaphore is signalled. In the interests of efficiency, a wait handler should only be active while there is an associated pending request. The wait handler function should poll the one or more status words associated with the one or more requests it is monitoring and return one of the following values: P_SIGNAL_DISABLE the status word was other than E_FILE_PENDING and the completion has been processed. There are no more requests to process and the wait handler can now be deactivated. P_SIGNAL_ENABLE a status word was other than E_FILE_PENDING and the completion has been processed. However, there are still pending requests (either because the wait handler is associated with more than one request or because another request was queued) and the wait handler should remain active. P_SIGNAL_UNUSED all status words contained E_FILE_PENDING and no processing took place. The wait handler remains active. In the first two cases, where the signal is consumed, p_iowait loops back and waits on the I/O semaphore again. If the wait handler does not detect the completion of an internal request and returns P_SIGNAL_UNUSED, p_iowait will call any other active wait handlers and will only return to the caller when there are no active wait handlers or when all the active wait handlers return p_S1GNAL_UNUSED. If in the processing of the completion of one request the wait handler cancels another request, the wait handler would normally use up the signal from the cancelled requests by calling p_waitstat. An active wait handler is deactivated before being called and is only reactivated when it returns with the value P_SIGNAL_ENABLE Or P_SIGNAL_UNUSED. This normally works such that any calls to p_iowait within a wait handler will not cause the same wait handler to be called recursively. Other active wait handlers may still be called within a p_iowait within the wait handler. 87 PLIB REFERENCE eee The wait handler function vec is called with pcb as its single parameter. Where re-entrant code is required, pcb would normally be an address leading to the status word (or words) associated with the requests the wait handler is interested in. In non re-entrant code, where static data is used, it may not be necessary tO use pcb. VOID p_sveccal lL (HANDLE hand, INT isactive); If isactive is TRUE activate wait handler hand (where hand was returned by p_svecadd). If isactive is FALSE deactivate wait handler hand. In the interests of efficiency, wait handlers should be deactivated when they have no pending requests to process. As well as being externally deactivated by calling p sveccal!, a wait handler can deactivate itself by returning P_SIGNAL_DISABLE. VOID p_svecrem(HANDLE hand); Remove wait handler hand (where hand was returned by p_svecadd) from the I/O semaphore wait handler list. 88 CHAPTER 9 I/O SYSTEM This chapter describes the EPOC I/O system in general and the C functions used to access I/O devices. To use a particular device you need (also) to read a description of the device driver. The files device driver and the asynchronous timer device driver are described in this manual - in the chapters Files and Time, Timers and Dates respectively. Other device drivers are described in the I/O Devices manual. The last section of this chapter describes a set of console services for constructing rudimentary user interfaces with the minimum of effort. Worthier user interfaces may be implemented using the services described in the Window Server reference manual. SSS Sr a a a ee I/O Device Drivers The principal purpose of device drivers is to provide convenient software interfaces that hide the internal details of the underlying hardware. For example, the software interface to the RS232 driver is independent of the interface to the underlying hardware. The fact that the RS232 hardware is different between SIBO machines and PCs is not apparent to the user of the RS232 driver. Many device drivers do not themselves interface to hardware but layer over one or more other device drivers that ultimately access the hardware. For example, the majority of the code in the RS232 driver is independent of the hardware interface and the driver is implemented in 2 layers - an upper hardware- independent layer and a lower hardware-dependent layer. Some device drivers do not access hardware at all - even indirectly. In such cases, the device driver mechanism is used to extend the services available to applications. For example, the C standard floating point library is implemented as a device driver. LDDs and PDDs In EPOC there are two types of device driver: = physical device drivers (PDDs) which are hardware dependent = — logical device drivers (LDDs) which are hardware independent Applications normally interface to LDDs only. An LDD may use one or more PDDs in its implementation. For example, the RS232 driver is an LDD (the upper layer) using an appropriate PDD (the lower layer) depending on the underlying hardware. PDDs are also used by the file server system process to access different types of SSDs. Some device drivers are built into the operating system where their code is in the ROM. For example, the RS232 LDD and its PDD are normally built into the operating system whereas a bar code reader device driver is normally external and has to be loaded. External device drivers External device drivers are loaded from a device driver file into a device memory segment. The device driver file has the file name extension .LDD or .PDD depending on whether it is an LDD or PDD respectively. The device memory segment is allocated by the segment allocator as described in the chapter Memory Allocation. The ability to load and remove external device drivers without a system reset is a key feature of the EPOC I/O system and contrasts with most other operating systems, which require a system reset to install 89 PLIB REFERENCE a device driver. On a SIBO machine you can physically attach a peripheral device (for example, a bar code wand) and load a device driver without having to exit any application processes. The I/O system also notifies devices when the machine switches off and on so that the driver can take appropriate device specific action before losing power and to recover when the power is resumed. The interface between the operating system and an LDD does not conform to a C calling convention. An LDD may be written in C but some 8086 assembly language is required to provide the LDD interface. See the System Services reference manual for more about device drivers. Opening a channel to a device The I/O device driver functions are accessed by opening a channel to the device by calling p_open and passing it a name consisting of a 3 character device name and a terminating colon. Examples of device names are: FIL: for opening a file PAR: for opening a parallel port TTY: for opening an RS232 port TIM: for opening an asynchronous timer Note that device names of external LDDs bear no relation to the file name from which they were loaded. Depending on the nature of the device, the ':' may be followed by further text. Where the device driver supports more than one unit, the device name may be followed by a unit letter. For example, "tTTy:a" is used to open a channel to serial port A and "Par:B" is used to open a channel to parallel port B. In the file system device FIL:, the qualifying text is normally a file name or full path name. Because the file system has more p_open calls made to it than any other device, the p_open function effectively inserts a"FIL:" before a device name if it fails to recognise a valid device name - making the leading "FIL:" optional. For example, calling p_open with the name "c:\NOTES\NEW.TXT" has the same effect as the name "FIL:C:\NOTES\NEW. TXT". Keeping the FIL: prefix removes any possibility of mistaking the file specification for a device name - for example to open a file on the default directory with file name "TTY Ss" The p_open function works such that a loaded device driver supersedes any existing device of the same device name. Operations on an open I/O channel Once a channel has been opened on a device, the primitive p_ioa function is used (directly or indirectly) to make an asynchronous J/O function request on the channel. Asynchronous requests are described in the chapter Asynchronous Requests and Semaphores. All I/O function requests are asynchronous in principle and the process I/O semaphore is always signalled as a result of an I/O function request. In practice, many I/O functions are implemented synchronously, which means that the I/O operation will have completed before p_ioa returns. A typical I/O device will provide zero, one, two or three truly asynchronous functions (where the request will probably not have completed before p_ioa returns) with the remaining functions provided synchronously. For example, the I/O function that closes an I/O channel is always implemented synchronously, whereas the I/O function that reads input from a device is commonly implemented asynchronously. In practice, p_ioa is often called indirectly by: p_ioc which makes an asynchronous I/O request in a way that simplifies the handling of error returns - this should generally be used in preference to p_ioa p_iow which makes the I/O request and then waits for the request to complete (by calling p_waitstat) The particular I/O function requested by a call to p_ioa, p_ioc or p_iow is specified by a function number parameter of the form P_Fxxx, defined in p_file.h. Some I/O functions are specific to a particular device (for example P_FseTEoF, which sets the end of file position on an open file channel). Some I/O functions apply to more than one device, including: P_FREAD to read data from a channel P_FWRITE to write data to a channel P_FCLOSE to close a channel P_FCANCEL to cancel outstanding asynchronous requests on a channel 90 9 V/O SYSTEM —_—. eee P_FSENSE to sense channel characteristics P_FSET to set channel characteristics P_FFLUSH to flush out data held in buffers This manual uses the notation p_ioa(P_fxxx), p_ioc(P_Fxxx) and especially p_iow(P_Fxxx) to refer to the corresponding function call with P_Fxxx passed as the I/O function number. (in the descriptions of p_ioa, p_ioc and p_iow later in this chapter the I/O function number has the parameter name func.) The most commonly used I/O functions are supported by their own synchronous convenience functions as follows: p_close which calls p_iow(P_FCLOSE) p_read which calls p_fow(P_FREAD) p_write which calls p_iow(P_FWRITE) p_seek which calls p_iow(P_FSEEK) The functions p_close, p_read and p_write are used across many devices (p_close is applicable to all devices) and are described in this chapter. The function p_seek applies to an open file channel only and is described in the Files chapter. The file server The file server is a high priority system process (with process name SYS$FSRV) that performs all file related operations. These include those that are accessed using the I/O channel services (the FIL: device) and those that do not require a channel to be opened (eg deleting a file, making a directory). The file server is also responsible for loading executables (see the chapter Processes and Inter-Process Messages) and for loading external device drivers (described in this chapter). The file server serialises access to shared file storage devices (eg SSD drives on a SIBO machine) which may be local or remote. On SIBO machines, the file server uses PDDs extensively to access the many different types of SSD (different configurations of FLASH, RAM, ROM). The file server also uses an installable PDD to connect to remote devices via a remote file server. The file server is a process rather than an LDD because, in a multi-tasking system, more than one process can be accessing a particular file storage device at a time (at a lower level only the file server owns an SSD drive where it performs all operations on the SSD on behalf of requesting processes). Application processes that use the file server are called "clients". To become a client of the file server, a process must connect to it. Because most applications need the file server, the normal code that precedes main connects to the file server. The client processes send the file server an inter-process message to request a file server service. If the service accesses local devices, it is implemented synchronously since the file server runs at a higher priority than any application process. If the service accesses remote devices, it may be implemented asynchronously. The application programmer does not program at the message passing level but uses the interfaces provided by the FIL: device and a number of ROM resident functions (most of which are described in the Files chapter). Whether provided by a function or via the FIL: device, all access to the file server ultimately involves the sending of an appropriate inter-process message. Attached drivers An attached driver is an LDD using the services of an another LDD to provide a different set of services to its user. Some of the devices described in the J/O Devices manual are attached drivers. For example, the pro: device is an attached driver, layered over a suitable print output device to provide primitive printer driver services (involving translates, preambles and postambles as driven from a .PRD file). In this case a suitable print output device is any device that supports the normal p_FWRITE operation as provided by the par:, TTY: and FIL: devices. The user of the pro: device does the following: = open the print output device using p_open (eg PAR: OF TTY:) ® perform any initialisation on the channel (eg to set the Baud rate on a TTY: channel) = open the pro: device using p_open, attaching it to the opened print output device Once the pro: device has been opened, it replaces the P_FWRITE, P_FCANCEL and P_FCLOSE functions of the underlying device. 91 PLIB REFERENCE In general, an attached driver may completely replace the original driver's functions or it may augment them (possibly also replacing or disabling some functions). It may also allow some functions through to the underlying driver or beyond. Like any other LDD, an attached driver may be written in C but some assembly language code is required to provide the LDD vector interface. See the System Services reference manual for more about writing attached device drivers. Attached drivers that provide one or more of their services asynchronously make internal asynchronous requests on the device they are attached to. Where this is the case, the attached driver processes the completion of its internal requests in code that is entered via its wait handler vector. The attached device installs its device wait handler when the channel is opened and the wait handler vector is subsequently called from within the p_iowait of the process which opened the device whenever the process I/O semaphore is signalled. Except for the way in which they are called, device wait handlers are the same as application wait handlers as described in the chapter Asynchronous Requests and Semaphores. SSS eS nn i ay Channel-based I/O functions INT p_open(VOID **ppfcb, TEXT *name, UINT mode); INT f_open(VOID **ppfcb, TEXT *name, UINT mode); Open a channel to the device with the zero terminated device name name or attach driver name to an existing channel. The parameter name consists of a 3 character device name terminated by a ':' and optionally followed by further data, depending on the device. The interpretation of the mode parameter depends upon the device and some devices ignore mode. When a device ignores mode, the caller should pass a mode of -1. The files device (device name "FIL:") and the asynchronous timer device (device name "TIM:") are described in the chapters Files and Time, Timers and Dates in this manual. These devices are also described in the I/O Devices manual along with many other devices. If the device name is not an attached device and it is opened successfully, the address of the channel control block is written to *ppfcb. If the open fails, *ppfcb is not written to - setting *ppfcb to zero before calling p_open can simplify clean-up code since p_close(0) has no effect and p_close can subsequently be called regardless of whether the open call was successful. If device name is an attached device, the driver control block is attached to *ppfcb - the value in *ppfcb is not changed. Returns zero if successful or a negative error number if it failed. As well as device specific errors (as described in the description of each device) the following error numbers may be returned: E_FILE_ALLOC failed to allocate memory for the control block E_FILE_DEVICE the device does not exist E_GEN_ARG the value of the mode parameter was invalid (possibly as a result of falling through to the FIL: device, as described next) The function f_open is identical to p_open except that it calls p_teave (passing the error number) rather than return a negative error number. If p_open fails to locate a device that matches name, then name is passed to the FIL: device driver (in which case the process must be connected to the file server). To put it another way, the leading F1L: device name is optional when opening files and, for example, calling p_open with a name Of "LOC: :A:\DEF.EXT" is equivalent to a name of "FIL:LOC::A:\DEF.EXT" (the FIL: device is described in more detail in the Files chapter). This behaviour has an undesirable side effect: an attempt to open a device that does not exist does not give the expected E_FILE_DEVICE error since name is passed on to the FIL: device with a result that depends upon mode (with some values of mode the open might even be successful). Passing a mode of -1 is guaranteed to cause the FIL: device driver open to fail - albeit with a misleading error number (E_GEN_ARG). 92 9 VO SYSTEM —_ eee INT p_ioa(VOID *pcb, INT func, WORD *pstat, ...); INT p_ioa3(VOID *pcb, INT func, WORD *pstat); INT p_ioa4(VOID *pcb, INT func, WORD *pstat, VOID *a1); INT p_ioa5(VOID *pcb, INT func, WORD *pstat, VOID *a1, VOID *a2); Start the I/O operation func with zero, one or two parameters on the opened channel peb and return without waiting for the operation to complete. You can either use p_ioa, which presents the cpEct calling convention, or one of the p_ioa? variants, which uses a more efficient register calling convention. Note that it is almost always preferable to use p_ioc in preference to p_ioa. The legitimate values of func depends upon the device. The number of function parameters (zero to two) and the interpretation of the function parameters (if any) depends on the function and the device. If a function parameter exists, it is normally an address that may be used as input, output or both input and output. The function returns zero if the I/O request was started successfully or a negative error number. Returns E_FILE_INV if func is not valid for this device. Other device specific errors may be returned. When the successfully started operation completes, the process I/O semaphore is signalled and *pstat contains the completion status. While the operation is pending (ie before the I/O semaphore has been signalled), *pstat contains E_FILE_PENDING. Asynchronous requests in general are described in the chapter Asynchronous Requests and Semaphores. After the operation has completed, *pstat contains zero if the operation completed successfully or a negative error number if the operation completed with an error. The possible error numbers depend upon the device but any asynchronous operation that is successfully cancelled completes with *pstat containing E_FILE_CANCEL. Drivers normally only support one pending request per I/O operation per channel. For example, on the TTY: (serial port) device you must wait for an asynchronous write request to complete before you can make another write request on the same channel (the driver will call p_panic if this is attempted). However, it is legitimate to have one read request and one write request simultaneously pending. The storage pointed to by pstat must be retained for the duration of the operation. A common error, which has disastrous results, is to allocate the status word on the stack and then to return from the function before the operation has completed - as in the following example: GLDEF_C INT DisastrousWrite(VOID *peb, TEXT *buf) € WORD stat; UWORD len; len=p_slen(buf); return(p_ioa(peb,P_FWRITE,&stat,buf,&len)); 3 The following generic I/O functions (where a1 and a2 are further parameters to p_ioa) are commonly (but not always) implemented asynchronously by device drivers: P_FREAD request a read operation where ai is the address of the buffer to take the data and a2 is the address of a worp length to read (or the maximum length to read if the device is record oriented). When the read completes, *pstat contains zero if the read was successful or a negative error number if the read failed and *a2 contains the number of bytes written to a1. P_FWRITE request a write operation where a1 is the address of the data to write and a2 is the address of a word length to write. When the write completes, *pstat contains zero if the write was successful or a negative error number if the write failed. 93 PLIB REFERENCE VOID p_ioc(VOID “pcb, INT func, WORD *pstat, ...); VOID p_ioc3(VOID *pcb, INT func, WORD *pstat); VOID p_ioc4(VOID *pcb, INT func, WORD *pstat, VOID *at); VOID p_ioc5(VOID *pcb, INT func, WORD *pstat, VOID *al, VOID *a2); Behaves as for p_ioa except that if the I/O request func fails to start, the failure is reported as if it had started successfully but completed with that error. You can either use p_ioc, which presents the cpEcL calling convention, or one of the p_ioc? variants, which uses a more efficient register calling convention. The implementation of p_ioc is effectively as follows: GLDEF_C VOID p_ioc(VOID *pcb, INT func,WORD *pstat,VOID *a1,VOID *a2) € INT ret; if (ret=p_ioa(pcb, func, pstat,a1,a2)) € *pstat=ret; p_iosignal(); > > In most cases, p_ioc is preferred to p_ioa because there is only one place (*pstat) to check for an error rather than two (the return from p_ica and *pstat). It is rarely necessary to differentiate between a failure to start the I/O operation and a failure in its completion. In the following example, WriteTimeout writes the Len bytes at buf to channel pcb and returns as for p_write. However, if the write does not complete within secs seconds, the write operation is cancelled and E_FILE_CANCEL is returned. For simplicity, it assumes that completion of the write or expiry of the timer are the only two events that are expected to cause a return from the call to p_iowait. This will be true if there is no other asynchronous activity. LOCAL_C INT WriteTimeout(VOID *pcb, UBYTE *buf, UWORD len, UINT secs) € WORD tstat; WORD wstat; ULONG tval; p_ioc(pcb,P_FWRITE,&wstat,buf,&len); tval=10L*secs; p_ioc(tcb,P_FRELATIVE,&tstat,&tval); p_iowait(); Tf (tstat!=£_FILE_PENDING) { /* the timer expired */ p_iow(pcb,P_FCANCEL); /* cancel write */ p_waitstat(&wstat): > else if (wstat!=E_FILE_ PENDING) { /* the write completed */ p_iow(tcb,P_FCANCEL); /* cancel timer */ p_waitstat(&tstat); > else P_panic(254); /* unexpected, unrecognised signal */ return(wstat); > The call to p_iowait returns when either request completes. If the timer has completed, the write request is cancelled. Otherwise, the timer request is cancelled. If both requests have completed by the time p_iowait returns (quite possible if a higher priority process "hogged" the CPU), the cancel of the write request will have no effect and wstat will contain the completion code of the write. In the example, the static variable tcb is the channel of a previously opened asynchronous timer, as in: p_open(&tcb, "TIM:",-1): 94 9 VO SYSTEM —_ eee The following version of writeTimeout is more general, and caters for the presence of other asynchronous activity. LOCAL_C INT WriteTimeout(VOID *“peb, UBYTE “buf, UWORD len, UINT secs) € WORD tstat; WORD wstat; WORD count; ULONG tval; p_ioc(pceb,P_FWRITE, &wstat, buf ,&len); tval=10L*secs; p_ioc(tcb,P_FRELATIVE,&tstat,&tval); count=0; FOREVER € p_iowait(); if (tstat!=E_FILE_PENDING) € /* the timer expired */ p_iow(pcb,P_FCANCEL); /* cancel write */ p_waitstat(&wstat); break; > else if (wstat!=E_FILE_PENDING) { /* the write completed */ p_tow(tcb,P_FCANCEL); /* cancel timer */ p_waitstat(&tstat); break; > else countt=1; /* count unrecognised signals */ > while (count--) p_iosignal(); /* replace unrecognised signals */ return(wstat); > In such a situation, with other asynchronous activity, it may be better to handle the expiry of the timer in the main p_iowait loop. The wait for the write to complete could then be handled by: p_waitstat(&wstat); p_iow(tcb,P_FCANCEL); /* cancel timer */ p_waitstat(&tstat); iO op INT p_iow(VOID *peb, INT func, ...); INT p_iow2(VOID *pcb, INT func); INT p_iow3(VOID *pcb, INT func, VOID *a1); INT p_iow4(VOID *pcb, INT func, VOID *a1, VOID *a2); Behaves as for p_ioa except that it waits for operation func to complete and returns the completion status, providing a synchronous (as opposed to an asynchronous) interface. You can either use p_iow, which presents the cpect calling convention, or one of the p_iow? variants, which uses a more efficient register calling convention. 95 PLIB REFERENCE The code for p_fow is effectively: GLDEF_C INT p_jow(VOID *pcb, INT func,VOID *a1,VOID *a2) € WORD stat; INT ret; if (!(ret=p_ioa(peb, func, &stat,a1,a2))) € p_waitstat(&stat); ret=stat; > return(ret); > Calling p_iow is simpler and requires one less parameter (since the status word is returned) than p_ioa or p_ioc and should be used in preference to p_ioa or p_ioc unless there is a need for an asynchronous request. The following generic I/O functions are essentially synchronous and are normally requested using p_iow: P_FCANCEL to cancel outstanding requests on a channel P_FSENSE to sense channel characteristics P_FSET to set channel characteristics P_FFLUSH to flush out data held in buffers INT p_close(VOID *pcb); Close 1/O channel peb and return zero if successful. If pcb is NULL, just return zero. The code for p_close is effectively: GLDEF_C INT p_close(VOID *pcb) a if (!peb) return(0); return(p_iow(peb,P_FCLOSE)); > Although p_ctose can return an error, it will always succeed in closing the channel (and pcb should not be used subsequently). If the device buffers written data, the close operation may need to perform one or more write operations on closing, in which case P_FCLOSE can return similar errors to p_FwRITE. However, the failure to flush the data will not (assuming a competently written device driver) cause the close operation to be aborted although the failure to flush will be reported. Carefully written applications avoid this problem by using P_FFLUSH to flush the data (and taking appropriate action if this fails) before closing the channel without risk of failure. INT p_read(VOID *peb, VOID *buf, UINT len); INT f_read(VOID “pcb, VOID *buf, UINT len); Request a P_FREAD of up to Len bytes of data into buf from channel pcb, wait for the request to complete and, if successful, return the number of bytes written to buf or a negative error number if the read failed. 96 9 VO SYSTEM a er ee The code for p_read is effectively: GLDEF_C INT p_read(VOID “pcb, UBYTE *buf, UINT len) { INT ret; UWORD |; l=len; ret=p_iow(pcb,P_FREAD, buf ,&l); if (!ret) ret=l; return(ret); > The function f_read is identical to p_read except that, if there is an error, it calls p teave(err) rather than return the negative error number err. INT p_write(VOID *pcb, VOID *buf, UINT len); INT f_write(VOID *pcb, VOID *buf, UINT len); Request a P_FWRITE of len bytes of data from buf to channel pcb, wait for the request to complete and, if successful, return zero or a negative error number if the write failed. The code for p_write is effectively: GLDEF_C INT p_writeCVOID *peb, UBYTE *buf, UINT Len) € UWORD L; l=Llen; return(p_jow(peb, P_FWRITE,buf,&l)); > The function f_write is identical to p_write except that, if there is an error, it calls p_ leaveCerr) rather than return the negative error number err. INT p_iow(VOID *pcb, P_FCANCEL); Cancel any outstanding asynchronous requests on channel peb and return zero. Harmless if there are no pending requests. Device drivers that support truly asynchronous services provide a cancel service. The detailed effect of the cancel depends upon the device driver. However, the following general principles apply: = the cancel precipitates the completion of the request (it does not stop the request from completing) = the cancel may or not be effective (that is, the request may complete naturally before the cancel is processed) = after a cancel, you must still process the completion of the asynchronous request (typically by immediately calling p_waitstat to "use up” the signal) The above principles actually apply to cancelling any asynchronous request (not just an asynchronous I/O request). Although legitimate, using p_ioa¢P_FCANCEL) Or p_ioc(P_FCANCEL) (rather than p_iow(P_FCANCEL)) is somewhat perverse since you then have two signals to use up - one for the request being cancelled and one for the cancel itself. 97 PLIB REFERENCE E> SSS eee ee ee et 1 ee ee Se ee) Device driver functions sjoadidd = 2—————C(<‘(RNN.. Load a logical device driver INT p_loadldd¢TEXT *pName); Load the logical device driver (LDD) from the zero terminated file name pName. If pName contains no extension an extension of .LDD is assumed (an LDD file would normally have the extension .LDD). If pName is not a full path name the current path is assumed. Returns zero if successful or one of the following negative error numbers: E_FILE_EXIST an LDD of the same file name has already been loaded E_FILE_NXIST the LDD file does not exist E_GEN_IMAGE the LDD file does not have the correct format or has been corrupted E_GEN_NOMEMORY Not enough memory to satisfy the request E_GEN_NOSEGMENTS No memory segment handles are available After the LDD has been loaded, a channel may be opened to it by calling p_open. Applications that rely on an external LDD should use p_toadidd and not care if it fails with E_FILE_EXIST. cadpdd INT p_loadpdd( TEXT *pName); Load the physical device driver (PDD) from the zero terminated file name pName. If pName contains no extension an extension of .PDD is assumed (an PDD file would normally have the extension .PDD). If pName is not a full path name the current path is assumed. Returns zero if successful or any of the same negative error numbers as for p_loadldd, above. INT p_devdel (TEXT *pName, INT devType); Delete the logical (devType is E_LDD) or physical (devType is E_PDD) device driver with the zero terminated device name pName (as used in p_open but without a trailing ':'). Only device drivers that have been loaded into RAM (and not those devices that are built into the ROM) can be deleted by this service. Returns zero if successful or one of the following negative error numbers: E_FILE_DEVICE The device driver is not currently loaded. E_GEN_NSUP The device driver is a ROM device driver and cannot be deleted. E_GEN_INUSE The device driver is currently open and cannot be deleted. A device dependent Returned by the device driver code. error number If you have loaded an external device driver and finished with it, it is good practice to attempt to delete it by calling p_devdel and to ignore the return value. The call is harmless if the device is loaded by another process or if the device is built into the ROM. WARNING: If pName points to a null string, the first located unloadable device driver of the type specified by devType will be deleted. pdevqu = Query the number of units supported by a device INT p_devqu(TEXT *pName); Returns the number of units supported by logical device driver with the zero terminated device name pName (as used in p_open but without a trailing ':'). The function is not applicable to PDDs. 98 9 YO SYSTEM Returns a positive number if successful or one of the following negative error numbers: E_GEN_FAIL Unlimited units (as returned by the FIL: device driver as it can support a large number of files). E_FILE_DEVICE Device driver not found. For example: p_devqu("TTY"); returns 2 if there are two serial expansion boards fitted. HANDLE p_devfnd(HANDLE fHandle, TEXT *pMatch, INT devType, TEXT *pName); Write the next device name (as used in p_open but without a trailing ':') of type devType (either E_ppp to find PDD devices or E_LoD to find LDD devices) that matches the zero terminated match string pMatch as a zero terminated string to pName where fHandle is zero for the first call and is subsequently the positive return value from the previous call. When there are no further devices of type devtype that match pMatch, p_devfnd returns E_FILE_DEVICE. Used repeatedly to find all the PDD or LDD devices that match the wild card string pointed to by pMatch. The buffer at pName should be big enough to receive &_MAX_NAME+2 bytes. The wild card string patch should remain the same between successive calls. No memory is used by this service and it can be abandoned at any time without taking any further action. Calls p_panic if fHandle is invalid. Example LOCAL_C VOID PrintDevices(VOID) € HANDLE fh; TEXT bbfE_MAX_NAME+2]; fh=0; FOREVER { fh=p_devfnd(fh,"*",E_LDD,&bb{0}); if (fh<0) break; p_puts(&bb{0] ); > > EEE eee ee a a) Simple console |/O The simple console functions are provided for "quick and dirty" applications - for example test programs and software tools. They are not suitable for constructing quality user interfaces. The console functions use the services of the con: device driver to implement the following screen output and keyboard input functions: p_putch to write a character to the screen p_puts to write a line of text to the screen and move to the beginning of the next line p_printf to convert numbers to printable form and write them as a line of text to the screen and then move to the beginning of the next line P_print to convert numbers to printable form and write them to the screen p_getch to get (without echo) a single character from the keyboard p_gets to input (with simple backspace editing) a line of text from the keyboard 99 PLIB REFERENCE p_getl to display a prompt and then input (with simple backspace editing) text from the keyboard Redirecting console writes These functions automatically open con: when they are first used. The open channel is stored in the global static: GLREF_D VOID *winHandle; which is initialised to NULL. You can open a suitable alternative device (eg a file or TTY:) before the first usage of a console function to redirect the output functions p_putch, p_print and p printf. For example, to redirect to the file a./is in the current path, use: p_open(&winHandle,"'a.lis",P_FREPLACE |P_FUPDATE); Note that if you do redirect the console to such a device, you should not use p_getch, p_gets or p_ gett. Changing the size of the console window If you want to change the size of the console window, you can do this by declaring the global p_REcT Structure DefScreenRect and initialising it to the required size before the first usage of a console function. The P_RECT struct is defined in p_graf.h as: typedef struct { WORD x; /* Horizontal coordinate */ WORD y; /* Vertical coordinate */ > P_POINT; typedef struct { P_POINT tl; /* Top left point */ P_POINT br; /* Bottom right point */ } P_RECT; where the top left coordinates should be (0,0) and the bottom right coordinates should reflect the required dimensions in character columns and rows as in, for example: GLDEF_D P_RECT _DefScreenRect=(0,0,10,20}; for 10 columns by 20 rows, or: GLDEF_D P_RECT _DefScreenRect; _DefScreenRect.tl.x=0; _DefScreenRect.tl.y=0; _DefScreenRect.br.x=columns; _DefScreenRect.br.y=rows; Changing the console window mode By default the console starts up in the native mode of the machine. At the time of writing, all SIBO machines default to single pixel (non-compatibility) mode, with no access to grey. If you want to change the mode, you can do this by declaring the global variable _DefscreenMode and initialising it to the required value before the first usage of a console function. The possible modes are: 0 use the native mode of the machine - this is the default value 1 compatibility mode, allowing Series 3 software to run on the Series 3a 2 non-compatibility mode, with grey enabled 3 compatibility mode, but with grey enabled Thus, compatibility mode may be set on the Series 3a as follows: GLDEF_D INT _DefScreenMode; _DefScreenMode=1; Values that are not relevant to a particular type of machine are simply ignored. 100 VOID p_putch(UINT c); Write character c to the console, opening the console if necessary. VOID p_puts(TEXT *str); Write the zero terminated string str to the console and start a new line. The console is opened if necessary. VOID p_printf(TEXT *fstr, ...); Converts multiple arguments to an internal buffer under control of the format string fstr, writes the complete string to the console screen and then moves the print position to the beginning of the next line. The console is opened if necessary. The internal buffer is p_Maxsys1o (258) bytes long and the length of the output (which depends on the arguments) must be limited to P_MAXSYS10-2 (256) bytes per call of p_printf. The format of fstr is exactly the same as for p_atob, which is described in the chapter Integer Conversion and Rectangle Functions. VOID p_print(TEXT *fstr, ...); Behaves as for p_printf above except that the print position is not automatically moved to the beginning of the next line after the write. You can embed \r and \n characters in fstr to move to the beginning of the line and to move down a line respectively. INT p_getch(VOID); Wait for a key to be pressed and return its character code. The console is opened if necessary. INT p_gets(TEXT *str); Input (with simple backspace editing) a line of up to P_MAXSYSIO-1 characters from the keyboard, creating a Zero terminated string at str. The input is terminated by the user pressing the Enter key. Returns the length of the string at str. There should be at least P_MAxSysio bytes at str. The console is opened if necessary. INT p_getl(TEXT *pmt, TEXT *str, INT len); Write the zero terminated string pmt to the console and input (with simple backspace editing) up to len characters from the keyboard, creating a zero terminated string at str. The input is terminated by the user pressing the Enter key. Returns the length of the string at str. There should be at least Len+1 bytes at str. The console is opened if necessary. 101 CHAPTER 10 TIME, TIMERS AND DATES SS eS een ee eee eee System time The system time is set and sensed as a ULONG, counting the number of seconds since 00:00:00, January 1, 1970 (compatible with UNIX system time). The system time overflows approximately 136 years after 1970 (which is sometime in the year 2106). pL ULONG p_date(VOID) Return the system time as the number of seconds since 00:00:00, January 1, 1970. VOID p_sdate(ULONG newTime); Set the system time to newTime (the number of seconds since 00:00:00, January 1, 1970). Note that setting the system time forward by an interval will cause any absolute timers due in that interval to expire. SS eee ae eee Absolute and relative timers In the EPOC operating system a process can be waiting on a timer in two ways: = The process is in the time delta queue as a result of calling p_sleep, p_sleept Or p_sleepa. = The process is waiting for its I/O semaphore to be signalled by a timer device entry in the time delta queue after making an asynchronous timer request by calling p_joc(P_FRELATIVE) or p_ioc(P_FABSOLUTE) on an open timer channel. Each entry in the time delta queue contains the signed long delta time in system ticks relative to its predecessor while the head of the queue contains the delta time relative the current system time (for more on delta queues see the chapter Characters, Strings, Buffers and Queues). The head of the time delta queue is decremented every system tick and the corresponding timer expires (and is removed from the queue) when its delta time is zero or negative. If the entry is a process, the process will either run (if it has the highest priority) or it will wait in the ready queue. If the entry is a timer device entry, the associated process I/O semaphore is signalled. See the chapter Asynchronous Requests and Semaphores for information on the I/O semaphore, asynchronous requests and the ready queue. As well as being marked as processes or timer device entries, entries in the time delta queue are also marked as being absolute or relative. 103 PLIB REFERENCE Absolute timers An absolute timer is characterised by the following: = the timer expiry is set in terms of an absolute time (the number of seconds since 00:00:00, January 1, 1970) s SIBO machines (as opposed to a PC running EPOC) that have switched off will automatically switch on when an absolute timer is due to expire = setting the system time forward by an interval will cause any absolute timers due in that interval to expire Absolute timers are used to implement, for example, alarms for the Diary and Alarms applications on the MC GI machines. You can set absolute timers to expire after a relative time interval (expressed in seconds) by adding the required interval to the time returned by p_date. Although absolute timers are converted to relative signed long delta time in the time delta queue, absolute timer entries can re-launch themselves to cover ranges in excess of Ox7fffffff ticks. Relative timers A relative timer is characterised by the following: = the timer expiry is set as a time interval relative to the current system time (either as a number of 1/10ths of a second or as a number of system ticks) # the timer stops running while SIBO machines are switched off and it follows that relative timers do not wake up the operating system up ® changing the system time has no effect on relative timers If a relative timer is set to expire after 5 seconds when the system time is advanced by 1 hour the timer still expires after 5 seconds (provided the machine is not switched off). If a relative timer is due to expire in 5 seconds when the machine switches off for 1 hour, the relative timer actually expires after 1 hour plus 5 seconds. SIBO machines are normally configured to switch off automatically after a period of inactivity (typically 5 minutes). A process that repeatedly waits on a relative timer with an interval shorter than the switch off inactivity period (for example to implement a clock or a flashing cursor) would by default stop the machine from ever switching off. Keeping a battery powered machine on indefinitely is normally undesirable and, in this situation, the application should call p_unmarka (described in the chapter Processes and Inter-Process Messaging) to stop such activity from keeping the machine on. Relative timers are ultimately converted to system ticks in a signed long - giving them a range of Ox7fffffff ticks, or approximately 2.1 years, on a SIBO machine (which ticks 32 times a second). INT p_sleep(ULONG n); Return zero after n 1/10ths of a second has elapsed. Returns E_GEN_OVER immediately if the conversion from 1/10ths to ticks overflows (ie the number of ticks is greater than Ox7fffffff). This function uses a relative timer. If the machine switches off before the function returns, it will take indefinitely longer than n tenths of a second to return. INT p_sleept(LONG nTicks); Returns zero after nTicks system ticks if successful or E_GEN_ARG if nTicks is negative. On the SIBO hardware the system ticks 32 times a second. On IBM PCs and compatibles the system ticks 18.2 times a second. The constant E_TICKS_PER_SECOND in epoc.h contains the number of ticks per second, to the nearest integer. A request to sleep for zero ticks is valid and will just force a re-schedule, with the process calling this service losing the remainder of its time slice if there are other processes at the same priority. 104 10 TIME, TIMERS AND DATES This function uses a relative timer. If the machine switches off while the process is suspended, it will take indefinitely longer than nticks system ticks to return. In the absence of any switching off, the actual time the process is suspended is greater than or equal to nticks and less than nTicks+1. INT p_sleepa(ULONG time); Return when the system time is time (the number of seconds since 00:00:00, January 1, 1970). Returns zero if successful or E_GEN_ARG if time is earlier than the current system time. This uses an absolute timer which will wake the machine up if necessary when the timer expires. Another process setting the system time past the expiry time (using p_sdate) will cause p_sleepa to return. SS ES Se ee ee Asynchronous timers The functions p_sleepa, p_sleep and p_sleept are synchronous functions in the sense that they return only when the requested operation (in this case the expiry of a timer) has completed. With asynchronous timers, the function call to start the timer returns immediately. This allows other processing to take place before waiting for any one of a number of events (including the expiry of the timer) by calling p_iowait. See the chapter Asynchronous Requests and Semaphores for an explanation of asynchronous requests in general. In EPOC, asynchronous timers are implemented as an I/O device with the device name "TIM:". To use an asynchronous timer, you open a channel to "TIM:" and use: p_ioc(P_FRELATIVE) to start a relative timer p_ioc(P_FABSOLUTE) to start an absolute timer p_iow(P_FCANCEL) to cancel a timer p_close to close a timer channel The constants P_FRELATIVE, P_FABSOLUTE and P_FCANCEL are defined in p_file.h. See the chapter 1/O System for a description of the I/O system in general. If you want to run multiple timers in parallel, you open a channel for each timer needed. Although you can open a timer channel and use p_iow(P_FRELATIVE) OF p_iow(P_FABSOLUTE) (rather than p_ioc) to the same effect as p_sleep or p_sleepa respectively, the latter should be preferred for their simplicity and efficiency. INT p_open(VOID **pptcb,"TIM:",-1)3 Open a timer channel and write the address of the timer control block to *pptcb. Returns zero if successful or the negative error number E_GEN_NOMEMoRY if, for example, it failed to allocate memory for the control block. . t @ relative timer VOID p_ioc(VOID *ptcb, P_FRELATIVE, WORD “pstat, ULONG “pn); Start a relative timer to expire after *pn tenths of a second. Calls p_panic if a timer is already pending on channel ptcb. While the timer is pending, *pstat contains £_FILE_PENDING. When the timer expires successfully, *pstat is set to zero and the I/O semaphore is signalled. The timer is not started and *pstat is set to E_GEN_ovER if the conversion from tenths of a second to ticks overflows (ie the number of ticks is greater than Ox7fffffff). 105 PLIB REFERENCE ey Start an absolute timer VOID p_ioc(VOID *ptcb, P_FABSOLUTE, WORD *pstat, ULONG *ptime); Start an absolute timer to expire when the system time is *ptime (the number of seconds since 00:00:00, January 1, 1970). Calls p_panic if a timer is already pending on channel ptcb. While the timer is pending, *pstat contains E_FILE_PENDING. When the timer successfully expires, *pstat is set to zero and the I/O semaphore is signalled. The timer is not started and *pstat is set to E_GEN_ARG if *ptime is earlier than the current system time. INT p_fow(VOID *“ptcb, P_FCANCEL); Cancel a timer on channel ptcb and return zero. The operation is harmless if no timer is pending. This is important because there is always a chance that a timer will expire before the cancel gets to it. If the cancel does get to the timer before it expires, the I/O semaphore is still signalled but *pstat is set to E_FILE_CANCEL rather than zero. However, when cancelling a timer, you don't normally care how the timer actually completed. It is common to call p waitstat immediately after the cancel to "use up" the signal. To reset a timer, you first cancel the timer using p_iow(P_FCANCEL) followed by a call to p waitstat and then call p_ioc(P_FRELATIVE) to start the timer again. Close the timer channel ptcb and return zero. You should close a timer channel when you no longer need it. ——SSS ee SSS te ee Converting between binary representations of time The system time format sacrifices range to obtain high compression and is the most convenient and efficient representation for adding and subtracting small time intervals such as seconds, minutes, hours and days. There are 86,400 seconds in a day (P_NSECDAY is defined as 86400L in p_date.h). There are three different binary representations for time in PLIB: = The number of seconds since 00:00:00, January 1, 1970 (system time format) = Days since January 1, 1900 and seconds in day = (Year since 1900,Month,Day) and (Hour, Minute, Second) Note that the first representation works from a base year of 1970 while the second two representations work from a base year of 1900. PLIB contains conversion routines that convert both ways between adjacent representations in the above list. By combining conversion functions, you can convert between the first and third representation. The second format measures the days since January 1 1900 (day =0 for Jan 1) and the seconds since 00:00:00 and is stored in a P_paysEc struct, defined in p_date.h as follows: typedef struct € ULONG day; /* day number since Jan 1 1900 */ ULONG sec; /* seconds in day, 0 to 86399 */ } P_DAYSEC; 106 10 TIME, TIMERS AND DATES The day number is useful for date calculations not involving time. Examples are to count the number of days between two dates, or to calculate a new date from a base date and a number of days (positive or negative). The third format is closest to a human understandable representation of the date and time and is defined by the p_DATE struct in p_date.h: typedef struct € UBYTE year; /* Year since 1900 (0 is 1900) */ UBYTE month; /* Month number in year, 0 to 11 */ UBYTE day; /* Day number in month, 0 to 30 */ UBYTE hour; /* Hour in day, 0 to 23 */ UBYTE minute; /* Minute in hour, 0 to 59 */ UBYTE second; /* Second in minute, 0 to 59 */ UWORD yrday; /* Day in year */ > P_DATE; The last member, yrday is a function of year, month and day. It is provided when p_pATE is generated from P_DAYSEC but is ignored when converting from P_DATE. VOID p_sttods(ULONG *pstim, P_DAYSEC *pds) Convert *pstim from system time format (the number of seconds since 00:00:00, January 1, 1970) to the number of days since 1900 and the number of seconds in the day, both being written to pds. INT p_dstost(P_DAYSEC *pds, ULONG *pstim); Convert pds from the number of days since 1900 and the number of seconds in the day to system time format (the number of seconds since 00:00:00, January 1, 1970). The result is written to *pstim. Returns zero if successful, or one of the following negative error numbers: E_GEN_OVER date in pds is too late for system time E_GEN_UNDER date in pds is too early for system time (ie before 1970) E_GEN_ARG seconds is greater than seconds in day INT p_dstodt(P_DAYSEC *pds, P_DATE *“pdt); Convert P_DAYSEC time pds (days since 1900, seconds in day) to P_DATE time pdt (year, month, day, hour, minute, second and day in year) and return zero if successful or one of the following negative error numbers: E_GEN_OVER the year is greater than 255 (year 2155) E_GEN_ARG seconds is greater than seconds in day As well as the date and time, this function calculates pdt->yrday (the day number of the year, where day zero is January 1). Although you could calculate this yourself from the year, month and day it is not a simple calculation since it involves adding the number of days in preceding months and thus depends on leap years. This function actually performs two independent calculations: = converting the number of days since 1900 to year, month, day and day in year (date calculation) = converting the number of seconds since 00:00:00 to hours, minutes and seconds (time of day calculation) Both calculations may be abandoned if either calculation fails. If you are only interested in one of the calculations, set the other member of P_DaYSEc to zero (which is a legal input for both calculations). 107 PLIB REFERENCE INT p_dttods(P_DATE *pdt, P_DAYSEC *pds); Validate the p_DATE format pdt and convert it to the P_DAYSEC format pds and return zero if successful or the negative E_GEN_ARG if the content of pdt is invalid (ie no such date or time exists). The value of pdt->yrday is ignored. The validation can fail for one or more of the following reasons: = pdt->month is outside the range 0 to 11 = —pdt->day is outside the range for the particular month taking into account leap years for February = pdt->hour is outside the range 0 to 23 = pdt->minute is outside the range 0 to 59 = pdt->second is outside the range 0 to 59 This function actually performs two independent calculations: ® converting the year, month and day to the number of days since January 1 1900 (date calculation) = converting the hours, minutes and seconds to the number of seconds since 00:00:00 (time of day calculation) Both calculations may be abandoned if either calculation fails. If you are only interested in one of the calculations, set the members of p_DATE corresponding to the other calculation to zero (zero is a legal input for all members in both calculations). INT p_dayinm(INT year, INT month); Return the number of days in month month of year year where year is the number of years since 1900 (zero is 1900) and month is O to 11 inclusive. (Returns the negative &_GEN_ARG if month is greater than 11.) The year is required because it affects the number of days in February. This may be used to test for a leap year, for example: if (p_dayinm(year, 1)==29) INT p_wkday(ULONG nDay); Returns the week day number given the number of days nDay since January 1 1900. The value returned is in the range 0 to 6, with 0 being Monday and 6 being Sunday. The day number since 1900 would normally come from a P_DAYSEC struct. INT p_weekno(ULONG nDay); Return the week number, in the range 1 to 53 inclusive, of the week containing day nday (the number of days since January 1 1900). Returns zero if successful or E_GEN_ARG if nDay exceeds the year 2155. The value returned is dependent on the value of the system E_CONFIG structure field startofwWeek (see p_getctd in this chapter). 108 10 TIME, TIMERS AND DATES ———————————SSS EE a Se ae Time and date components in text form The functions in this section get a range of date and time components in text form. They constitute a more primitive set of functions than those, described in the following section, that generate strings containing full date and/or time representations. These functions are based on language-dependent information that is built into the ROM. If you use these functions, your code should automatically work on, for example, English, French and German machines. The functions are: p_nmday to get the day names (eg Monday, Tuesday) p_nmmon to get the month names (eg January, February) p_nmdaya to get the day name abbreviations (eg Mon, Tue) p_nmmona to get the month name abbreviations (eg Jan, Feb) p_getsuffixes to get the 31 day in month number suffixes (eg st, nd) p_getampmtext to get the am and pm suffixes p_getetd to get a copy of the setable country-dependent data and time preferences (such as whether to use a 12 or 24 hour clock) The following example displays the system time in the form: Monday, 11th April 1988 11:03 and uses many of the functions described in this chapter. #include GLDEF_C VOID PrintDateTimec) { ULONG st; P_DAYSEC ds; P_DATE dt; E_CONFIG cfg TEXT DayName [32], TEXT MonthName[32], TEXT Suffix{31] [3]; st=p_date(); p_sttods(&st,&ds); p_dstodt (ads ,&dt); p_nmday(&DayName[0] ,p_wkday(ds.day)); pP_nmmon(&MonthName [0] ,dt .month)); p_getsuffixes(&Suffix{0} [0] ); p_getctd(&cfg); P_printf("%s, Auss Xs Zu KO2uze%O2u"", &DayName [0] ,dt.day+1 ,&Suf fix [dt.day] [0], &MonthName [0] ,dt.year+1900, dt.hour,cfg.timeSeparator ,dt.minute); > This example is somewhat artificial since the same action can be performed more simply by the use of p_nowtostr, described in the following section. Object-oriented programmers may prefer to use the time class in OLIB (described in the OLIB Reference manual of the SIBO SDK Object Oriented Extension) to produce textual representations of the date and time. pnmday | oS Get the day name INT p_nmday(TEXT *buf, INT daynum); Write the language dependent name of day daynum as a zero terminated string to buf where daynum should be in the range 0 to 6 inclusive and day 0 is Monday. Returns zero if successful or €_GEN_ARG if daynum is not in the range 0 to 6. 109 PLIB REFERENCE If you are working in a fixed language, you will know how long the longest day name is. If you are writing a program that has to work with different language ROMs, you can use the fact that the tool that builds the day name list for the ROM limits a particular day name to E_MAX_DAY_NAME (32), including the zero terminator. INT p_nmdaya(TEXT *buf, INT daynum); This function is only available in EPOC version 3.18 or later. Write the language dependent abbreviation for the name of day daynum as a zero terminated string to buf where daynum should be in the range 0 to 6 inclusive and day 0 is Monday. Returns zero if successful or E_GEN_ARG if daynum is not in the range 0 to 6. All day name abbreviations for a particular language are of the same length and could be one, two or three characters. In no language will day name abbreviations exceed three characters. INT p_nmmon(TEXT *buf, INT monthnum); Write the language dependent name of month monthnum as a zero terminated string to buf where monthnun should be in the range 0 to 11 inclusive and month 0 is January. Returns zero if successful or €_GEN_ARG if monthnum is not in the range 0 to 11. If you are working in a fixed language, you will know how long the longest month name is. If you are writing a program that has to work with different language ROMs, you can use the fact that the tool that builds the month name list for the ROM limits a particular month name to E_MAX_MONTH_NAME (32), including the zero terminator. INT p_nmmon(TEXT *buf, INT monthnum); This function is only available in EPOC version 3.18 or later. Write the language dependent abbreviation of the name of month monthnum as a zero terminated string to buf where monthnum should be in the range 0 to 11 inclusive and month 0 is January. Returns zero if successful or £_GEN_ARG if monthnum is not in the range 0 to 11. All month name abbreviations for a particular language are of the same length and could be one, two or three characters. In no language will month name abbreviations exceed three characters. aba VOID p_getsuffixes(TEXT *buf); Write the language dependent array of the 31 day-in-month number suffixes to buf. The suffixes are written as an array of 31 3-byte fixed length elements where each element contains 0, 1 or 2 characters followed by a zero terminator (suffixes contain at most 2 characters in any language). There must be at least 31*3 bytes of memory at buf. Each element in the array contains the suffix for the corresponding day of the month. For example, in English, the first element contains "st" and the second element contains "nd". VOID p_getampmtext(TEXT *buf, INT n); Write the language dependent am or pm time suffix as a zero terminated string to buf. If n is 0, write the am suffix. If n is 1, write the pm suffix. The am and pm suffixes are limited to 2 characters in any language. 110 10 TIME, TIMERS AND DATES VOID p_getctd(E_CONFIG *pcfg); Write a copy of the system E_CONFIG struct to pefg where the E CONFIG struct is defined, in P_config.h, as: typedef struct € UWORD countryCode; WORD gmtOffset; UBYTE UBYTE UBYTE UBYTE UBYTE UBYTE UBYTE UBYTE UBYTE UBYTE UBYTE UBYTE UBYTE UBYTE UBYTE UBYTE UBYTE UBYTE UBYTE UBYTE dateType; timeType; currencySymbol Position; currencySpaceRequired; currencyDecimalPlaces; currencyNegativelInBrackets; currencyTriadsAl lowed; thousandsSeparator; decimalSeparator; dateSeparator; timeSeparator; currencySymbol [9] ; startOfWeek; summerT ime; clockType; dayAbbreviation; monthAbbreviation; workDays; units; spare [9] ; > E_CONFIG; In the context of this chapter we are interested in the following items: gmtOffset the offset in minutes of the local system time from Greenwich Mean Time. dateType one of E_DATE_USA for MM/DD/YY, £_pATE_Europe for DD/MM/YY or E_DATE_JAPAN for YY/MM/DD. timeType either E_TIME_12 for a 12 hour clock or E_TIME_24 for a 24 hour clock. dateSeparator the character code of the date separator. For example, the character '/'. timeSeparator the character code of the time separator. For example, the character ':'. startOfWeek the day number (in the range 0 to 6 inclusive where day 0 is Monday) of the first day in the week - as used by p_weekno. This is normally either Sunday (day 6) or Monday (day 0). For example, it is normally Sunday for USA machines and Monday for UK machines. summerT ime a bit pattern indicating summer time-zones as follows: &_psT_HOME if the system time should be adjusted for summer time; E_DST_EUROPEAN if a European time- zone should be adjusted for summer time; E_DST_NORTHERN if a non-European time-zone in the Northern hemisphere should be adjusted for summer time; E_DST_SOUTHERN if a time-zone in the Southern hemisphere should be adjusted for summer time. clockType either E_ANALOGUE_CLOCK to indicate a preference for an analogue clock display OF E_DIGITAL_CLOCK to indicate a preference for a digital clock display dayAbbreviation how many leading characters to take from the day name (as returned by p_nmday) to abbreviate the day name monthAbbreviation how many leading characters to take from the month name (as returned by p_nmmon) to abbreviate the month name workDays a bit mask of 7 bits indicating (by being set) which days are to be considered work days where the least significant bit corresponds to Monday The data may be set using p_setctd - as described in the chapter General System Services. 111 PLIB REFERENCE SEES ee ee SSS eS eee Generating time and/or date strings The functions in this section construct text strings that represent the date and time in a range of formats. The functions are based on language-dependent information that is built into the ROM, in particular, the system E_CONFIG struct (see the description of p_getctd in the Language and country section of the General System Services chapter). If you use these functions, your code should automatically work on, for example, English, French and German machines. Note that the implementation of these functions uses static data. In consequence they may not be used in the code of a dynamic library (DYL). Date and time format strings The form of the output of the date and time conversion functions described below is controlled by a format string, in a similar way to the format strings used by p_printf, p_atob and p_atos. The format string consists of literal text intermixed with embedded commands. The literal text is simply copied to the output and the embedded commands are replaced by the corresponding time or date element. An embedded command is of the form %c or %*c, where c is one of the characters listed below, and * indicates that the output should be in an abbreviated form. The abbreviated form can be specified for all commands, but in some cases there is no difference between the full and abbreviated forms. The available commands are as follows: x% replaced by a single '%‘ character. Abbreviation has no effect. %: replaced by the time separator character as specified by the timeSeparator field of the system E_CONFIG struct. Abbreviation has no effect. af replaced by the date separator character as specified by the dateSeparator field of the system E_CoNFIG struct. Abbreviation has no effect. 4A depending on the supplied time of day, this is replaced by the appropriate am or pm text (as obtained by use of p_getampmtext). Abbreviation supplies just the first character of this text. Note that abbreviation may not be appropriate in some languages. D replaced by the day-in-month number, in the range 01 to 31, as two digits with a leading zero as necessary. Abbreviation suppresses any leading zero. KE replaced by the day name, as supplied by p_nmday. Abbreviation causes the name to be truncated to the number of characters specified in the dayAbbreviation element of the system E_CONFIG struct. 4H replaced by two digits in the range 00 to 23 (24-hour format) corresponding to the hours component of the supplied time of day. Abbreviation suppresses any leading zero. 41 replaced by two digits in the range 01 to 12 (12-hour format) corresponding to the hours component of the supplied time of day. Abbreviation suppresses any leading zero. 7a replaced by two digits in the range 01 to 12 corresponding to the month number for the supplied date. Abbreviation suppresses any leading zero. *N replaced by the month name for the supplied date. Abbreviation causes the name to be truncated to the number of characters specified in the monthAbbreviation element of the system E_CONFIG struct. %S replaced by two digits in the range 00 to 59 corresponding to the seconds component of the supplied time of day. Abbreviation suppresses any leading zero. “IT replaced by two digits in the range 00 to 59 corresponding to the minutes component of the supplied time of day. Abbreviation suppresses any leading zero. 112 10 TIME, TIMERS AND DATES eeeeeeFFFeeeeeeeSSsSsSssh a1 ww av replaced by two digits in the range 01 to 53 corresponding to the week number for the supplied date, as provided by p_weekno. Abbreviation suppresses any leading zero. replaced by the suffix text corresponding to the (>) day number for the supplied date. Abbreviation has no effect. replaced by four digits, in the range 1900 to 2155, corresponding to the year number for the supplied date. Abbreviation discards the first two digits (note that, for example, both 1900 and 2000 will appear as 00). replaced by three digits, in the range 001 to 366, corresponding to the day-of- year number for the supplied date. Abbreviation discards any leading zeros. replaced by the first component of a three-component (day, month and year) date, where the order of the components is determined by the value of the dateType Component of the system E_CONFIG struct. Abbreviation has no effect, but the form of the generated text is conditioned by the toggle commands described below. In the absence of any toggles, %1 is equivalent to: % if dateType is E_DATE_EUROPE %M if dateType is E_DATE_USA %*Y if dateType is E_DATE_JAPAN replaced by the second component of a three-component (day, month and year) date, where the order of the components is determined by the value of the dateType Component of the system E_CONFIG struct. Abbreviation has no effect, but the form of the generated text is conditioned by the toggle commands described below. In the absence of any toggles, %2 is equivalent to: %M if dateType is E_DATE_EUROPE %D if dateType is E_DATE_USA wm if dateType is E_DATE_JAPAN replaced by the third component of a three-component (day, month and year) date, where the order of the components is determined by the value of the dateType component of the system E_CONF1G struct. Abbreviation has no effect, but the form of the generated text is conditioned by the toggle commands described below. In the absence of any toggles, %3 is equivalent to: %Y if dateType is E_DATE_EUROPE %Y if dateType is E_DATE_USA %D if dateType is E_DATE_JAPAN replaced by the first component of a two-component (day and month) date, where the order of the components is determined by the value of the dateType component of the system E_coNFIG struct. Abbreviation has no effect, but the form of the generated text is conditioned by the toggle commands described below. In the absence of any toggles, %4 is equivalent to: %D if dateType is E_DATE_EUROPE 4M if dateType is E_DATE_USA Or E_DATE_JAPAN replaced by the second component of a two-component (day and month) date, where the order of the components is determined by the value of the dateType component of the system E_CONFIG struct. Abbreviation has no effect, but the form of the generated text is conditioned by the toggle commands described below. In the absence of any toggles, %5 is equivalent to: aM if dateType is E_DATE_EUROPE 4D if dateType is E_DATE_USA Or E_DATE_JAPAN replaced by the hour in the format determined by the value of the timeType component of the system E_CONFIG struct. Abbreviation discards any leading zero. The action of % is equivalent to: #H if timeType is E_TIME_26 %1 if timeType is E_TIME_12 replaced by the am/pm text (as for xa) if the timeType component of the system E_CONFIG struct has the value &_TIME_12, otherwise produces no output. If output is produced, abbreviation reduces the output to just the first character of the text, as for %a. Note that format strings of the form "%1%/%2%/%3" and "%4%/%5" respectively generate three- and two- component dates that automatically conform to the system configuration dateType setting, and that a 113 PLIB REFERENCE format string of the form "%6%:%T%:%s%7" generates a time that automatically conforms to the system configuration timeType setting. The commands %1, %2, %3, %4 and %5 are conditioned by the following toggles: aF produces no output, but toggles the behaviour of subsequent day items between numeric (the default) and name generation. For example, if dateType is E_DATE_EUROPE and the supplied date is 09/03/1993, the format string "%1 441 %F%1" will generate the (English) string "09 Tuesday 09". Abbreviation has no meaning, and is ignored. The items conditioned by this toggle depend on dateType as follows: %1 and %4 for E_DATE EUROPE #2 and %5 for E_DATE_USA %3 and %5 for E_DATE_JAPAN %0 produces no output, but toggles the behaviour of subsequent month items between numeric (the default) and name generation. For example, if dateType is E_DATE_EUROPE and the supplied date is 09/03/1993, the format string "%2 %0%2 %0%2" will generate the (English) string "03 March 03". Abbreviation has no meaning, and is ignored. The items conditioned by this toggle depend on dateType as follows: %2 and %5 for E_DATE EUROPE #1 and %4 for E_DATE_USA %2 and %4 for E_DATE_JAPAN 4G produces no output, but toggles the behaviour of subsequent day items between their full (the default) and their abbreviated forms. For example, if dateType is E_DATE_EUROPE and the supplied date is 09/03/1993, the format string NZF%1 %G%1 %G%1" will generate the (English) string "Tuesday Tue Tuesday”. Abbreviation has no meaning, and is ignored. The items conditioned by this toggle depend on dateType as follows: %1 and %4 for E_DATE EUROPE %2 and %5 for E_DATE_USA %3 and %5 for E_DATE_JAPAN xP produces no output, but toggles the behaviour of subsequent month items between their full (the default) and their abbreviated forms. For example, if dateType is E_DATE_EUROPE and the supplied date is 09/03/1993, the format string "%0%2 %P%2 %Px%2" will generate the (English) string "March Mar March". Abbreviation has no meaning, and is ignored. The items conditioned by this toggle depend on dateType as follows: #2 and %5 for E_DATE EUROPE %1 and % for E_DATE_USA %2 and %4 for E_DATE_JAPAN mw produces no output, but toggles the behaviour of subsequent year items between their full (the default) and their abbreviated forms. For example, if dateType is E_DATE_EUROPE and the supplied date is 09/03/1993, the format string "%3 “U%3 %U%3" will generate the string "1993 93 1993". Abbreviation has no meaning, and is ignored. The items conditioned by this toggle depend on dateType as follows: % for E_DATE EUROPE %3 for E_DATE_USA %1 for E_DATE_JAPAN %L produces no output, but toggles between the absence (the default) and the presence of a day number suffix following a day-in-month number (but not a day name) generated by %1, %2 or %3. For example, if dateType is E_DATE_EUROPE and the supplied date is 09/03/1993, the format string "%6%1 %L%1 %L%1" will generate the (English) string "9 9th 9", but "%F%G%1 %L%1 %L%1" will generate the (English) string "Tue Tue Tue". Abbreviation has no meaning, and is ignored. Function return values The Window Server animates time displays either by flashing the last time separator character in the text or, if the text contains a seconds display, by updating the time every second. 114 10 TIME, TIMERS AND DATES — All the functions described below retum a value that indicates the form of animation required. The possible return values are: -2 The display does not show seconds and contains no time separators. The display is not animated. -1 The display shows seconds. Animation updates the display every second. any other value The return value is the offset in the string to the last time separator character. Animation flashes this character. INT p_dt2str(TEXT *buf, TEXT *fstr, P_DATE *pdt); Write a zero terminated string containing formatted date and time text to buf, controlled by the zero terminated format string pointed to by fstr and the content of the P_pATE struct pointed to by pdt. The format string fstr contains literal text, embedded with commands, as described above. INT p_ds2str(TEXT *buf, TEXT *fstr,P_DAYSEC *pds); Write a zero terminated string containing formatted date and time text to buf, controlled by the zerc terminated format string pointed to by fstr and the content of the P_DAYSEC struct pointed to by pds. The format string fstr contains literal text, embedded with commands, as described above. INT p_st2str(TEXT *buf, TEXT *“fstr, ULONG “pst); Write a zero terminated string containing formatted date and time text to buf, controlled by the zero terminated format string pointed to by fstr and the system time (seconds since 00:00:00, January 1, 1970) pointed to by pst. The format string fstr contains literal text, embedded with commands, as described above. INT p_now2str(TEXT *buf, TEXT *fmt); Write a zero terminated string containing formatted date and time text to buf, controlled by the zero terminated format string pointed to by fstr and the currently set time. The format string fstr contains literal text, embedded with commands, as described above. 115 CHAPTER 11 FILES SSS ae eee ee er ee eee) Files in EPOC The file server The file server is a high priority system process (with process name SYS$FSRV) performing all file related operations on behalf of "client" processes. An application process must connect to the file server before using its services. However, this is normally taken care of by the C startup module (the code that precedes main) and supplied as standard for use with the PLIB library. (Most applications need the services of the file server and the overhead of connecting to the file server is modest.) A client process sends the file server an inter-process message to request a file server service. However, the application programmer does not program at the message passing level but uses the interface provided by the FIL: device (ie using p_open) together with the interface provided by a number of ROM resident functions (eg p_delete to delete a file) as described in this chapter. Any process that attempts to send a message to the file server without having connected is panicked with panic number 41. File systems The file server supports multiple file systems (also called nodes). In principle, the file server supports any number of file systems (p_open may be used to get a list of the file systems as described later in this chapter). At the time of writing, three file systems have been implemented: LOC:: The local filing system with devices M: (the RAM drive) and SSD drives A:, B:, ...(where the quantity depends upon the hardware). REM:: The remote filing system, available while the file server is connected to a remote file server. The structure of the filing system depends upon what the remote system is. If the remote system is a PC or another SIBO machine, the structure is the same as for Loc::. ROM:: The ROM filing system, used to access ROM-based files. This filing system is not normally visible to the user. The Rom:: file system does not support devices or directories. Within the Loc:: file system (and where rem:: is connected to a PC or another SIBO machine), the devices and directories structure is compatible with the MSDOS filing system. File systems are implemented as PDDs (Physical Device Drivers) which are normally resident in the ROM (PDDs are described in the chapter J/O System). If you use (as described in I/O System): GLDEF_C VOID PrintPDDs(VOID) € HANDLE h; TEXT bIE_MAX_NAME+2]; for (h=0;(h=p_devfnd(h,""*",E_PDD,&b[0] ))>=0;) p_printf(" %s",&b{0]); > 117 PLIB REFERENCE to get a list of PDDs, the list would include: FSY LOC the Loc:: PDD FSY .ROM the RoM:: PDD FSY REM the rEM:: PDD SSD drives SIBO machines have 2 to 4 (depending on the machine) Solid State Disk (SSD) drives, taking SSDs of various type and capacity (from 32K bytes to 8Mb and beyond). SSDs are so called because they are based on silicon memory with no moving parts. The different types of SSD (in order of decreasing unit cost) include: = static RAM with integral Lithium battery =# Flash EPROM = one-time-programmable ROM # masked ROM An SSD drive can physically and logically mount any type of SSD. The physical interface between an SSD and the drive has only 6 connectors (4 for power, and 2 for data). The 2 data connections are an instance of a high speed serial channel. This is a fundamental part of the SIBO hardware architecture, implemented in custom chips. It can be used to communicate with other peripherals. The high speed serial channel is driven synchronously by a clock which normally runs at 3.84 MHz, giving a data transfer speed comparable to the more expensive hard disks on PCs but, without any latency for the drive head to position to the required track. Writing to a Flash SSD is slower because of the time taken to program the EPROM and, in this case, the speed of writing is twice as fast as writing to a typical floppy disk on a PC. SSDs are driven by the Loc:: file system using a number of subsidiary PDDs (Physical Device Drivers) handling the different SSD types and the internal m: device. The list of PDDs produced by calling PrintPDDs (described above) would include: LOC.TYO the RAM SSD PDD Loc.TY1 the Flash SSD PDD LOC.TYM the internal RAM (m:) PDD Files are stored in a completely different way on RAM SSDs from Flash SSDs. SSD drive doors have a switch that keeps the file server informed of possible SSD removals and insertions. After an SSD drive door has been opened the file server checks each drive to see if it contains a new SSD. When the file server detects a new SSD it automatically mounts the new SSD. If a previous occupant of the SSD drive had one or more files open on it, the file server keeps a record of the SSD. When access is subsequently attempted on a file channel on that SSD, the file server uses p_notify (described in the chapter Error Handling) to request the user to replace the SSD and to retry or to abandon the channel. If the user replaces the SSD and selects retry such that the operation completes successfully, the application process is not made aware of any problem. If the user chooses to abandon the channel, the operation completes with the error €_FILE_ABORT. When this occurs, the channel is disconnected from the file and put into an abort state where the only possible operation is to close the channel (any operation other than p_close fails with the error E_FILE_ABORT). An application that receives an E_FILE_ABORT error can either make do without the file (which probably means having to exit) or to appeal yet again to the user to put the SSD back in the drive and, if the user does put the SSD back, to locate and re-open the file and recover. The latter takes more code but is kinder to the user. Unattended applications The file server will also normally call p_notify to give the user a chance to retry any file operation that fails on a mounted medium. This kind of failure is rare on SSDs but is more common on magnetic media - especially floppy disks. In some cases, the user may be able to correct the error (eg to close the door on a floppy disk drive) and successfully retry. If the user abandons, the file operation completes with an error other than E_FILE_ABORT (eg E_FILE_READ if a floppy disk read fails). 118 11 FILES es eee This scheme whereby the file server uses p_notify to give the user a chance to correct the problem and Tetry works well when the client process is an interactive application but poorly if the requesting process is designed to run unattended (eg a communications program) or is itself a server process (eg the window server trying to read a font file). Such processes can use p_setnotify(FALSE), described in the chapter Error Handling, to stop the file server from using the notify service. In this case all errors are returned directly. RAM SSDs RAM SSDs use static RAM backed up by an integral Lithium battery. While the RAM SSD is inserted in an SSD drive, it draws current from the SIBO machine. When the SSD is removed, it relies on its internal battery to maintain its data. The allocation of the space for directories and files on a RAM SSD is block structured - identical to that which is used on PC hard disks and floppy disks. Just like floppy disks on a PC, there is a limit to the number of files that may appear in the root directory (where this limit includes directory files). This root directory limit does not limit the number of files that can be stored on a RAM SSD since any number of files may be stored in subdirectories. The format function (described below in this chapter) automatically allocates the capacity of the root directory as a function of the capacity of the SSD. The dependence of the number of files that may appear in the root directory on SSD size is as follows: less than 128K 128K or greater but less than 256K 256K or greater but less than 512K 512K or greater but less than 1M 1M or greater but less than 2M ae or greater but less than 4M M 6M 8M The RAM SSD PDD on the Loc:: file system on SIBO machines does not buffer written data. This protects the integrity of the SSD contents from being corrupted by application process or system crashes occurring while files are open, or by the removal of the SSD while files are open on it. Flash SSDs Flash SSDs use Flash EPROM chips in which all the data bits are initially set. A particular bit may be cleared by programming but it can not be set again unless all the bits on the chip are set (by erasing the chip). Unlike older generation EPROMs, Flash EPROMs are erased electrically (rather than by exposure to UV light). The speed with which Flash can be programmed is also substantially faster than the old UV erasable EPROMS. A Flash SSDs may be erased in its SSD drive by formatting (formatting is described later in this chapter). Since Flash memory does not require a battery to maintain it, the data on a Flash SSD is very secure - more so than on RAM SSDs or magnetic media. Flash memory is also cheaper than RAM. The Flash filing system is designed such that the logical interface to files on a Flash SSD (whether reading or writing) is entirely equivalent to that on other devices (RAM SSD, hard disk etc). However, when overwriting or when deleting a file, space is consumed and is not recovered until the SSD is next formatted. Note also that renaming a file, setting the date and time or setting the file attributes all use up additional space and can therefore fail through lack of remaining capacity (the function returns E_FILE_FULL). However, deleting a file does not consume any further space. You can randomly access a Flash file and overwrite sections of it in exactly the same way as for any other file. However, in contrast to read/write block storage devices, overwriting incurs a storage overhead. For example, the following code: pos=O0L; do € p_seek(chan, F_FABS,pos); > while (!p_write(chan,"Pointless",8)); continuously overwrites the first 8 bytes of the file on channel chan. On a RAM file, this would work indefinitely and just exercise the hardware. On a Flash file, the code would work but each iteration would use up space on the Flash SSD and the p_write would eventually fail and return £_FILE_FULL. 119 PLIB REFERENCE In practice, you should not worry about limited overwriting of file data - for example, to patch a program file or to update a header. However, you cannot reasonably repeatedly and indefinitely overwrite a Flash file and, for example, Flash SSDs are unsuitable for B-tree index files. See the chapter Database Files for a description of a Flash-friendly set of functions allowing random access to, and the deleting and updating of, variable length records. These functions take advantage of the fact that a byte can be physically overwritten (with no storage penalty) provided that the overwrite is such that each bit in the byte either stays the same or is cleared. Flash SSDs are particularly attractive for storing program files, read-only data (say for data-referral applications), for securely storing logged data in the field and for archiving data. The storage of files on a Flash SSD is very different from that used on RAM SSDs. The scheme is not at all block structured but is based on linked variable length records. All this is hidden from the caller and the logical interface to a file on a Flash SSD is the same as for a file on a RAM SSD (or any other medium). The data in each write to a file on a Flash SSD is written straight to the SSD (ie it is not buffered)!. However, in the interests of efficient storage, the record just written is kept open (with a oxffff length) for as long as possible - until the file channel is closed or flushed using P_FFLUSH or until a write to another file on the same SSD occurs. The date and time stamp is also not written until the file channel is flushed or closed. If the SSD is removed or the machine resets while there is an open file channel with an outstanding write, the record is closed when the file is next accessed (the file is then also stamped with the current date and time). On a Flash SSD, there is no limit on the number of files or directories in the root directory. You will also find that it is possible to fit slightly more data on a freshly formatted Flash SSD than on a RAM SSD of the same nominal capacity. This is due to the fact that files are allocated space in multiples of whole blocks on a RAM SSD whereas Flash files are stored in exactly sized variable length records. File specifications A full file specification has the form: where the components are: the file system node (eg Loc::) the device name (eg B:) the directory name (eg \NOTES\OLD\) the file name (eg PLANS). the extension name (eg .TPD) An example of a full file specification is: LOC: :B:\NOTES\OLD\PLANS. TPD Although the file server assumes that a file specification may be decomposed into a , , , and , it avoids any assumption of the detailed syntax of the , , and components. This is left to the file system code that implements the file specification manipulation functions p_fparse and p_chdir. Leaving such detailed considerations to the file system is designed to allow the file stores of remote foreign file systems to be mapped transparently to the file server model (which is compatible with the MSDOS filing system). For example, in the VMS operating system, the above example of a file specification might translate to: REM: :USER: [NOTES.OLD] PLANS.TPD and on an Apple Macintosh, it might be: REM: :HD40:NOTES:OLD : PLANS .TPD This may not be the case for text files, which are handled by a layer over the Fit: device. The buffering of such files is thus outside the file server's control. 120 11 FILES SS SSS and on Unix, it might be: REM: :USER: :NOTES/OLD/PLANS. TPD To prepare for such foreign systems, applications should follow the example of the file server and avoid making assumptions about the syntax of file specifications and use functions such as p_fparse and p_ chdir to manipulate file specifications. File specifications satisfy the following rules: ® a file specification will not require more than P_FNAMESIZE (128) bytes, including a zero terminator = the component is always P_FSYSNAMESIZE bytes long (excluding any zero terminator) where P_FNAMESIZE and P_FSYSNAMESIZE are defined in p_file.h. Except for and the P_FNAMESIZE total, you should not make any assumptions about the maximum size of the components of a file specification. Default path The file server stores a default node, device and directory, also called the default path, for each of its clients. In addition, the file server stores a lower level default path, the system-wide default path. This is the default path assigned to new clients when they connect to the file server. A process that is a client of the file server uses: p_setpth to set its default path p_getpth to get a copy of its default path A process (normally a system process such as the shell) can change the system-wide default path - the path subsequently assigned to connecting clients - by calling p_setdefaul tpath. Channel-based services A client of the file server can use p_open on the FIL: device with different values of mode to do the following: P_FSTREAM, to open a file and manipulate it as a flat binary file P_FSTREAM_TEXT P_FTEXT to open a file and manipulate it as a record-oriented text file P_FDIR to get a list of the files and subdirectories in a directory P_FDEVICE to get a list of the available devices P_FNODE to get a list of the available file systems P_FFORMAT to format a Loc:: device Once a channel has been opened, one or more I/O functions (depending on mode) may be requested synchronously using p_iow (or a convenience function such as p_read) or asynchronously using p_ioc or p_ioa. There is no practical limit to the number of file channels that may be opened in the system or by a particular process. Non-channel-based services These are file operations not involving the FIL: device driver. They are requested by the following function calls: p_fparse, to parse a file specification p_fparseasync p_chdir, to change the directory component of a file specification p_chdirasyne P_ninfo, to get file system node information p_ninfoasyne p_dinfo, to get information on the medium in a device p_dinfoasyne 121 PLIB REFERENCE SEE p_finfo, to get information on a file or a directory p_finfoasync p_rename, to rename a file or a directory p_renameasync p_delete, to delete a file or a directory p_deleteasync p_mkdir, to make a new directory p_mkdirasync p_sfstat, to set the attributes of a file (and to set the volume label of a medium) p_sfstatasync p_fdate, set the modification date and time of a file p_fdateasync The name of asynchronous equivalents are generated by appending async to the name of the corresponding synchronous function. The asynchronous function takes the same parameters as the synchronous function but with the addition of a status word parameter at the end of the parameter list. Asynchronous file operations The file server is essentially asynchronous in its operation and most functions are provided in both asynchronous and synchronous forms. Because the file server has a higher priority than any of its clients, any operation that does not wait for a slow external device will have completed by the time an asynchronous request has returned. This includes any operation on the Rom:: file system or where the Loc:: file system accesses SSDs or the internal RAM drive (M:). However, when accessing the Rem:: file system where the connection is via an RS232 cable, any asynchronous request is almost certain to return before the operation is complete. Although, in practice, most file operations complete in a fraction of a second, a particular file request may take an extended time. For example, a read of 8K bytes from a REM:: file, connected over a 1200 baud modem link, would take well over a minute. However, a request will never take an indefinite time to complete (as, for example, a write to the parallel port can do when the printer is off line). For these reasons, the file server does not actively support a cancel service. (Incidentally, it is also worth considering the state in which a file would be left if a partially completed write operation were cancelled.) A request on an open file channel may be cancelled using p_iow(P_FCANCEL) but the cancel will not cause the operation to complete any sooner (and it will not complete with £_FILE_CANCEL). In general, it is preferable to simulate a cancel by waiting for current service to complete and forcing the status word to E_FILE_CANCEL, as illustrated below. The cancelling of an asynchronous request such as: p_ioc(pcb,P_FREAD ,&stat,buf, len); may be simulated by: if (stat==E_FILE_PENDING) { p_waitstat(&stat); /* wait for completion */ stat=E_FILE_CANCEL; /* report a cancel */ > This will normally complete almost immediately. You should, however, be aware that in rare cases when using the remote filing system (say, when the remote computer is re-booted and the communications link is set up to have a long timeout period) it may take an extended time to complete. 122 11 FILES SSS SSS ee ere ae Manipulating file specifications p_fparse {or f_ INT p_fparse(TEXT *name, TEXT *related, TEXT *full, P_FPARSE *perk); INT f_fparse(TEXT *name, TEXT *related, TEXT *full, P_FPARSE *perk); INT p_fparseasync(TEXT *name, TEXT *related, TEXT *full, P_FPARSE “perk, WORD *stat); Builds a full file specification of the form: as a zero terminated string in full, where the components are: the file system node (eg Loc: :) the device name (eg B:) the directory name (eg \NOTES\OLD\) the file name (eg PLANS). the extension name (eg .TPD) The parameters name and full may point to the same address, but related and full should not have the same address. There should be at least p_FNAMESIZE (128) bytes of memory reserved at full. Note that p_FNAMESIZE bytes are always written to full even when the generated file specification is shorter. If you have a name produced by a previous call to p_fparse and you are calling p_fparse again to fill in the struct at perk, you still need P_FNAMESIZE bytes at full. Subject to the restrictions described below, under the heading Using p_fparse across filing systems, the components making up the full file specification are taken from the following (in order of precedence): = the zero terminated file specification name = the zero terminated related file specification related (the related file specification may be omitted by passing NULL). = the default path (the node, device and directory as set by p_setpth) Thus components are only taken from related if they are missing from name. If there are still missing components, they are taken from the default path. Note that the and fields in name and related may contain wildcard characters. The output file specification in full is converted to upper case characters. Prior to EPOC version 2.31, conversion to upper case was performed by folding (as by using p_tofold). From EPOC version 2.31 onwards the conversion is by converting to upper case (as by using p_toupper). This has no significant effect on UK applications, but improves the handling of file names containing, for example, accented characters. Further information on the content of full is written to the p_FPARSE struct perk. If this information is not required, perk may be passed as NULL. If perk is not NULL, it should be the address of a P_FPARSE struct, defined in p_file.h as follows: typedef struct € UBYTE system; /* file system name length */ UBYTE device; /* device name length */ UBYTE path; /* path name length */ UBYTE name; /* name length */ UBYTE ext; /* extension length */ UBYTE flags; /* information on the presence of wildcards */ > P_FPARSE; 123 PLIB REFERENCE The members system, device, path, name and ext are set to the lengths of their corresponding fields in full, including delimiters. The flags field contains information on the occurrence of wild card characters in the extended specification according to the following masks: P_PWILD_ANY if set, the file specification contains one or more wild card characters and at least one of the following bits are set P_PWILD_NAME if set, the name field contains one or more wild card characters P_PWILD_EXT if set, the extension contains one or more wild card characters The function returns zero if successful or one of the following negative error numbers: E_FILE_NAME either name or related contains an invalid name E_FILE_DEVICE either name or related contains an invalid device name E_FILE_DIR either name or related contains an invalid directory name E_GEN_FSYS either name or related contains an invalid file system name or the file system does not exist The function f_fparse is identical to p_fparse except that it calls p_leave (passing the error number) rather than return a negative error number. As an example, if the current default path is "Loc::A:\", then p_fparse("FRED", "\\FILES\\DOCS\\JOE.DOC", buf, &crk); writes the zero terminated string LOC::A:\FILES\DOCS\FRED.DOC to buf and (5,2,12,4,4,0} to crk. The function is often used to change the extension of a file as, for example, in: p_fparse(".BCK", "\\FILES\\DOCS\\JOE .DOC", buf ,&crk); Using p_fparse across filing systems Since the syntax of a file specification may differ between filing systems (see File specifications, earlier in this chapter) the functions p_fparse and p_chdir are implemented in the code of each file system. The function p_fparse builds a full file specification from up to three sources, which may therefore specify two or more different filing system nodes. This section describes the rules that determine which implementation performs the function p_fparse in any particular case, and which of the three sources contribute towards the full file specification. The three sources will be referred to as name, related and the default path, as in the description of p fparse. The node which performs the p_fparse is determined from name, related and the default path, according to the following rules: ® If neither name nor related contain an explicit component, the node specified by the default path is assumed (the default path always has an explicit component). = If either name or related (but not both) contain an explicit component, this is taken to be the node which performs the p_fparse. = If both name and related contain explicit components, the specified in name is taken to be the node which performs the p_fparse. If an explicit in related differs from that determined from the above rules, related is assumed not to be valid for the specified node and is ignored by p_fparse. If the in the default path differs from that determined from the above rules, the default path is assumed not to be valid for the specified node and is ignored by p_fparse. Thus, assuming that the default path is "Loc::A:\", then p_fparse("PLIB.MAK", "REM: :HD40:SOURCE:", buf, NULL); is handled by the remote filing system and writes the string REM: :HD40:SOURCE:PLIB.MAK to buf, but p_fparse("LOC: :PLIB.MAK", "REM: :HD40: SOURCE :PLIB.MAKE", buf ,NULL); is handled by the local filing system and writes the string Loc: :A:\PLIB.MAK to buf (related is ignored). All sources that are not ignored by p_fparse are checked as being valid for the specified node, even if they do not contribute to final full file specification. Thus 124 11 FILES p_fparse("LOC::C:\\PLIB.MAK", "PLIB.MAKE", buf ,NULL); will fail with error E_FILE_NAME because the in retated is not valid for the Loc:: filing system, even though all the components of a full file specification are present in name. This type of situation is typically likely to occur when copying files between the REM:: and Loc:: filing systems. ang INT p_chdir(TEXT “src, TEXT *outp, INT mode, TEXT *subdir); INT p_chdirasync(TEXT *src, TEXT *outp, INT mode, TEXT *subdir, WORD *stat); Parse src with a NULL related name and change its directory to produce a new file specification outp according to mode, which should be one of: P_CD_ROOT to move to the root directory P_CD_PARENT to move to the parent directory P_CD_SUBDIR to move to the zero terminated subdirectory subdir The parameter subdir is ignored if mode is not P_CD_SUBDIR. If sre contains a file name then this name is retained and appended to the new directory specification outp. There should be at least P_FNAMESIZE (128) bytes of memory reserved at outp. Note that P_FNAMESIZE bytes are always written to outp even when the generated file specification is shorter. The parameters src and outp may point to the same address. Note that p_chdir is just a means of setting up outp from mode, src and subdir with no consideration to the existence of the directories in src and subdir. The operation is performed by the appropriate file system (as specified by the component of the full file specification) allowing a file specification to be manipulated without knowledge of the detailed structure (eg the delimiters used) of file specifications on that . When mode is P_CD_SUBDIR, subdir is just inserted into the full file specification at the appropriate position. Except for checking that the resultant full file specification length does not exceed P_FNAMESIZE bytes, the validity of outp is not checked. The function returns zero if successful or a negative error number if it fails. As well as the errors that can be returned by the internal parse of sre, p_chdir can retum: E_FILE_NXIST mode was P_CD_PARENT and src was already at the root directory E_FILE_DIR subdir contains an invalid directory name E_FILE_NAME inserting subdir would make the file specification length exceed P_FNAMESIZE bytes For example, if the current default path is "Loc::a:\", then p_chdir("\\fred\\*.c", buf,P_CD_SUBDIR,"bill") writes LOC::A:\FRED\biLL\*.C to buf p_chdir("\\fred\\aa.c",buf,P_CD_SUBDIR,"jim") writes LOC::A:\FRED\j im\AA.C to buf p_chdir¢"\\fred\\aa.c", buf,P_CD_SUBDIR, "bill\\jim") writes LOC: :A:\FRED\bilL\jim\AA.C to buf (although, depending on where bil\jim came from, this breaks the spirit of p_chdir by including a delimiter in subdir) p_chdir("\\fred\\j im\\*", buf ,P_CD_PARENT,NULL) writes LOC::A:\FRED\* to buf p_chdir("\\fred\\j im\\*", buf ,P_CD_ROOT,NULL) Writes LOC::A:\* to buf p_chdir("rem: :hd40: fred:aa.c", buf ,P_CD_SUBDIR,""jim") writes REM: :HD4O:FRED:jim:AA.C to buf 125 PLIB REFERENCE a SS ek Se se A a ee | The default node, device and directory INT p_setdefaultpath(TEXT *name); Set the file server system-wide default path to name. This is the default path that is assigned to a process when it connects to the file server. The parameter name is a zero terminated string of the form: where is the file system node (eg Loc: :) is the device name (eg 8:) is the directory name (eg \NOTES\OLD\) The parameter name is parsed with a NULL related file name and any file name and extension component is discarded. The function returns zero if successful or the same error numbers as for p_fparse if name failed to parse. A successful call implies that file system exists at the time of the call (note that some file systems such as REM:: are not permanently installed) and that and are valid for the file system. It does not mean that contains a medium, or that exists. For example, after: p_setdefaul tpath("LOC: :B:\\"); all new file server clients will initially have the default path of Loc::B:\. INT p_setpth(TEXT *name); INT p_setpthasync(TEXT *name, WORD *stat); Set the default node, device and directory for this process to name where name is a zero terminated string of the form: where is the file system node (eg Loc: :) is the device name (eg B:) is the directory name (eg \NOTES\OLD\) The parameter name is parsed with a NULL related file name and any file name and extension component is discarded. The specified directory must exist. The function returns zero if successful or a negative error from p_fparse if name failed to parse or: E_FILE_DEVICE if the device does not exist E_FILE_NOTREADY if the device does not contain a medium E_FILE_DIR if the directory does not exist When a process connects to the file server, it is assigned the file server system-wide default path, set by the last call to p_setdefaul tpath. For example, if the current default path is Loc::M:\, then: p_setpth("B:"); sets the default path to Loc: :B:\. 126 11 FILES VOID p_getpth(TEXT *name); Write the default node, device and directory for this process as a zero terminated string to name of the form: where is the file system node (eg Loc::) is the device name (eg B:) is the directory name (eg \NOTES\OLD\) There should be P_FNAMESIZE bytes of memory reserved at name. There is no asynchronous version of p_getpth because the process default node, device and directory is stored by the file server without recourse to the appropriate file system (although the interpretation of and may depend on ). INT p_getpthbyid(HANDLE pid, TEXT *name); Write the default node, device and directory of process pid as a zero terminated string to name. The content of name is as for p_getpth, described above. Returns zero if successful or £_FILE_NXIST if pid is not a client of the file server. SS eS nn ee Se | Operations on nodes and devices INT p_open(VOID **ppfcb, TEXT *name, UINT mode); INT p_iow(VOID *pfcb, INT func, TEXT *buf, P_NINFO “pinfo); INT p_close{VOID *pfcb); To get a list of file system node names, you: = call p_open with a mode of P_FNODE to open a node list channel = repeatedly call p_iow with a func of P_FREAD to read each node name (until it returns E_FILE_EOF) = call p_close to close the node list channel The name parameter to p_open may be "FIL:" or NULL. The mode parameter must be P_FNODE. Each successful call to p_iow(P_FREAD) writes the next file system node name as a zero terminated string to buf (buf should have a capacity of P_FSYSNAMESIZE+1 (6) bytes). The call to p_iow(P_FREAD) returns E_FILE_EOF after all the node names have been read. If the parameter pinfo is not NULL it is taken as the address of a P_NINFO struct which is filled with the same node information as would be obtained by calling p_ninfo, described below. For example: LOCAL_C VOID ListNodes(VOID) { VOID *ncb; TEXT buf [P_FSYSNAMESIZE+1]; P_open(&ncb, "FIL:",P_FNODE); while (!p_iow(ncb,P_FREAD,&buf [0] ,NULL)) p_puts(&buf (0) ); p_close(ncb); > 127 PLIB REFERENCE lists the current node names. INT p_ninfo(TEXT *node, P_NINFO *pninfo); INT p_ninfoasync(TEXT *node, P_NINFO *pninfo, WORD *stat); Write information on the zero terminated file system node to the P_NINFO structure at pninfo and return zero if successful or the negative E_GEN_FSYS if node is invalid or does not exist. The P_NINFO struct is defined in p_file.h as: typedef struct { UWORD version; UWORD type; UWORD formattable; UBYTE spare[26]; } P_NINFO; where pninfo->type contains: P_FSYSTYPE_FLAT if file system node does not support hierarchical directories P_FSYSTYPE_HIER if file system node does support hierarchical directories and pninfo->formattable is TRUE if file system node supports the formatting of its devices and FALSE otherwise. The version field pninfo->version is designed to allow future versions of p_ninfo (which would write further information to pninfo->sparef]) to be identified by the caller. At the time of writing, pninfo->version is set to 2. INT p_locchg(INT mask); This function is only available in EPOC version 2.16 or later. Check if Loc:: has changed (for example, if the SSD door has been opened) since the last call to p_locchg. The return value has the bits that were set in mask either set or cleared, depending on whether or not LOC:: has changed. The use of a mask allows multiple independent calls from within one application, with each caller consistently using one specific bit of mask. Bit 15 is ignored, so as never to return a negative value (even though the call can never fail). Bits 8 to 14 inclusive are reserved for system use. An application may therefore make up to eight independent calls, using bits 0 to 7. Typical calling code would make an initial call to p_tocchg, to ensure that only subsequent changes are detected. It would then poll for changes by calling p_locchg, say, every two seconds. p_Opentr INT p_open(VOID **ppfcb, TEXT *name, UINT mode); INT p_jow(VOID *pfcb, VOID *buf, NULL); INT p_close(VOID *pfcb); To get a list of device names for a particular file system you: = call p_open with a mode of P_FDEVICE to open a device list channel = repeatedly call p_iow with a func of P_FREAD to read each device name (until it returns E_FILE_EOF) = call p_close to close the device list channel The name parameter to p_open should be a zero terminated node name (with or without a leading FIL:). The parameter mode must be P_FDEVICE. If the node name is illegal or does not exists, p_open fails and returns E_GEN_FSYS. If the node does not support devices (as is the case for ROM::), p_open fails and returns E_GEN_NSUP. Each successful call to p_iow(P_FREAD) writes the next device name as a zero terminated string to buf (buf should have a capacity of P_FNAMESIZE (128) bytes). The call to p_jow(P_FREAD) returns £_FILE_EOF after all 128 11 FILES eee the device names have been read. The parameter following buf in the call to p_iow(P_FREAD) should be NULL. For example: LOCAL_C VOID ListDevices(VOID) € VOID *ncb, *dcb; INT ret; TEXT node [P_FSYSNAMESIZE+1] ; TEXT device [P_FNAMESIZE]; p_open(&ncb, "FIL:",P_FNODE); while (!p_jow(nceb,P_FREAD, &node [0] ,NULL)) € p_puts(&node [0] ); if ((ret=p_open(&dcb, &node [0] ,P_FDEVICE))<0) { p_puts("\tDevices not supported"); continue; > while (!p_ jow(dcb,P_FREAD,&device[0] ,NULL)) P_printf("\t%s", &device [0] ); p_close(deb); > p_close(ncb); > lists the current node names with their devices. INT p_dinfo(TEXT *dname, P_DINFO *pdinfo); INT p_dinfoasync(TEXT *dname, P_DINFO *pdinfo, WORD *stat); Write information on the device with zero terminated name dname and on the medium (eg SSD or diskette) that is mounted on device dname to the P_DINFO struct at pdinfo. Returns zero if successful or one of the following negative error numbers: E_FILE_DEVICE if an invalid or non-existent device is specified &_FILE_NOTREADY if the device does not contain a medium E_FILE_CORRUPT the Loc:: sub file system on a SIBO machine has recognised a RAM or Flash SSD without a valid boot record (probably because the SSD is not formatted) E_FILE_UNKNOWN none of the file systems recognise this medium and formatting is unlikely to make it readable The P_DINFO struct is defined in p _file.h as: typedef struct € UWORD version; UWORD mediatype; UWORD removable; ULONG size; ULONG free; UBYTE name [P_VOLUMENAME] ; WORD batterystate; UBYTE spare[16]; } P_DINFO; Except for pdinfo->removable, which is True if the device has removable media, all information describes the medium mounted on device dname. The least significant byte of pdinfo->mediatype takes one of the following values: P_FMEDIA_UNKNOWN unknown media type P_FMEDIA_FLOPPY drive takes 3.5 inch or 5.25 inch diskettes P_FMEDIA_HARDDISK drive contains a hard disk 129 PLIB REFERENCE P_FMEDIA_RAM drive contains a RAM disk (read/write) P_FMEDIA_FLASH drive contains a Flash disk P_FMEDIA_ROM drive contains a ROM disk (read only) P_FMEDIA_WRITEPROTECTED drive contains a write protected medium The most significant byte of pdinfo->mediatype takes a combination of the following bit flags: P_FMEDIA_COMPRESSIBLE it is worth compressing out logically deleted records from a file on this medium to reduce both the file size and the storage consumed by the file (not set for Flash SSDs) P_FMEDIA_DYNAMIC the media capacity pdinfo->size can change over time P_FMEDIA_INTERNAL media is internal (implicitly not removable) P_FMEDIA_DUAL_DENSITY device drive is dual density (if this is set then p_open(P_FFORMAT) can take the optional P_FLOWDENSITY for a low density format) P_FMEDIA_FORMATTABLE media is formattable The members pdinfo->size and pdinfo->free give the total capacity of the medium in bytes and how much of that capacity is free (also in bytes) respectively. If P_FMEDIA_DYNAMIC is set in pdinfo->mediatype (as it is for the internal RAM device M:), pdinfo->size and pdinfo->free may change over time. The volume name, with a maximum of 12 characters (for example, "D1SKNAME.DSK"), is written as a zero terminated string to &pdinfo->name [0]. The system is designed to be able to take advantage of hardware which can detect a low battery voltage in a RAM SSD. When this is not possible (either because the medium is not a RAM SSD or because the hardware does not support it), pdinfo->batterystate contains E_GEN_NSUP. If the SSD does contain a battery and the hardware to detect a low voltage, pdinfo->batterystate contains FALSE if the battery is low Or TRUE (more precisely, a non-zero value other than £_GEN_NSUP) if the battery voltage is acceptable. The content of this field is undefined for values of pdinfo->version less than 3. The version field pdinfo->version is designed to allow future versions of p_dinfo (which would write further information to pdinfo->spare[) to be identified by the caller. In the following example, ListDevices produces a device list with some device information, including the devices on REM:: (if the file server is connected to a remote file server) as well as Loc::. LOCAL_C TEXT *GetTypeText(UINT type) € switch (type) € case P_FMEDIA_FLOPPY: return "Floppy": case P_FMEDIA_HARDDISK: return "Hard"; case P_FMEDIA_FLASH: return "Flash"; case P_FMEDIA_RAM: return "RAM"; case P_FMEDIA_ROM: return "ROM"; case P_FMEDIA_WRITEPROTECTED: return "Protected": > return "Unknown"; > 130 11 FILES ESSE LOCAL_C VOID ListDevices(VOID) € VOID *ncb, *dcb; INT ret; TEXT device [P_FNAMESIZE] ; TEXT bb[E_MAX_ERROR_TEXT_SIZEJ; P_DINFO dinfo; P_printf(" Device Name Type Size Free"); P_printf("sssssssss2= BESSSEsssS SSSisessss sssssss= sssesses!!)- P_openc&ncb, "FIL:",P_FNODE)- while (!p_iow(ncb,P_FREAD,&device [0] ,NULL)) { if (p_open(&dcb, &device [0] ,P_FDEVICE)) continue; while (!p_iow(deb, P_FREAD , &device [P_FSYSNAMESIZE] ,NULL)) € if (Cret=p_dinfo(&device [0] ,&dinfo))<0) € p_errs(&bb[0] , ret); P_printf("%- 12s %- 12s<%s>", device [0] ,"**Failed**", &bb[0] ); continue; > p_printf("%- 12s %- 12s%- 11s &7ldK %7LdK", &device [0] ,&dinfo.name{0] ,GetTypeText (dinfo.mediatype&0xff), (dinfo.size+512)>>10, (dinfo. free+512)>>10); > p_close(dcb); > p_close(ncb); INT p_open(VOID **ppfcb, TEXT *name, UINT mode); INT p_read(VOID *pfcb, VOID *buf, UINT Len): INT p_close(VOID *pfeb); To format a medium in a device you: = call p_open with a mode of P_FFORMAT to open a device format channel " call p_read to get the total format count (optional) = repeatedly call p_read until it returns €_FILE_EOF = call p_close to close the device format channel The name parameter to p_open should be a zero terminated file specification with a root directory (name is parsed with a NULL related name) of the form: LOC: :\ to format the medium in , giving it the volume name . The volume name may subsequently be changed using p_sfstat, described later in this chapter. The mode parameter to p_open must be P_FFORMAT. If the device supports dual density formatting (as may be established by calling p_dinfo), P_FLOWDENSITY may be or'ed into mode to format at the lower density. The call to p_open returns zero if successful. As well as the error numbers that can be returned by p_fparse, p_open(P_FFORMAT) can also return one of the following negative error numbers: E_GEN_NSUP the file system, device or medium does not support formatting E_FILE_PROTECT the medium is write protected or is read-only (eg a ROM SSD) At the time of writing, formatting is only supported on the Loc:: system on a SIBO machine. You can tell in advance if a file system supports formatting by calling p_ninfo (described above). If the file system does support formatting, you can tell if a device contains a formattable medium by calling p_dinfo (also described above). 131 PLIB REFERENCE SSeS The first call to p_read writes a uworD total count to buf and returns zero. This count represents the number of subsequent p_read calls required to complete the format. The Len parameter to p_read is ignored in all cases. A call count can be combined with the total format count to present a percentage done indication. When the format is complete, p_read returns E_FILE_EOF; you should then call p_close. Abandoning the format, by calling p_close prematurely, will leave the medium in a corrupted state. For example, the following function: LOCAL_C VOID FormatDevice(TEXT *name) { INT err,i; UWORD count; VOID *chan; TEXT bb[E_MAX_ERROR_TEXT_SIZE]; chan=NULL; if (Cerr=p_open(&chan, name,P_FFORMAT))<0) goto exit; if (Cerr=p_read(chan, &count ,0))<0) goto exit; p_printf("Formatting %s count=%d", name, count); i=1; while (Cerr=p_read(chan, &val ,0))>=0) P_print("\r%05u", i++); if (err==E_FILE_EOF) err=0; exit: p_close(chan); if Cerr<0) { p_errs(&bb[0] ,err); p_printf("\r\nFormat failed: %s",&bb[01); 3 else p_printf("\r\nFormat complete"); > could be called with: FormatDevice("LOC: :A:\\BACKUP"); to format the SSD in drive a:, giving it the volume name BACKUP. INT p_locdevice(INT aDevice, UWORD *pMedia); This function is only available in EPOC version 3.18 or later. Write, to *pMedia, the media type of the local device (that is, a device on the Loc:: file system) specified by aDevice. The value of aDevice must be one of: = 'M' (0x4d) a 'T' (0x49) = 'A' (0x41) to 'H' (0x48) inclusive. where 'I' (Internal) is an alias for 'M'. Apart from two additional flags, the value written to *pMedia is the same as the value written to the mediatype Of a P_DINFO struct by p_dinfo. The call is more efficient than a call to p_dinfo, provided that the media type is the only information that is required. The two extra flags that may be written to *pMedia are E_FMEDIA_BATTERY_VALID and E_FMEDIA_BATTERY_GooD, defined in epoc.h. If E_FMEDIA_BATTERY_VALID is not set then the device does not support battery measurement. If it is set then E_FMEDIA_BATTERY_GOOD will be clear if the battery voltage is too low, otherwise it will be set. 132 11 FILES SSS Returns zero or a negative error number. Errors that may be returned are: E_FILE_NOTREADY no device is available E_FILE_DEVICE the device specified by aDevice is not in the valid range of devices E_GEN_NSUP no PDD exists which can handle the device E_GEN_UNKNOWN Note that the media does not have to be mountable for this service to work. INT p_locreadpdd(INT aDevice, LONG *aPos, VOID *aPtr, UINT aLen); This function is only available in EPOC version 3.18 or later. Read, to the buffer pointed to by aPtr, aLen bytes starting at an offset of *aPos bytes into the SSD from the local SSD (that is, an SSD on the Loc:: file system) specified by aDevice. The data is read by means of direct access to the physical device driver (PDD) and the reading is therefore very efficient. The value of aDevice must be one of: = 'M' (Ox4d) sw 'T' (0x49) = ‘A’ (0x41) to 'H' (0x48) inclusive. where 'I' (Internal) is an alias for ‘M'. The medium must have been mounted prior to using this service. (To ensure the medium is mounted, just make any normal device access.) The service returns zero or one of the following negative error numbers: E_GEN_OS the medium is not mounted £_FILE_CORRUPT the specified offset is greater than the size of the SSD Sen eee ee ee eee Operations on directories and files INT p_open(VOID **ppfcb, TEXT *name, UINT mode); INT p_iow(VOID *pfcb, INT func, TEXT *buf, P_INFO *pinfo); INT p_close(VOID “pfcb); To get a list of files in a directory you: = call p_open with a mode of P_Fp1r to open a directory list channel = repeatedly call p_iow with a func of P_FREAD to read each directory entry (until it returns E_FILE_EOF) = call p_close to close the directory list channel The name parameter to p_open is a zero terminated file specification. Internally to the call to p_open, this is parsed with a wild card related name (such as "*.*") that will find all files in the directory. It is therefore not necessary to include a file name or file name extension in name unless you wish to restrict the directory search to files with that name and/or extension. If present, the file name and the file name extension in name would normally contain wildcards. Passing a name of ™ produces the names of all the files in the current directory. The parameter mode must be P_FDIR. Each successful call to p_iow(P_FREAD) writes the next matching file name (excluding the node, device and directory component) as a zero terminated string to buf (buf should have a capacity of P_FNAMESIZE (128) bytes). The call to p_iow(P_FREAD) returns E_FILE_EOF after all the file names have been read. 133 PLIB REFERENCE If the parameter pinfo is not NULL it is taken as the address of a P_INFO struct where P_INFO is defined in p_file.h as: typedef struct { UWORD version; UWORD status; /* status bits */ ULONG size; /* size of the file in bytes */ ULONG modst; /* system time of last modification */ UBYTE spare[4]; > P_INFO; The file status pinfo->status has the following bit fields: P_FAWRITE set if file is not read-only P_FAMOD set if the file has been modified P_FAHIDDEN set if file is hidden P_FASYSTEM set if file is a system file P_FADIR set if the file is a directory file P_FAVOLUME set if the file is a volume name directory P_FATEXT set if file is a text file Note that a directory read may return with a volume name written to buf and P_FAVOLUME set in pinfo->status. This will only occur if the directory is a root directory of a PC-based device that has a volume name. A directory read only returns with P_FATEXT set in pinfo->status when the file system can recognise a text file. The Loc:: file system cannot recognise text files but a remote file server, running on an operating system (eg VMS) which can recognise text files, will set the p_FATEXT bit as appropriate. The field pinfo->size gives the logical file length (the end-of-file position). The field pinfo->modst gives the time the file was last modified, expressed in system time format (the number of seconds since 00:00:00 January 1 1970). See the chapter Time, Timers and Dates for details on how to convert to and from the system time. The p_INnfo file information may also be obtained when using p_finfo, described below. Example #include LOCAL_D VOID *dcb=NULL; LOCAL_C VOID panic(TEXT *msg, INT errno) € TEXT bb[E_MAX_ERROR_TEXT_SIZEJ; p_close(dcb); dcb=NULL; p_errs(&bb[0] ,errno); p_printf("%s: %s",msg,&bb[0] ); p_leave(errno); > 134 11 FILES eee LOCAL_C VOID PrintDirLine(TEXT *name, P_INFO *pinfo) € P_DAYSEC ds; P_DATE dt; TEXT *p,6[40}; p=&b [0] ; if (pinfo->status&P_FAVOLUME) P=p_scpy(p, "Vol ,"); if (pinfo->status&P_FADIR) P=p_scpy(p,"Dir,"); if (pinfo->status&P_FAMOD) P=p_scpy(p, "Mod,"); if (!(pinfo->status&P_FAWRITE)) P=p_scpy(p, "Read, "); if (pinfo->status&P_FASYSTEM) P=p_scpy(p,"Sys,"); if (pinfo->status&P_FAHIDDEN) P=p_scpy(p,"Hid,"); if (*(p-1)==',') *--p=0; p_sttods(&pinfo->modst ,&ds); p_dstodt (&ds,&dt); P_printf("%- 12s Blu %02u-%02u-%02u %02u:%02u Xs", name, pinfo->size,dt.day+1,dt.month+1,dt.year,dt.hour,dt.minute,&b[0) ); > LOCAL_C VOID CDECL PrintDirList(TEXT *dir) € INT err,NoFiles; P_INFO info; TEXT name [P_FNAMESIZE]; if (Cerr=p_open(&dcb,dir,P_FDIR))!=0) panic("Failed to open directory file",err); NoFi les=TRUE: while (!(Cerr=p_iow(dceb,P_FREAD ,&name [0] ,&info))) € NoFiles=FALSE; PrintDirLine(&name [0] ,&info); > p_close(dcb); dcb=NULL; if Cerr!=€_FILE_EOF) panic("Failed to read directory",ret); if (NoFiles) P_printf("No files found"); > GLDEF_C INT main(VOID) {€ TEXT name[P_FNAMESIZE]; while (p_getl(">", &mame [0] ,P_FNAMESIZE)) p_enter((VOID *)PrintDirList, &name([0]); return(Q); > mation INT p_finfo(TEXT *name, P_INFO *pinfo); INT p_finfoasync(TEXT *name, P_INFO *pinfo, WORD *stat); Parse name with a NULL related name and write information about the specified file (which may be a directory file) to the p_INFO struct at pinfo. The same file information is written to pinfo as is obtained when using p_iow(P_FREAD) on a directory list channel that was opened with P_open(P_FDIR), as described above. 135 PLIB REFERENCE The version field pinfo->version is designed to allow future versions of p_finfo (which would write further information to pinfo->spare{]) to be identified by the caller. At the time of writing, pinfo->version is set to 2. Returns zero if successful or a negative error number. As well as the p_fparse error numbers if the parse fails, p_finfo can return: &_FILE_DEVICE if the device does not exist E_FILE_NOTREADY if the device does not contain a medium E_FILE_DIR if the directory does not exist E_FILE_NXIST if the file does not exist The atomic nature of the p_finfo (unlike p_open) makes it a good choice for checking whether a file exists (it returns E_FILE_NXIST if the file does not exist). To test for the existence of a directory, you can also use p_testpth, described below. INT p_testpth(TEXT *dname); INT p_testpthasync(TEXT *dname, WORD *stat); Return zero if the directory component of the zero terminated file specification dname exists. The file specification dname is parsed with a NULL related name and any file name component is discarded. If the parse fails, p_testpth returns the error return from p_fparse. It can also fail with: E_FILE_DEVICE if the device does not exist E_FILE_NOTREADY if the device does not contain a medium E_FILE_DIR if the directory does not exist For example, if the current path is Loc::A:\, then: p_testpth("\\dir1\\dir2\\fred.c"); returns zero if the directory LoC::A:\DIR1\DIR2\ exists. INT p_rename(TEXT *oldname, TEXT *newname); INT p_renameasync(TEXT *oldname, TEXT *newname, WORD *stat); Parse each of newname and oldname with a NULL related name and, if successful, rename oldname to newname. Both buffers should be at least p_FNAMESIZE bytes in length. The file specified by oldname must exist and newname must not already exist. Neither file name may include wild cards. Both files should be on the same node and device. On toc::, files may be renamed across directories (but this may not be supported by some remote systems). Prior to version 3.5 of EPOC, renaming across directories can fail when the target directory is the root of Loc::M:. Directory files (which do not need to be empty) may be renamed, but not across different directories. The function returns zero if successful or a negative error number if it fails. As well as a p_fparse error number if either oldname or newname fails to parse, p_rename can return the following error numbers: E_FILE_DEVICE oldname and newname are on different devices or the device does not exist E_FILE_NOTREADY the device does not contain a medium E_FILE_DIR the directory does not exist E_FILE_EXIST newname already exists E_FILE_NXIST oldname does not exist E_FILE_LOCKED oldname exists but a process has the file open 136 11 FILES E_FILE_PROTECT "the medium is write protected or is read-only (eg a ROM SSD) E_FILE_FULL not enough room on the medium for the rename (possible on Flash SSDs) For example, if the current path is Loc::A:\, then: p_rename("\\dir1\\dira\\fred.c","\\dir1\\dir2\\jim.c"); renames FRED.C i LOC::A:\DIR1\DIR2\ to JIM.C while: p_rename("\\dir1\\dir2\\fred.c","fred.c!): effectively moves FRED.C in LOC::A:\DIR1\DIR2\ to the root directory. It is sometimes useful to parse newname with oldname as a related name before calling p_rename as in: GLDEF_C INT RenameFile(TEXT *oldname, TEXT *newname) € TEXT buf [P_FNAMESIZE] ; if (ret=p_fparse(newname, oldname, &buf [0] , NULL)) return(ret); return(p_rename(oldname, &buf [0] )); } Then, for example, calling: RenameFilec"\\dirt\\dir2\\fred.c","jim.c"); renames FRED.C iM LOC::A:\DIR1\DIR2\ to JIM.C while: RenameF i lLeC"\\dir1\\dir2\\fred.c","\\jim.c"); renames FRED.C in LOC: :A:\DIR1\DIR2\ to JIM.c and moves JIM.C to the root directory. INT p_delete(TEXT *name); INT p_deleteasync(TEXT *name, WORD *stat); Parse name with a NULL related name and, if successful, delete the specified file (which may be a directory file). A directory can not be deleted unless it is empty. The function returns zero if successful or a negative error number if it fails. As well as a p_fparse error number if name fails to parse, p_delete can return the following negative error numbers: E_FILE_DEVICE the device does not exist E_FILE_NOTREADY the device does not contain a medium E_FILE_DIR the directory does not exist E_FILE_NXIST the file does not exist E_FILE_LOCKED a process has name open E_FILE_ACCESS name is a read-only file E_FILE_PROTECT the medium is write protected or is read-only (eg a ROM SSD) E_FILE_EXIST name is a directory that contains files For example, if the current path is Loc::A:\, then: p_delete("\\dir1\\dir2\\fred.c"); deletes LOC: :A:\DIRI\DIR2\FRED.C. 137 PLIB REFERENCE INT p_mkdir(TEXT *name); INT p_mkdirasync(TEXT *name, WORD *stat); Parse name with a NULL related name and create the specified directory. The name of the directory to be created is specified by the file name component of name if name contains a file name component. Otherwise it is specified by the directory component of the parsed file specification. Intermediate directories are also created if necessary. Although unusual, there is no reason why a directory should not have an extension, and any extension in name is significant. The function returns zero if successful or a negative error number if it fails. As well as the p_fparse error numbers returned if name fails to parse, p_mkdir can return the following negative error numbers: E_FILE_DEVICE the device does not exist E_FILE_NOTREADY the device does not contain a medium E_FILE_EXIST the directory already exists E_FILE_PROTECT the medium is write protected or is read-only (eg a ROM SSD) E_FILE_FULL there is no more room on the medium E_FILE_DIRFULL there is no more room in the root directory For example, if the current path is Loc: :a:\, then either: p_mkdir¢"\\dirt\\dir2\\"); or p_mkdirc"\\dirI\\dir2"); makes the directory Loc: :A:\DIR1\DIR2\ and also makes Loc: :A:\DIR1\ if it does not already exist. INT p_sfstat(TEXT *name, UINT status, UINT mask); INT p_sfstatasync(TEXT *name, UINT status, UINT mask, WORD *stat); Set or clear the attributes of the file specified by the zero terminated file name name (but see also Setting the volume name below). Both status and mask contain a bit field made up of the following bit masks: P_FAWRITE set if file may be written to P_FAMOD set if the file has been modified P_FAHIDDEN set if file is hidden P_FASYSTEM set if file is a system file The function only modifies those bits that are set in mask where attribute is set or cleared depending on the value of the corresponding bit in status. The function returns zero if successful or a negative error number if it fails. As well as a p_fparse error number if name fails to parse, p_sfstat can return the following negative error numbers: E_FILE_DEVICE the device does not exist E_FILE_NOTREADY the device does not contain a medium E_FILE_DIR the directory does not exist E_FILE_NXIST the file does not exist E_FILE_LOCKED a process has name open E_FILE_PROTECT the medium is write protected or is read-only (eg a ROM SSD) For example: p_sfstat("joe.doc",0,P_FAWRITE); 138 11 FILES ———_—_—. EEE makes the file joe.doc read-only. Setting the volume label If mask has the P_FAVOLUME bit set, status is ignored p sfstat and p sfstat sets or deletes the volume name. At the time of writing, only the Loc:: file system supports setting the volume label (the function returns E_GEN_NSuP if it is not supported). The name parameter should be a zero terminated file specification with a root directory (name is parsed with a NULL related name) of the form: LOC: :\ to label the medium in , giving it the volume name . If name does not contain a Component, the volume label is deleted. The function returns zero if successful or a negative error number if it fails. As well as a p_fparse error number if name fails to parse, p_sfstat can return the following negative error numbers: E_FILE_ DEVICE the device does not exist E_FILE_NOTREADY the device does not contain a medium E_FILE_PROTECT the medium is write protected or is read-only (eg a ROM SSD) For example, if the current path is Loc::a:\, then: p_sfstat("d:\mydisk",0,P_FAVOLUME) ; gives the medium in device Loc: :p: the label myDISk, p_sfstat("d:\diskname.dsk",0,P_FAVOLUME); gives the medium in device Loc: :p: the label DISKNAME.DSK and: p_sfstat("d:\",0,P_FAVOLUME); deletes any volume name from device Loc: :D:. INT p_fdate(TEXT *name, ULONG date); INT p_fdateasync(TEXT *name, ULONG date, WORD *stat); Set the file creation date to date where date is in the form of the system time - that is, the number of seconds since January Ist 1970. The date may not be set earlier than January Ist 1980. If p_fdate is called with any earlier system time, the date is forced (without error) to January 1st 1980. The system time used for the creation date is always converted to an even number of seconds (i.e. the least significant bit of date is discarded). The function returns zero if successful or a negative error number if it fails. As well as a p_fparse error number if name fails to parse, p_fdate can return the following negative error numbers: E_FILE_DEVICE the device does not exist £_FILE_NOTREADY the device does not contain a medium E_FILE_DIR the directory does not exist E_FILE_NXIST the file does not exist E_FILE_LOCKED a process has name open E_FILE_ACCESS name is a read-only file £_FILE_PROTECT the medium is write protected or is read-only (eg a ROM SSD) The following example copies the date of fred.doc to fred. txt. 139 PLIB REFERENCE P_INFO info; p_finfo("fred.doc",&info); p_fdate("fred.txt", info.modst); ———SS SEE ee ee a a ea Binary file access To open a file to be manipulated as a flat binary file, you call p_open with the 3rd parameter mode or'ed with p_FSTREAM. For example: p_open(&feb,"fred.dat",P_FSTREAM|P_FSHARE) opens a file for reading only. p_open(&fcb, "fred.dat",P_FSTREAM|P_FUPDATE|P_FREPLACE |P_FRANDOM) creates a writable binary file. The mode flags (eg P_FREPLACE) are described under p open, below. There is no practical limit to the number of channels that may be opened in the system or by a particular process. Once you have opened a binary file channel, the possible I/O operations are: p_read to read from the file channel p_ioc(P_FREAD) p_ioa(P_FREAD) p_write to write to the file channel p_ioc(P_FWRITE) p_ioa(P_FWRITE) p_close to close the file channel p_seek to set and sense the channel file position p_iow(P_FSETEOF) to set the logical end of file p_iow(P_FFLUSH) to flush the file buffers p_iow(P_FCANCEL) to cancel all pending asynchronous requests (not actively supported) Bytes can be read or written to the file in any length up to a maximum of p_FMAXSSIZE bytes per read or write. Shared access Any number of processes can open the same file for reading only (but only if all the readers specify P_FSHARE when they open the file). However, the file server does not allow multiple processes to open a channel to the same file for writing. Once a file has been opened for reading, it may not be opened again for writing (but it may be opened again for reading). Once a file has been opened for writing, it may not be opened again for reading or for writing. If you have an multi-process application design which needs to update shared data, consider one of the following: = put the shared data in a named segment (see the chapter Memory Allocation) = access the shared file data via a server process (see the chapter Processes and Inter-Process Messaging) Example of binary file access The following example, which compares the contents of two files, uses many of the functions described in this chapter. 140 11 FILES #include typedef struct € INT ret; VOID *chan; LONG len; TEXT name [{P_FNAMESIZE]; UBYTE buf (P_FBLKSIZE]; > FILE_DATA; LOCAL_D FILE_DATA 1=(O,NULL?; LOCAL_D FILE_DATA f2={O,NULL}; LOCAL_C VOID CleanUp(TEXT *msg, FILE_DATA *pf) € TEXT bb[E_MAX_ERROR_TEXT_SIZE]; p_close(pf->chan); pf->chan=NULL; if (pf->ret<0) { p_errs(&bb[0) ,pf->ret); p_printf("Failed to %s %s (%s)",msg, &pf->name [0] , &bb(01 ); pf->ret=0; > > LOCAL_C VOID Exit(TEXT *msg) { if (fl.ret>=0 && f2.ret>=0) p_printf(msg); CleanUp(msg,&f1); CleanUp(msg,&f2); p_leave(0); > LOCAL_C VOID OpenFile(FILE_DATA *pf, TEXT *name, TEXT *related) { LONG pos; if (pf->ret=p_fparse(name, related, &pf->name [0] ,NULL)) Exit("parse"); if (pf->ret=p_open(&pf->chan, &pf->name [0] ,P_FSTREAM|P_FSHARE |P_FRANDOM)) Exit("open"); pf->len=0L; p_seek(pf->chan,P_FEND,&pf->Len); pos=0L; p_seek(pf->chan,P_FABS,&pos); } LOCAL_C VOID ReadFile(FILE_DATA *pf) € pf->ret=p_read(pf->chan,&pf->buf [0] ,sizeof(pf->buf)); if (pf->ret!=E_FILE EOF && pf->ret<0) Exit("read"); > 141 PLIB REFERENCE ————_— ee LOCAL _C VOID CDECL CompareFiles(TEXT *filel, TEXT *file2) € OpenFile(&f1,file1,NULL); OpenF i le(&f2, file2,&f1.name[0)); P_printf("Compare %s (%ld)",&f1.name (0), f1.len); P_printfc" with Xs (4ld)",&f2.name [0], f2. len); if (f1.len!=f2. len) Exit("Files are of different length"); FOREVER € ReadFilec&f1); ReadFilec&f2); if (fl.ret==€_FILE_EOF && f2.ret==E_FILE_EOF) { fl.ret=f2.ret=0; Exit("Files are identical"); > if (p_bemp(&f1.buf [0] ,f1.ret,&f2.buf [0], f2.ret)) Exit¢"Files are different"); > > GLDEF_C INT main¢VOID) { TEXT *p; TEXT bb{P_FNAMESIZE] ; while (p_geti("Enter ? ",&bb(0] ,P_FNAMESIZE)) € p=p_skipch(&bb[0] ); if (*p) *pr+=0; p_enter((VOID *)CompareFiles,&bb[0] ,p skipwh(p)); > return(0); > The program solicits two file names to compare (where the second name is parsed with the first name as a related name in CompareFiles) and reports whether they are the same or different. As well as illustrating binary file access, the example gives a realistic illustration of the use of p_enter and p_teave. Note also that the function Cleanup takes advantage of the fact that p close(NULL) is harmless. INT p_open(VOID **ppfcb, TEXT *name, UINT mode); Open a channel to the file specified by the zero terminated file specification name and, if successful, return zero and write the channel to *ppfcb (ppfeb is not written to if the open fails). To open a file to be manipulated as a flat binary file, mode should be or'ed with P_FSTREAM. The file specification name is parsed with a NULL related file name (see p_fparse). If this fails, the open fails and returns the return value from p_fparse. The mode in which the file is opened is selected by oring in one (and only one) of the following bit fields into mode: P_FOPEN Open an existing file. If the file does not exist, the error E_FILE_NXIST is returned. This option would normally only be used to open a file for read access. P_FCREATE Create a file which must not already exist. If the file does exist, the error E_FILE_EXIST is returned. To enable write access to the file you must specify P_FUPDATE (described below). P_FREPLACE If the file exists, open it and truncate it to zero length. If the file does not exist, then create a file. To enable write access to the file you must specify P_FUPDATE (described below). 142 11 FILES P_FAPPEND This is the same as for P_FOPEN except that the initial current position is set to the end of file such that the next write will append to the file. It is not necessary to specify p_FRANDOM (described below) but to enable write access to the file you must specify p_FuPDATE (described below). P_FUNIQUE Create a unique file using the passed path as the related path name in which to create the file. The unique file name is written back to name (there should be room for P_FNAMESIZE bytes). It is not necessary to specify p_FUPDATE (described below). The access which is subsequently permitted is formed by oring together a combination of the following flags in mode: P_FUPDATE specifies that write access as well as read access is required for the file. If an attempt is made to write to a file when this flag has not been set, the error E_FILE_RDONLY is returned. P_FRANDOM specifies that random access (as opposed to sequential access) is required for the file. If a call is made to p_seek with a file that has not been opened with this option, the error &_FILE_INV is returned. You should not specify p_FRANDOM unless you do intend to use p_seek because the file system may be able to optimise the device access if it knows that only sequential access is required. P_FSHARE specifies that this open should not block the file from being opened again for read access. If this flag is not set, a subsequent request to open a file (by the same or another process) will fail with E_FILE_LOCKED. Note that shared write access is not supported and p_FSHARE can not be combined with p_FuPDATE. Returns zero if successful or a negative error number if it fails. As well as the p_fparse error numbers which may be returned if name fails to parse, p_open(P_FSTREAM) Can return the following negative error numbers: £_GEN_NOMEMORY failed to allocate memory for the control block E_GEN_ARG mode contains an illegal combination of flags £_FILE_DEVICE the device does not exist E_FILE_NOTREADY the device does not contain a medium E_FILE_EXIST attempt to create a file which already exists E_FILE_NXIST attempt to open a file which does not exist E_FILE_ACCESS access to the file in the requested mode is not available E_FILE_DIRFULL there is no more room in the root directory E_FILE_PROTECT attempted to create/replace when the medium is write protected or is read-only (eg a ROM SSD) E_FILE_FULL there is no room on the device to create/replace this file E_FILE_LOCKED the file is already open (possibly by another process) E_FILE_DEVICE the device in name does not exist E_FILE_DIR the directory in name does not exist INT p_close(VOID *pfcb); Close the binary file channel pfcb and return zero if successful. If pcb is NULL, just return zero. Although p_close can return an error, it will always succeed in closing the channel (and pfcb should not be used subsequently). If the file system buffers written data, the close operation may need to perform one or more write operations on closing. Even if written data is not buffered, if data has been written to the file, the close will update the modification date on the file. Because of this, p_ close can return the some of the errors numbers which can be returned from p_write and p_fdate. However, the failure to flush the data or to 143 PLIB REFERENCE write the new date will not cause the close operation to be aborted although the failure will be reported by an error return. Carefully written applications avoid this problem by using p_iow(P_FFLUSH) to flush the data and to apply the date (and taking appropriate action if this fails) before closing the channel without risk of failure. The Loc:: file system on SIBO machines does not buffer written data. When EPOC is running on a PC, the Loc:: file system is built over the MSDOS filing system which does buffer written data. INT p_read(VOID “pfcb, VOID *buf, UINT len); Reads ten bytes or the number of bytes remaining before the end of file, whichever is smaller, from the current position of binary file channel pfcb and writes the data to buf. The current position is incremented by the number of bytes read. If the current position is already at the end of file, zero bytes are read and the negative error number E_FILE_EOF is returned. The parameter len must not be greater than P_FMAXSSIZE (16K bytes). The most efficient way of processing files is to read in multiples of P_FBLKSIZE (512) while ensuring that the file position remains on P_FBLKSIZE boundaries. The function returns the number of bytes written to buf if successful or one of the following negative error numbers: E_FILE_EOF end of file encountered E_FILE_ABORT the SSD containing the file is or was previously not present and the channel] is now in the abort state &_FILE_READ failed to read from the file (eg because of a CRC failure on a floppy disk) INT p_write(VOID *“pfcb, VOID *buf, UINT len); Write len bytes from buf to file channel pfcb at the current file position. The current position is incremented by the number of bytes written. The parameter len must not be greater than P_FMAXSSIZE (16K bytes). The most efficient way of processing files is to write in multiples of P_F8Lks1Ze (512) while ensuring that the file position remains on P_FBLKSIZE boundaries. When writing to Flash SSDs, you can physically overwrite a single byte (where len is one) provided that the new byte can be written by just clearing bits in the old byte (if this is possible, the file modification date is not changed). In general, overwriting data on a Flash SSD-based file will consume space and reduce the remaining capacity of the SSD. The function returns zero if successful or one of the following negative error numbers: E_FILE_FULL not enough room on the device for the data E_FILE_RDONLY the file channel was opened without the P_FUPDATE access bit set E_FILE_ABORT the SSD containing the file is or was previously not present and the channel is now in the abort state E_FILE_WRITE failed to write to the file (eg because of a CRC failure on a floppy disk) INT p_seek(VOID *pfcb, INT sense, LONG *ppos); INT f_seek{VOID *pfcb, INT sense, LONG *ppos); Set the current file position of file channel pfcb to a new file position which depends on sense and *ppos, where sense is one of: P_FABS to set the current file position to *ppos 144 11 FILES P_FEND to set the current file position to *ppos relative to the current end-of-file position P_FCUR to set the current file position to *ppos relative to the current file position If successful, the new file position (which may be the same as the old) is written to *ppos and p_seek returns zero. Otherwise, it returns one of the following negative error numbers: E_FILE_INV the channel was not opened with p_FRANDOM E_FILE ABORT the SSD containing the file is or was previously not present and the channel is now in the abort state If the channel was opened with P_FSTREAM_TEXT on a remote file system, the full p_FseEk functionality may not be supported. When this is the case, p_seek fails with E_FILE_1INv. However, the following is always supported on P_FSTREAM_TEXT channels: = using P_FABS to set the current position to zero = using P_FEND with a zero relative position to set the current position to the end of the file (although with P_FSTREAM_TEXT you should not use the value which is written to *ppos) If calling p_seek results in an absolute file position which is negative then the file is positioned at the beginning of the file. If the absolute file position is greater than the end of file then the new position is set to the end of file. See p_iow(P_FSETEOF) for setting a new end of file. The function f_seek is identical to p_seek except that, if there is an error, it calls p_leavecerr) rather than return the negative error number err. For example, if the current file position is 0x2000 then LONG pos; pos=256L; p_seek(pfcb,P_FCUR,&pos); sets the current position to 0x2100 and pos=-256L; p_seek(pfcb,P_FCUR,&pos) >; sets the current position to 0x1f00 and pos=O0L; p_seek(pfcb,P_FCUR, &pos); does not change the current position but could be used to sense the current position (pos contains 0x2000 after p_seek has returned). If the current end of file is 0x4000 then pos=0L; p_seek(pfcb,P_FEND, &pos); sets the current position to the end of the file and senses the length of the file (pos contains 0x4000 after p_seek has returned). To set the current position to the beginning of the file, use: pos=0L; p_seek(pfcb,P_FABS,&pos); p_iow{P & INT p_iow(VOID “pfcb, P_FFLUSH); __ Flush internal file buffers Flush all buffered written data to binary file channel pfecb and write the file's modification date. Returns zero if successful (the negative error number returns are as for p_write). The amount of information which is flushed depends on the file system and on the medium in the drive. The RAM and Flash SSD PDDs do not buffer any file data (in this case, calling p_iow(P_FFLUSH) just writes the file date). On Flash SSDs the last record written is held open until the file is closed or a write occurs to another file on the same SSD (see the section Flash SSDs at the beginning of this chapter). When EPOC is running on a PC, written file data is buffered to improve performance. Calling p_iow(P_FFLUSH) will ensure that any buffered data is written to the file but repeated flushing is likely to degrade performance. 145 PLIB REFERENCE Although harmless, there is absolutely no benefit in calling p_iow¢P_FFLUSH) on file channels which were opened without the p_FUPDATE flag. INT p_iow(VOID *pfcb, P_FSETEOF, ULONG *peof); Set the logical end of file on channel pfcb to position *peof and return zero if successful (the negative error number returns are as for p_write). The the file channel must have been opened with P_FUPDATE. If *peof is greater than the current end of file, the file size is extended to *peof. On block-structured devices (ie excluding Flash SSDs), this is equivalent to appending indeterminate data to the end of the file to pre-allocate storage. (It is not useful to extend a file on a Flash SSD.) The current position is not affected when the file is extended with p_iow(P_FSETEOF). If *peof is less than the current end of file, the file is truncated to *peof (the current position is also reduced if necessary to the new end of file). INT p_iow(VOID *pfcb, P_FCANCEL); Cancel all asynchronous I/O requests on channel pfcb and return zero. The operation is harmless if no request is pending. In fact, for reasons which were described at the beginning of this chapter (under the heading Asynchronous file operations), a call to p_iow(P_FCANCEL) has no effect even when a request is pending. However, if you are performing asynchronous file access (which is not at all common) you may (for the sake of consistency and provided the status word is currently E_FILE_PENDING) Use p_fow(P_FCANCEL) - normally followed by a call to p_waitstat - to effect cancellation as you would do for any other asynchronous request. It is always possible that a future implementation of the file server may process P_FCANCEL operations. At the time of writing, a preferable alternative is to simulate a cancel service, as illustrated in the earlier example, under the heading Asynchronous file operations. SSS SSS ee eee ee ee ee A ar a Stream text file access Different file systems vary in their support of a text file type and (if such support is provided) implement text files in different ways. Some systems (for example, DEC's VMS operating system which runs on a Vax) support text file types that are not implemented as crLF terminated records. Other systems (such as MSDOS, UNIX and the EPOC Loc:: file system on a SIBO machine or a PC) do not support a text file type, and on such systems the following convention predominates: = text records are terminated by a CRLF sequence - a carriage return (code 13) followed by a line feed (code 10) ® a file may optionally be terminated by a sus character (code 26) The terminating sus is not necessary on systems that store a logical file length and it is falling out of use. Applications running under EPOC that read or write text files can choose either to process the records in a text file themselves, or to take advantage of the system's text file handling. The system support for text files, using p_open(P_FTEXT), is described in the next section of this chapter. Applications that do their own text file processing, for performance purposes or otherwise, should use p_open(P_FSTREAM_TEXT) in preference to p_open(P_FSTREAM). Using P_FSTREAM_TEXT when opening such a text file on the REM:: file system declares the intention that the file is to be considered as a text file. It causes the remote file server to present the data from the file as if it were implemented with crLF terminated records. In more detail, this presentation layer does the following: when reading the text record content on the remote system is converted to that content followed by a crtF 146 11 FILES when writing the data is parsed for crLF record terminators and converted into text records on the remote system (the parser will also recognise cr, LF or LFCR as a record terminator) Although in many cases (and certainly when opening a local file) opening with P_FSTREAM_TEXT has an identical effect to opening with P_FSTREAM, there is no guarantee that the effect will be the same on a remote file. There is no penalty to using P_FSTREAM_TEXT - only a potential gain when accessing a remote file. The upshot of all this is... All this sounds very complicated (and it is - especially for the implementor of the remote file server) but you should trust the system and just remember this: To open a text file to be manipulated as a flat binary file, you should call p_ open with mode or'ed with P_FSTREAM_TEXT and not P_FSTREAM. With the exception of p_open, all functions are exactly as for flat binary files, described in the previous section. INT p_open(VOID **ppfcb, TEXT *name, UINT mode); Open a channel to the file specified by the zero terminated file specification name and, if successful, return Zero and write the channel to *ppfcb (ppfcb is not written to if the open fails). To open a stream text file (a text file to be manipulated as a flat binary file) mode should be or'ed with P_FSTREAM_TEXT. For all other details, see the description p_open(P_FSTREAM) in the previous section. SSS SSS SSS SS ee EET] Text file access To open a file to be manipulated as a record oriented text file, you call p_open with the 3rd parameter mode OR’ed with p_FTEXxT. For example: p_open(&fcb, "fred. Lis",P_FTEXT |P_FSHARE) Opens a text file for reading only. p_open(&fcb, "fred. Lis", P_FTEXT |P_FUPDATE|P_FREPLACE) Creates a text file which may be written to. Once you have opened a text file channel, the possible I/O operations are: p_read to read the next text record from the file channel p_ioc(P_FREAD) p_ioa(P_FREAD) p_write to append a text record to the file channel p_ioc(P_FWRITE) p_ioa(P_FWRITE) p_close to close the text file channel p_seek to set and sense the channel file position p_iow(P_FSETEOF) to set the logical end of file p_iow(P_FFLUSH) to flush the file buffers P_iow(P_FCANCEL) to cancel all pending asynchronous requests (not actively supported) The text file handling is not part of the file server but is implemented as a layer of code over the P_FSTREAM_TEXT binary file access. The text file handling code runs in the caller's context ("on the client side") in the same way as regular function calls. This layer of code is inserted between the application and the file server by the FIL: device driver p_open code when it sees the p_FTEXT bit set in the mode parameter. The data is, however, buffered in this layer, so flushing is necessary to ensure that any buffered data is written to the file. 147 PLIB REFERENCE Implementing the code as a layer over P_FSTREAM_TEXT binary file access makes it independent of the file system node which is providing the services. What text file handling does The text file handling assumes that text files obey the following conventions: ® text records are terminated by a CRLF sequence - a carriage return (code 13) followed by a line feed (code 10) a a file may optionally be terminated by a sus character (code 26) The text file handling also assumes that the record content (excluding the record termination) does not exceed a length of P_FMAXRSIZE (256) bytes. A text record may not contain cR, LF or suB characters, but there is no other restriction on the contents. When reading a file, the text file handling parses a stream of bytes for cRLF record terminators such that the application gets the data a record at a time (excluding the terminator). The parser will also recognise CR, LF, LFCR or a SUB as a record terminator. If it sees a sus (either as a record terminator or immediately after a CRLF, CR, LF or LFCR record terminator), it will behave as if the end of file had been reached. When writing to a file, the text file handling simply adds the record terminator (which is always CRLF) to the end of the record data. A terminating sus is not written. For enthusiasts ... This is for interest only and may certainly be skipped. In case you were wondering what the TxT: device is, text file handling is implemented as an I/O device driver (tTxT:) which layers over the FIL: P_FSTREAM_TEXT mode binary file access (which was described in the previous section). The FIL: device redirects the p_open to TXT: when P_FTEXT is set in mode. This means that the following calls to p_open: p_open(&fcb, "fred.dat",P_FTEXT|P_FUPDATE |P_FREPLACE); p_open(&fcb, "FIL: fred.dat",P_FTEXT|P_FUPDATE |P_FREPLACE); p_open(&fcb, "TXT: fred.dat",P_FUPDATE |P_FREPLACE); are all equivalent. (The above examples are provided to help de-mystify the I/O system and the FIL: device and it would be obscure to use TxT: without good cause.) p_Op INT p_open(VOID **ppfcb, TEXT *name, UINT mode); Open a file to be manipulated as a record oriented text file where mode should be or'ed with P_FTEXT. Except for the use of P_FTEXT in place of P_FSTREAM Or P_FSTREAM_TEXT, the parameters and returns are as for opening a binary file, described in the previous section. INT p_close(VOID *pfcb); Close the text file channel pfcb and return zero if successful. Since text written to the file is buffered in the device driver, p_close may give rise to an E_FILE_WRITE error. It is therefore advisable to call p_iow(P_FFLUSH) before calling p_close. Otherwise, the behaviour and returns are as for closing a binary file, described in the previous section. "Read fro INT p_read(VOID *pfcb, VOID *buf, UINT len); Read the contents of the current record (ie excluding any record terminators) from text file channel pfcb, writing up to Len bytes to buf and, if successful, return the length of the record read (which is also the number of bytes written to buf). The channel is positioned to the next record. If ten is less than the length of the current record, the first ten bytes from the record are read and p_read returns the negative E_FILE_RECORD. The channel is still positioned to the next record. Note that p_read returns zero if it encounters a record of zero length. 148 11 FILES eee After the last record is read, the channel is positioned to the end of the file. When the channel is positioned at the end of the file, p_read returns the negative E_FILE_EOF (nothing is written to buf). Other negative error returns are: E_FILE_ABORT the SSD containing the file is or was previously not present and the channel is now in the abort state E_FILE_READ failed to read from the file (eg because of a CRC failure on a floppy disk) Example LOCAL_D VOID *fcb=NULL; LOCAL_C INT CDECL SearchFile(TEXT *file, TEXT *pattern) € INT ret; TEXT lLine{P_FMAXRSIZE+2] ; if (ret=p_open(&fcb, file,P_FTEXT)) panic("Failed to open file",ret); while ((ret=p_read( fcb,&l ime [0] ,P_FMAXRSIZE))>=0) € line fret] =0; if (ret && p_smatchi(&line[0] ,pattern)) p_printf(&line(0}); > p_close(fcb); if (ret!=sE_FILE_EOF) panic("Failed to read file",ret); return(0); > INT p_write(VOID *pfcb, VOID *buf, UINT Len); Write a record of length Len bytes (where Len is zero to P_FMAXRSIZE inclusive) from buf to the text file channel pfcb. Returns zero if successful or one of the following negative error numbers: E_FILE_FULL not enough room on the medium for the data E_FILE_RECORD the record size exceeds P_FMAXRSIZE E_FILE_RDONLY the file channel is opened without p_FuppATE being set in mode E_FILE_ABORT the SSD containing the file is or was previously not present and the channel is now in the abort state E_FILE_WRITE failed to write to the file (eg because of a CRC failure on a floppy disk) Records are always written to the end of file. The data between buf and buf+len should not include any record delimiters. INT p_seek(VOID *pfcb, INT sense, LONG *ppos); INT f_seek(VOID *pfcb, INT sense, LONG *ppos); Set the current record position of text file channel pfcb to a position which depends on sense and *ppos, where sense is one of: P_FREWIND to position to the first record (the value of ppos is ignored) P_FRSENSE to get the position of the last record read or written P_FRSET to set the record position which was previously got with a call to p_seek(P_FRSENSE) Retums zero if successful or the negative E_FILE_INv if the file was not opened with P_FRANDOM. i te 149 PLIB REFERENCE eee Some remote file systems may not be capable of supporting p_seek(P_FRSET) and p_seek(P_FRSENSE) and in this case p_seek will return E_FILE_INV. The function f_seek is identical to p_seek except that, if there is an error, it calls p_teave(err) rather than return the negative error number err. It is important to note that this function sets the current record position for read operations only. Records are always written at the end of file. Example while ((len=p_read( fcb, &buf [0] ,P_FMAXRSIZE))>0) € buf Clen]=0; if (buf {0}==':') { p_seek(fcb,P_FRSENSE, &pos); StoreLabel (&buf [1] , pos); > > INT p_iow(VOID *pfcb, P_FFLUSH); Flush all buffered written data to text file channel pfcb and write the file's modification date. The behaviour and returns are as for flushing a binary file, described in the previous section. Unlike the binary file, however, data is buffered for all file systems and media. INT p_iow(VOID *pfcb, P_FSETEOF, ULONG *peof); Set the logical end of file on channel pfcb to position *peof. Behaviour and returns are as for flushing a binary file, described in the previous section. INT p_iow(VOID *pfcb, P_FCANCEL); Cancel all asynchronous I/O requests on channel pfcb and return zero. The behaviour and return values are as for cancelling a binary file request, described in the previous section. At the time of writing, a preferable alternative is to simulate a cancel service, as illustrated in the earlier example, under the heading Asynchronous file operations. 150 CHAPTER 12 PROCESSES AND INTER-PROCESS MESSAGING rE = ee ee SS SS a ee Processes A process is a running program. It is normally created by loading an image file (also called an executable) using p_execc. After loading the program, the process creator normally calls p ) presume to Start the process running. EPOC is a single-user multi-tasking operating system that rapidly switches contexts between a number of independent processes - creating, at times, the illusion of multiple processes running in parallel. The maximum number of processes that may exist is E_MAX_PROCESSES (24). At any particular time, one process is actually running. On a reschedule, EPOC runs the process with the highest priority that is ready to run. If there is only one ready process at the highest priority it will continue to run indefinitely (and any lower priority ready processes will wait indefinitely). Preemptive multi-tasking means that the running process may be replaced at any time - it does not have to make a system call to yield the processor. A process which becomes ready will immediately run if it has the highest priority. If there is more than one ready process with the highest priority then each process is made current for a fixed time period (4 system ticks) after which the operating system makes the next process of that priority current in what is called a "round robin" fashion. On a SIBO machine, the system "ticks" 32 times a second. Because EPOC is a single-user system, the current process is comparatively rarely switched out by the system tick. It is more likely to stop running because an event occurred that made a higher priority process become ready or because the current process voluntarily gave up its ready status to wait for an event to occur. In EPOC, a process consists of at least the following: =" a process control block (described below) =“ adata segment, containing the processor stack, static variables and the heap (as described in the Memory Allocation chapter) = a primary code segment (which is shared if there are one or more other processes of the same program) = an I/O semaphore (as described in the chapter Asynchronous Requests and Semaphores) On SIBO machines, a process that tries to write to a memory segment other than its own data segment (except via functions that explicitly allow such activity) is panicked with panic number 60. A code segment may exist in ROM or it may be loaded into a RAM memory segment from an executable. A process may acquire other resources during its lifetime - for example, I/O channels or additional code segments. The system automatically releases owned resources such as memory segments, I/O channels and semaphores when it terminates. Server processes are also designed to clean up client resources when a client process terminates. 151 PLIB REFERENCE System processes When the operating system initialises itself, it creates a number of system processes some of which are essential to the operation of EPOC. After a system reset on a typical machine running EPOC, the system processes (by process name) are: SYSSNULL .$01 the zero priority null process runs when no other process is ready to run and switches the machine off (to reduce power consumption) after a period of inactivity SYSSMANG.$02 the supervisor has a higher priority than any other process and performs many critical system functions including memory segment moving and resource clean-up when a process terminates SYSSFSRV.$03 the file server (described in the Files chapter) has the second highest priority and performs all file related operations, including loading an image to create a process SYSSWSRV.$04 the window server (described in the Window Server manual) provides shared access to the screen, keyboard and, if present, the digitiser (or mouse on a PC) SYSSSHLL.$05 the shell process provides a user interface that allows other processes to be started (on a custom system it might provide a "turnkey" environment) The extension of the process name gives the process number (assigned by the operating system). The structure of process names is described later in this chapter. The functions (described in this chapter) which change the process name, change the process priority and suspend a process will fail when applied to sYSSNULL, SYSSMANG and SYSSFSRV. On systems with a digitiser (or mouse), the window server creates a subsidiary process, sharing its data segment, to draw the mouse icon. In EPOC, a subsidiary process that shares the data segment of its owner is called a task and the window server task has a name such as SYS$WSRV.205. The notifier service p_notify (described in the chapter Error Handling) may be provided by the window server itself or it may be provided by a client of the window server. If a separate notifier exists, it has a process name such as SYS$NTFY.$07. Process ID and process control block Each process is identified by its process ID - a positive 16-bit number containing two bit fields: = The least significant 12 bits is the offset of the process control block in the operating system data segment (also called the process slot). = The most significant 4 bits contains a value in the range 0-7 (note that a valid process ID is positive). This value is incremented modulo 8 each time a process slot is used so that a process ID may be rejected after a process has terminated. The function p_getpid returns the process ID of the caller. If a system function is passed a process ID that is zero or negative, or in which the least significant 12 bits is outside the range of the process table, the caller is panicked with panic number 7 (invalid process ID). 152 12 PROCESSES AND INTER-PROCESS MESSAGING rr The structure of the process control block is defined by the E_proc struct, defined in epoc.h as: typedef struct e_proc { struct e_proc *next; struct e_proc *prev; WORD queKey; WORD queData; UBYTE deltaType; UBYTE addressTrap; UBYTE status; UBYTE sstatus; UBYTE priority; UBYTE priorityH; UBYTE ramOrRom; UBYTE isTask; UBYTE name [E_MAX_NAME+1]; UBYTE active; UWORD semaphore; UBYTE *semHead; UBYTE *memBasePtr UWORD memGrowBy; UBYTE *mCtriPtr; UWORD minHeap; HANDLE fServer; HANDLE dataSeg; HANDLE codeSeg; UBYTE *saveSP; UBYTE *saveBP; UBYTE notify; UBYTE sndSem; UWORD magic; UWORD checkSum; UWORD terminate; > E_PROC; A copy of a process control block may be obtained by calling p_getosd (where E_PIDMASK is used to mask out the address portion fro E_PROC pcb; m the process ID) as follows: p_getosd(&pcb, (VOID *)(pid&E_PIDMASK),sizeof(pcb)); Many of the fields of &_proc are described in the course of this chapter. The remaining fields are reserved for system use. As an aside, and for the reader's interest, some miscellaneous fields (which would not otherwise be described) are described below. It must be emphasised that none of these fields should be modified directly by any application code. sstatus ram0rRom isTask active semaphore memBasePtr memGrowBy mCtriPtr minHeap TRUE if the process is waiting to be suspended (for example, if suspended while on the time delta queue - on leaving the queue it will be suspended rather than entering the ready queue) TRUE if the process code segment is in RAM, FALSE if the code segment is in ROM TRUE if the process is a task (a subsidiary process that shares the data segment of its creator) TRUE if the running of this process will stop the machine from switching off - as set by p_marka and p_unmarka the handle of the process I/O semaphore the address of the start of the heap (4 bytes before that obtained from p_allspace) the heap granularity in paragraphs as set by p_hgran contains the address of the message control block as set up by p_minit or zero if p_minit has not been called the minimum heap size in paragraphs 153 PLIB REFERENCE —_ See fServer the client ID as given by the file server or zero if the process has not connected to the file server dataSeg the handle of the process data segment codeSeg the handle of the process code segment if the code is in RAM or the paragraph address of the code segment if the code is in ROM notify the notifier state as set by p_sentnoti fy (and sensed by p_getnoti fy) sndSem TRUE if the process is waiting on the sound semaphore, to indicate the need to call p_signal on termination of the process magic the top 4 bits of the process ID, used to reject a process ID of a process that no longer exists - even when another process has been created and re-uses the same process slot terminate contains the termination message type as set by p_onterminate or zero if p_onterminate has not been called Process states Each process is in one of the following states (as stored in pcb. status): E_PROC_CURRENT the process that is currently running (at any time, one process is in this state) E_PROC_READY the process is waiting for a chance to run E_PROC_SEMAPHORE the process is waiting on a semaphore, most likely its I/O semaphore pcb. semaphore (described in the chapter Asynchronous Requests and Semaphores) E_PROC_DELTA the process is waiting in the timer delta queue (described in the chapter Time, Timers and Dates) E_PROC_SUSPENDED the process exists but will not run until it is resumed by calling p_presume Process queues Processes that are in the READY, SEMAPHORE OF DELTA state are in a doubly-linked queue (using pceb.next and peb.prev - see queues in the chapter Characters, Strings, Buffers and Queues). The READY queue is ordered by the process priority as stored in peb.priority. Each semaphore heads a SEMAPHORE queue. This queue may be empty, or may contain one or more processes in a first-in, first-out order. The DELTA queue is a special kind of doubly-linked queue (called a delta queue, as described in Characters, Strings, Buffers and Queues). It stores the time interval in system ticks between timer entries. These queues are described in more detail] in the chapter Asynchronous Requests and Semaphores. Process priorities When there is more than one process that is ready to run, the operating system runs the process with the highest priority (an unsigned byte value in the range 1 to 255). Lower priority processes are blocked indefinitely. If there is more than one ready process with the same highest priority, they take it in turns to run every four system ticks. Applications should set their priority in the range E_MIN_PRIORITY (64) to E_MAX_PRIORITY (192) inclusive (the operating system reserves the values outside this range). The initial process priority is normally taken from a value stored in the program file or image (and is generated by the tool used to build the image) but it may subsequently be changed using p_setpri (p_getpri returns the priority of a process). Interactive application processes that are clients of the window server are normally created at the priority E_PRIORITY_FORE (128) and subsequently leave it to the window server to change their priority depending on whether the process is receiving user input or not. See the Window Server manual. The supervisor runs at priority 248 and the file server at priority 240. A hardware interrupt runs at the same priority as the process it interrupts. Critical sections of interrupt code are protected by switching off pre-emption. a ee ee ae 154 12 PROCESSES AND INTER-PROCESS MESSAGING SSS Preemptive scheduling It is possible for a process to run for as long as it wants to and, in practice, this often happens. In this case, the current process eventually gives up its curRENT state by changing its state to: SEMAPHORE by calling p_iowait to wait on the I/O semaphore (or p_wait to wait on any semaphore) DELTA by calling p_sleep, p_sleept or p sleepa SUSPENDED by calling p_psuspend on its own process ID However, many events can cause a reschedule in which the current process is preempted by a higher priority process without completing its course. Such events include: ® the fourth consecutive system tick = a semaphore being signalled (eg as a result of user input) which releases a higher priority process = the expiry of a higher priority process in the timer DELTA queue The current process may, by its own action, cause itself to be preempted by: " — signalling the I/O semaphore of a higher priority process (by calling p_iosignalbypid or, for example, by sending it an inter-process message) = calling p_presume to release a higher priority process from the SUSPENDED state (especially after loading a process from an image) = raising the priority of a another process by calling p_setpri = lowering its own priority by calling p setpri Process names A process name takes the following form: where the component contains between one and eight characters and the component consists of a period, a $ and a two digit number of the form 01, 02, 03 .... This number gives the index (starting from 1) of the process slot. If the process is a task (a subsidiary process that shares the same data segment), the $ is replaced by aa where, except for the a, the process name is otherwise the same as the creator of the task. The maximum length of a process name is E_MAX_NAME (12), excluding the zero terminator (buffers normally allow E_MAX_NAME+2 bytes to include the zero terminator and to keep following variables on an even address). When a process is created by loading an image using p_execc, the process is created with a taken from the file name of the image. For example, if a program called Loc::\B:\UTILS\SORT. IMG is loaded twice the process names might be: SORT .$07 SORT .$11 The component of a process name guarantees that the process name is unique. Note that the process data segments are given the same names (see the chapter Memory Allocation) but that these segment names are held independently of the process name. To convert a process name into a process ID, you use p_pidfind. Since a program cannot anticipate the component, p_pidfind allows wild card characters in a match string. For example, to get the process ID of a database server process that was loaded from db$serv.img, you would use p_pidfind("DBSSERV.*"), When there is the prospect of more than one process of the same (because a program was loaded more than once), p_pfind may be used to get the process IDs of all instances. In the following example, the general purpose function ProcsofThisProg returns the number of processes having the same process name (excluding the extension) as this process (that is, it returns the number of processes of the calling program). 155 PLIB REFERENCE SSS GLDEF_C INT ProcsOfThisProg(VOID) € TEXT *pp; HANDLE h; INT count; TEXT mm{E_MAX_NAME+2) ; TEXT bb[E_MAX_NAME+2] ; p_pname(p_getpid(),&mm[0] ); pp=(&mm[p_slocr(&mm(0],'.")+1]); *pptt=!*'; *pp=0; for (h=0,count=0;(h=p_pfind¢h, &mm[0] , &bb[0] ))>=0;count++); return(count); > The function gets the name of this process using p_pname and p_getpid and builds a match string in m1) by replacing the extension with the '*' wildcard. It then uses this match string to count the number of matching processes. This function could be used to stop more than one process of a program (especially a server) from being executed where, for example, if ProcsofThisProg returns more than one, the server program could panic. Reserved statics The reserved statics (otherwise known as magic statics) are variables with fixed, known, addresses, existing in the process data space between addresses 0x00 and 0x40. They are 'magic’ in the sense that they are accessible from all parts of the process code, even from dynamic library code (which does not have any data space and therefore may not, normally, access statics). Some of these variables are used by the operating system as part of the process context. Others are used by system code such as the window server, and the graphics user interface libraries. Such usage differs from machine to machine in the EPOC range; you will find a description of any such usage in the Programming Guide for the appropriate machine or in user interface library documentation. The remaining reserved statics are freely available for use by the process code. A common use is to provide access from dynamic library code to application-specific data, without the need for it to be passed in function parameters. 0x00 DatWordDead The word at this address contains the value 0xDEAD. This value must not be changed by application code. Many operating system calls check for this value and will panic the process with panic reason code PanicDead0o if it has changed. A change in this value is symptomatic of a common software bug; the unintentional use of a NULL pointer. Ox02 DatHandNext The data at these two addresses are used as pointers to a queue of wait handler 0x04. DatHandPrev function descriptors. This data should not be modified by application code. 0x06 DatCountrySeg This holds the segment handle of the data segment containing the country and language specific fold tables for the process. This data should not be modified by application code. 0x08 DatClassHandle In applications which use any of the object oriented programming calls (this Ox0a DatClassPtr includes use of Hwif programs) these locations hold data required by the operating system to work out how to send a message to an object's superclass. It is recommended that application code does not modify the contents of these locations. Ox0c DatEClassHandle In applications which use any of the object oriented programming calls (this Ox0e DatEClassPtr includes use of Hwif) these locations hold data required by the operating system to work out how to perform a p_exactserd. It is recommended that application code does not modify the contents of these locations. 0x10 DatEnterFrameptr | The enter and leave mechanism provided by the operating system uses this address to store the pointer to the last enter frame generated on the stack. When a leave occurs this pointer is used to unwind the stack and restore the register set. This location should not be modified by application code. Ox12 w_ws In applications that use system user interface libraries this location is assumed to hold the object handle of an instance of the 'wserv' object. The application is responsible for ensuring that this location is set up correctly, normally by calling system code on application start-up. 156 12 PROCESSES AND INTER-PROCESS MESSAGING _- eee >: 0x14 w_am 0x16 wClientData 0x18 wserv_channel Oxia T Oxic r Oxte DatOsFramePtr 0x20 DatATFlag 0x21 DatHeapLocked Ox22 DatProcessNamePtr 0x24 DatCommandPtr 0x26 DatTest 0x28 DatApp1 Ox2a DatApp2 Ox2c DatApp3 Ox2e DatApp4 0x30 DatApp5 0x32 DatAppd 0x34 DatApp7 0x36 DatDialogPtr 0x38 DatGate Ox3a DatLocked Ox3c DatStatusNamePtr In applications that use the application manager object (all object oriented programs and Hwif programs) this location is assumed to hold the object handle of an instance of the application manager object. The application is responsible for ensuring that this location is set up correctly, normally by calling system code on application start-up. These locations are used by the window server process to store information about your application. If you use any graphics functions you should not modify the contents of these locations. This location is used by the OPL language translator. If your application does not use OPL then this location is free for use by application code. This location is used by the OPL language runtime code. If your application does not use OPL then this location is free for use by application code. This location contains a pointer to the last member of a linked list of operating system calling frames. Following this linked list will show which functions called which operating system services. This location should not be modified by application code. This byte location contains the current address trap status for the process. Certain operating system calls cause address trapping to be tured on or off and the operating system sets or clears this flag to record the current address trap hardware status. Just setting the flag to zero will not disable address trapping. This location should not be modified by application code. This byte location contains the current state of the heap locked flag. While the heap is being modified (for example, being resized, due to a memory allocation request) the heap is locked. This prevents the operating system from compressing the segment while the data structures used by the operating system to run the heap are in an inconsistent state. This location should not be modified by application code. System user interface library code assumes that this location contains either NULL Or a pointer to a zero terminated string that the application wishes to be displayed as its name. Otherwise it is free for use by application code. This location contains a pointer, set up by the operating system. It points to an alloc cell that contains the full path name, as a zero terminated string, of the .img or .app file from which the process was loaded. Immediately following the zero terminator, the alloc cell contains leading byte counted initial command line data. Psion's test system library code assumes that this location contains the object handle of the test code. Otherwise it is free for use by application code. These locations are free for application code to use. System user interface library code may assume that this location contains a pointer to the current dialog structure. Otherwise it is free for use by application code. System user interface library code may assume that this location contains an object handle. Otherwise it is free for use by application code. System user interface library code may assume that this location contains a flag indicating whether the application is capable of receiving termination or switch files messages. Otherwise it is free for use by application code. System user interface library code may assume that this location contains a pointer to a zero terminated string that will appear in a status window. Otherwise it is free for use by application code. 157 PLIB REFERENCE Ox3e System user interface library code may assume that this location contains a DatUsedPathNamePtr pointer to a fully parsed file name. Otherwise it is free for use by application code. Shared code segments When a second or subsequent process of the same program is loaded, the existing code segment (which has segment name .$Sc) is shared and not loaded from the image file (however, the initialized static data values are still loaded into the created process data segment). The system checks that the contents of the existing code segment matches the code in the image file by comparing pcb.checkSum with the checksum stored in the image header (the checksum in the image header is also used to check the integrity of the code when it is loaded). If the checksums do not match, the load fails. It follows that you cannot run different programs of the same name (or different versions of the same program) at the same time. Image files An image file is a form of executable (program) file that is run by calling p_execc. It normally has a . IMG file name extension. Application files (with a .app extension) are a particular type of image file. An image file is created by applying the emake. exe tool to a DOS executable. This is normally done automatically as part of the build process that generates an EPOC application program (for an example of the use of emake.exe, see \ts\sys\tsprj.txt). The creation process adds an ImgHeader struct (defined in epoc.h) to the front of the file and may optionally concatenate a number of other files into the image file. The ImgHeader struct is effectively defined as: #define SignatureSize 16 #define MaxAddFiles 4 typedef struct € UINT offset; UINT Length; > ADDFILE; typedef struct € UBYTE Signature [SignatureSize] ; UINT ImageVersion; UINT HeaderSizeBytes; UINT CodeParas; UINT InitialIP; UINT StackParas; UINT DataParas; UINT HeapParas; UINT InitializedData; UINT CodeCheckSum; UINT DataCheckSum; UINT CodeVersion; UINT Priority; ADOFILE Add [MaxAddFiles] ; UINT DylCount; ULONG DylTableOffset; UINT Spare; > ImgHeader; The meanings of the elements of this struct are as follows: Signature contains the string "ImageFileType**". ImageVersion the version number of the software tools used to create the image file. At the time of writing the version is 2.00F (ox200f) HeaderSizeBytes the offset of the start of the executable code within the image file CodeParas the required size, in (16 byte) paragraphs, of the memory to be reserved for the code segment 158 12 PROCESSES AND INTER-PROCESS MESSAGING _ -.CrvrerwrOrOaOaOwl ee eee Initial IP StackParas DataParas HeapParas InitializedData CodeCheckSum DataCheckSum CodeVersion Priority Add DyLCount DylTableOffset Spare the initial instruction pointer offset in the executable code - determined by the linker, (and normally 0) the size, in (16 byte) paragraphs of the stack - determined by which PLIB C startup module is linked into the program (see the discussion of startup modules in the Introduction chapter) the total size, in (16 byte) paragraphs, of the declared static data (including the initialised data - see below) the required size, in (16 byte) paragraphs, of the initial - and minimum - alloc heap, user definable via a parameter to emake. exe (see below) the size, in bytes, of the initialised static data internally generated and verified checksum on the code internally generated and verified checksum on the data the version number of the executable code, user definable via a parameter to emake. exe (see DbfVersion in the chapter Database Files for the form of a version number) the start-up priority of the process that will be created from this image file, user definable via a parameter to emake. exe (default value is 0x80) an array of four ADDFILE structs, describing up to four additional files included within the image file (common included files, in .APP files, are an icon graphic and a resource file) the number of object dynamic libraries (DYLs) concatenated into the image file. See also the Object Oriented Programming chapter of this manual. the offset within the file of the start of an array of DYLENTRY structs (defined in epoc.h) with dytcount entries, giving the names and file offsets of the included DYLs reserved The contents of this header may be displayed by applying the edump.exe tool to an image file. For efficiency, the value of HeapParas should be adjusted to be equal to, or slightly larger than, the heap space used by the running process immediately after it has started. Setting a smaller value means that the heap will have to be grown one or more times during the initialisation of the process. Growing the heap may involve moving large amounts of in-memory data and may, therefore, significantly increase the process start-up time (see also The heap allocator in the chapter Memory Allocation). SSS eae SS ee eS ee ne eee Process termination The subject of process termination (with associated functions) is described in detail in the chapter Error Handling. The functions described include those that terminate a process: p_pterminate or p_pkill P_ppanic to terminate another process (typically in response to a user request) to panic another process (normally following unreasonable behaviour from the process being terminated) and those functions that request notification of the termination of other processes: p_logona p_logon p_watchal L to be signalled when the specified process terminates to receive an inter-process message when the specified process terminates (convenient for server processes to keep track of their clients) to receive an inter-process message when any process terminates (only one process can call p_watchall - normally the Shell to monitor the termination of all processes) 159 PLIB REFERENCE When a process terminates, pcb.status in the process control block (which normally holds the process State) is set to E_PROC_FREE to mark it as available for use by a new process. When a newly created process re-uses the slot, pcb.magic is incremented modulo 8, as described above. i = ee ee ee ee ee a Creating a process HANDLE p_execc(TEXT *pName, VOID *pCommand, INT Length); Create a suspended process from the image file described by the zero terminated file Specification pNname and, if successful, return the positive process ID of the created process. The created process is suspended so that the creator can perform any initialisation (eg to set the process priority) before allowing the process to run by calling p_presume. If the created process has a higher priority than the creator, the call to p_presume may not return for some time. The file specification pName is parsed with a related name of ".1MG". The function allocates a command cell from the heap of the created process. This cell is loaded with the following sequence: = acopy of the zero terminated full file specification resulting from the parse of pName = abyte containing length = acopy of the Length bytes from pCommand where length must be between zero and E_MAX_COMMAND_BUFFER (127) inclusive. If pcommand is NULL, Length is taken to be zero, whatever is passed. The address of this command cell is written to the reserved static DatCommandPtr in the created process data segment. This variable is accessed from C by declaring: GLREF_D UBYTE *DatCommandPtr; Note that there is no restriction on the data in pcommand; it may include binary data structures as well as text. For example, it is often useful to pass the process ID of the caller in the command line. The name of the created process is taken from the file name component (excluding any extension) of pName. If a process, loaded from pName, already exists (as determined by a name and checksum match), the created process shares the loaded code segment. In this case, the code is not reloaded from pName (however, the initialized static data values are still loaded into the created process data segment). The function returns a positive process ID if successful or a negative error number if it failed. As well as the p_fparse error numbers that may be returned if the parse of pName fails, p_execc can return the following error numbers: E_GEN_NOMEMORY insufficient free system memory E_FILE_DEVICE the device in pName does not exist &_FILE_NOTREADY the device in pName does not contain a medium E_FILE_DIR the directory in pName does not exist E_FILE_NXIST the file in pName does not exist E_FILE_EXIST a process of a different program (ie with a different checksum) with the same name already exists E_GEN_IMAGE the file is not a valid image or if the image is corrupt E_GEN_NOPROC there are no more free process slots (the maximum process limit has been reached) E_GEN_ARG the total size of the data, stack and heap exceeds OxFFEO bytes or Length exceeds E_MAX_COMMAND_BUFFER 160 12 PROCESSES AND INTER-PROCESS MESSAGING eee The loading of an image is performed by the file server (see the Files chapter) and the caller must have connected to the file server before calling p_exece (otherwise the caller is panicked with panic number 41). The standard PLIB library connects to the file server before calling main. Example #include LOCAL_C VOID Exec(TEXT *name, TEXT *cmd, INT len) { INT ret; TEXT bbIE_MAX_ERROR_TEXT_SIZE]; ret=p_execc(name, cmd, len); if (ret<0) € p_errs(&bb{[0] ,ret); P_printf("Failed to start %s (%s)",name,&bb[0] ); > else { P_printf("Process ID is %x",ret); p_presume(ret); > } GLDEF_C INT main(VOID) { TEXT *p1,*p2; TEXT bb[64]; while (p_getl("Enter ? ",&bb[0] ,64)) { p1=p_skipwh(&bb [0] ); if (!*p1) continue; p2=p_skipch(p1); if (*p2) { *p2++=!'\0': p2=p_skipwh(p2); > Exec(p1,p2,p_slen(p2)+1); > return(0); > The program solicits a line of input and parses out the name of the image to run and a text string to pass to the created process (which is passed with the zero terminator). If this program is used to run dummy.img in the default path (which happens to be toc: :p:\) by entering: dumny fred 161 PLIB REFERENCE and the code for dummy.img is: #include GLREF_D UBYTE *DatCommandPtr; LOCAL_C VOID PrintCommandPtr(VOID) € UBYTE *p,*pe; UINT Lcell; if (!DatCommandPtr) p_panic(0); (cel l=p_alen(DatCommandPtr); p_printc"%d [",lcell); for (p=DatCommandPtr,pe=ptlcell;p"", *p); p_printt("]"); > GLDEF_C INT main(VOID) € PrintCommandPtr(); p_getch(); return(0); } then dummy.img prints: 24 [LOC::D:\DUMMY. IMG<00><05>fred<00>) INT p_execcasync(TEXT *pName, VOID *pCommand, INT length, WORD *pStatus, HANDLE *pPid); This is the asynchronous version of p_execc, which may return before the operation is complete. See the chapter Asynchronous Requests and Semaphores for an explanation of asynchronous requests. See p_execc above for the meaning of the parameters pName, pConmand and Length, the behaviour of the operation and its possible error returns. The function p_execcasyne returns zero if the asynchronous request was successful or a negative error number if the operation failed to start (eg E_FILE_NAME if pName failed to parse). When the load completes, the completion status is written to *pstatus and the process I/O semaphore is signalled. If the load completes successfully, *pstatus contains zero and *pPid contains the process ID of the created process (this corresponds to the value returned by p_execc). If the load completes unsuccessfully, *pStatus contains a negative error number (eg E_GEN_NOMEMORY). If pName refers to a file on the Loc:: file system, the load will actually have completed by the time p_execcasync has returned because the file server runs at a higher priority than any of its clients. However, if the image is on a REM:: file system where the connection is via an RS232 cable, p execcasync will return well before the load is complete. HANDLE p_pcreate(E_CPB *pBlock); Create a process from the information in the &_cps struct pointed to by pBlock and, if successful, return the positive process ID of the created process. This is the primitive process creation service that does not involve the file server. In the vast majority of cases, it is more convenient to use p_execc, which will eventually call this service. 162 12 PROCESSES AND INTER-PROCESS MESSAGING _—_ SSS The E_Pcs struct is defined in epoc.h as: typedef struct € UWORD codeParagraphs; UWORD initiallp; UWORD stackParagraphs; UWORD dataParagraphs; UWORD heapParagraphs; UBYTE *commandl ine; UWORD checkSum; UWORD minHeap; UBYTE priority; UBYTE ramOrRom; UBYTE name [E_MAX_NAME] ; > E_CPB; The function creates a process of initial priority pBlock->priority where the process name is taken from the zero terminated (up to 8 characters) in pBtock->name. If pBLock->ramOrRom is TRUE and a process of the same name already exists, its checksum is compared with pBlock->checksum and, if they match, the existing code segment is shared (in which case pBLock->codeParagraphs is ignored). If pBtock->ramOrRom is TRUE and a process of the same name does not already exist, p_pcreate creates a code segment of length pBlock->codeParagraphs paragraphs (and pBlock->checkSum is stored as the code segment checksum). In this case, the caller must deposit the code into the created code segment before resuming the process. If pBlock->ramOrRom is FALSE, pBlock->codeParagraphs is taken to be the paragraph address of the code segment in the ROM. When the process is resumed, the initial instruction pointer is set to pBlock->initialIp. A data segment is created, of sufficient size to include the stack (of length pBlock->stackParagraphs) a Static data space (of length pBlock->dataParagraphs) and a heap (of length p8lock->heapParagraphs). The total size of the data, stack and heap must not exceed oxFFE paragraphs. The pBlock->dataParagraphs area in the data segment is zero filled. The minimum heap size subsequently allowed for the created process is p8lock->minHeap paragraphs. If pBlock->commandL ine is not NULL it should point to a zero terminated string, immediately followed by a leading byte count buffer where the leading byte is less than or equal to E_MAX_COMMAND_BUFFER (as described in p_execc, above). The whole p8lock->commandLine data structure is copied into a heap cell that is allocated in the created process. The function returns a positive process ID if successful or on of the following negative error numbers: E_GEN_NOMEMORY insufficient free system memory E_FILE_EXIST a process of a different program (ie with a different checksum) with the same name already exists E_GEN_NOPROC there are no more free process slots (the maximum process limit has been reached) E_GEN_ARG the total size of the data, stack and heap exceeds OxFFEO bytes or the leading byte count length in the p8lock->commandLine data structure exceeds E_MAX_COMMAND_BUFFER E_FILE_NAME pBlock->name is invalid SSS eee ee EE ee ee ee Operations on the current process See the chapter Error Handling for the functions (p_exit and p_panic) that terminate the current process. 163 PLIB REFERENCE HANDLE p_getpid(VOID); Return the process ID of the caller. For example: TEXT ProcessName [E_MAX_NAME+2]; Pp_pname(p_getpid(),&ProcessName [0] ); writes the name of this process as a zero terminated string to ProcessName. VOID p_unmarka(VOID); Mark the calling process as non-active such that the running of the process will not keep the machine switched on. The zero priority null process switches the machine off (to reduce power consumption) after a period of inactivity. A context switch to a process marked as non-active does not count as activity. When a process is created it is initially marked as active. General purpose server processes should mark themselves as non-active since not to do so would disable their clients from effectively calling p_unmarka. For example, if the file server was marked as active, a file request by a non-active client would cause the file server to run and reset the inactivity timer. As it is, the file server is marked as non-active and it is left to the clients of the file server to reset the inactivity timer or otherwise. Applications that respond to a continuously restarted short interval relative timer (as in, for example, a clock program) should also call p_unmarka. Otherwise, the machine would never switch off while the program is running. Interactive programs that do call p_unmarka do not need to take any measure to reset the inactivity timer when responding to user input (a key press or the use of a pointing device if there is one) since the system (by one means or another) guarantees to reset the inactivity timer on user input. To reset the inactivity timer in response to an event other than user input, call p_tickle, described next. Note that a compute-bound process (such as a game program that is computing its best next move) will not allow the machine to switch off, simply because the null process never gets an opportunity to run. Such a process should assume responsibility for allowing the machine to switch off by calling p_al lowoff from time to time. VOID p_tickle(VOID); Reset the auto-switch-off inactivity timer. It is used by a process that is marked as inactive, but wishes to stop the system switching off. An example would be to keep the machine going if serial data is received. You don't need to call p_tickle in response to user input since the system automatically registers activity in this case. weve VOID p_markaCVOID); Mark the calling process as active such that the running of the process will stop the machine from switching off. The zero priority null process switches the machine off (to reduce power consumption) after a period of inactivity. A context switch to a process marked as active resets the inactivity timer. When a process is created it is initially marked as active and p_marka does not need to be called unless p_unmarka has previously been called. ae ee eZ 164 12 PROCESSES AND INTER-PROCESS MESSAGING QQ Ee a ee eee EE ee Operations on any process See the chapter Error Handling for functions (p_pterminate, p_pkill and p_ppanic) that terminate a process. INT p_getpriCHANDLE pid); Return the positive priority of process pid, or the negative E_FILE_NXIST if the process does not exist. INT p_setpriCHANDLE pid, INT nPriority); Set the priority of process pid to nPriority (between E_MIN_PRIORITY and &_MAX_PRIORITY inclusive) and return zero if successful or one of the following negative error numbers: £_GEN_RANGE nPriority is outside the range E_MIN_PRIORITY to E_MAX_PRIORITY E_FILE_NXIST process pid does not exist E_GEN_FAIL attempted to change the priority of the null process, the supervisor, or the file server Calling p_setpri causes a reschedule. | process INT p_presume(HANDLE pid); Resume process pid and return zero if successful or one of the following negative error numbers: E_GEN_ARG pid is not suspended E_FILE_NXIST process pid does not exist If process pid has a higher priority than the caller, the call to p presume may not return for some time. If you actually want to wait for the resumed process to terminate before continuing, you can use p_logona (described in the chapter Error Handling) as follows: p_logona(pid,&stat); p_presume(pid); p_waitstat(&stat); INT p_psuspend(HANDLE pid); Suspend process pid and return zero if successful or one of the following negative error numbers: E_FILE_NXIST process pid does not exist E_GEN_FAIL attempted to suspend the null process, the supervisor, or the file server A process that is in the READY queue or is currently running (ie the calling process) is suspended immediately. A process that is waiting on the SEMAPHORE queue or the DELTA queue is marked (in peb.sstatus) as requiring suspension and is subsequently placed in the SUSPENDED state when it would otherwise have been transferred to the READY queue (unless the process is resumed before this happens). INT p_pnameCHANDLE pid, TEXT *pName); Write the name of process pid as a zero terminated string to pName (which should be big enough to receive E_MAX_NAME+2 bytes) and return zero if successful or E_FILE_NXIST if the process does not exist. The process name written to pName includes the process slot extension as in, for example, SYSSNULL.$01. 165 PLIB REFERENCE INT p_prename(HANDLE pid, TEXT *pNewName); Rename process pid to the zero terminated pNewName and return zero if successful. The process name pNewName should not include a process slot extension (this is supplied by the system) and should be between 1 and 8 characters long. No check is made that the new name is unique, but an invalid name will return the error E_FILE_NAME. The possible error returns are: E_GEN_ARG process pid is not suspended E_FILE_NXIST process pid does not exist E_FILE_NAME the new name is invalid HANDLE p_pidfind(TEXT *pName); Return the positive process ID of the first process having a name matching the zero terminated pName (which may contain wild card characters) or return €_FILE_NXIST if there is no matching process. For example, to find the process ID of a process created by loading db$serv.img, use: pid=p_pidfind("DBSSERV.*"); HANDLE p_pfind(HANDLE pid, TEXT *pMatch, TEXT “pName); Called repeatedly to find all the processes that match the wild card string pointed to by pMatch, writing the process name as a Zero terminated string into pName (which should be big enough to receive E_MAX_NAME+2 bytes). The first call should pass a zero pid. Returns the positive process ID of the process found (which is also passed to the next find) or the negative E_FILE_NXIST if no more matching processes can be found. The wild card string pMatch should remain the same between successive calls. No memory is used by this service and it can be abandoned at any time without taking any further action. For example, to print the names of all processes: GLDEF_C INT main(VOID) { HANDLE h; TEXT b[E_MAX_NAME+2] ; for (h=0;(h=p_pfind(h,"*", &b[0]))>=0;p_printf(&b[0] )); Pp_getch(); return(0); > HANDLE p_getowner(HANDLE pid); This function is only available in EPOC version 2.17 or later. Returns the process id of the process which last resumed process pid, that is, the last process to call p_presume(pid). This process is defined to be the owner of process pid. Note that there is no guarantee that the owning process still exists. 166 12 PROCESSES AND INTER-PROCESS MESSAGING Sa ea ee ee ea Accessing a process data segment The functions p_pcpyfr, p_piscpyfr and p_pcpyto copy data between a (normally different) process data segment and the caller's data segment (cf p_sgcopyfr and p_sgcopyto which copy data between any segment and the caller's data segment). INT p_pcpyfrCHANDLE pid, VOID *pSource, VOID *pTarget, UINT mBytes); Copy nBytes bytes at offset pSource from the data segment of process pid to address ptarget (in the current process) and return zero if successful. If pSourcetnBytes exceeds the size of the processes data segment, the data up to the end of the segment is copied and zero is returned. The system ensures that the copy is not interrupted by another process. The function returns E_GEN_aRG if the process pid does not exist. INT p_piscpyfr(HANDLE pid, VOID *pSourceAddr, VOID *pTarget, UINT nBytes); This function is only available in EPOC version 2.14 or later. Read the pointer at offset pSourceAddr in the data segment of process pid. Then copy the zero terminated string indicated by this pointer to address pTarget (in the current process) and return zero if successful. If the pointer is NULL, a null string ("") is copied to ptarget. If the string is longer than nBytes, only nBytes of data (plus a terminating zero) are copied and zero is retumed. If the implied length of the source string extends beyond the end of the processes data segment, the data up to the end of the segment (plus a terminating zero) is copied and zero is returned. The system ensures that the copy is not interrupted by another process. The function returns E_GEN_ARG if the process pid does not exist. For example: GLREF_D TEXT *DatProcessNamePtr; LOCAL_C INT GetCalcName(VOID) { HANDLE h; TEXT buf (0x40); h=p_pidfind("cale.*"); if ¢h<0) return(h); p_piscpyfr(h,&DatProcessNamePtr , &buf [0] ,0x40); return(0); > fetches the process name of the calculator. INT p_pcpytoC(HANDLE pid, VOID *pTarget, VOID *pSource, UINT nBytes); Copy nBytes bytes from pSource in the current process data segment to pTarget in the data segment of process pid and return zero if successful. The system ensures that the copy is not interrupted by another process. Address trapping is automatically switched off for the duration of the copy. 167 PLIB REFERENCE Returns the negative £_GEN_ARG if the process does not exist or if ptarget+nBytes exceeds the size of the target process data segment (in which case the data is not copied). ———S——— SE ee eee ee aa Inter-process messaging Inter-process messaging in EPOC is designed for the efficient implementation of client-server relationships where a particular server may have multiple clients. A server process is a commonly provided to share a resource amongst multiple client processes. The EPOC operating system starts up with three multi-client servers: = the supervisor, which performs critical system functions and provides shared access to memory via the memory segment allocator = the file server, which provides shared access to file storage devices = the window server, which provides shared access to the screen, keyboard and, if present, a pointing device A server may also be a client of another server. For example, the window server is a client of the file server (since, for example, it loads bitmap files) and all processes are implicitly clients of the supervisor. In order that the relative priorities of client processes have their intended effect, a multi-client server process should run at a higher priority than any of its clients. A client's priority is effectively lowered to that of the server while waiting for completion of a service provided by a low priority server. Message slots A process that wishes to receive messages must first call p_minit to initialise the message system. Calling p_minit(nMess, \Mess) allocates nMess message slots (from the heap) where each message slot contains an E_MESSAGE struct header followed by a buffer of length (Mess. The E_MESSAGE struct is defined in epoc.h as: typedef struct message € struct message *next; UBYTE *status; UINT type; HANDLE pid; > E_MESSAGE; The structure of the iMess bytes of data following the &_MESSAGE header is defined by the receiver of the message and is normally limited to a few words. For example, the file server and the supervisor both use an \Mess of 8. Note that the sender has no control over the length of the message sent (a later version of a server could increase the message length to provide additional services while maintaining upward compatibility). When the sender calls p_msend (or a variant) the arriving message is copied into a previously free message slot, which is also placed in the receiver's message queue. From this time the message slot is allocated, in P_nreceilvew the sense that it cannot be overwritten by another incoming message. It contains the message type (as specified by the sender) in type and the process ID of the sender in pid, followed by \Mess bytes of data from the sender. (The fields next and status in the E_MESSAGE header are used internally by the message system.) dequeued allocated The message slot is removed from the queue when the receiver calls, for example, p_mreceivew (which returns the address of the message slot). The content remains safe from being overwritten until the receiver calls p mfree to indicate that it has completed processing the message. Calling p_mfree returns the message slot to its free state, available to receive another incoming message. 168 12 PROCESSES AND INTER-PROCESS MESSAGING ———— eee What the server does A server is always a passive process that waits for messages to arrive from a client process. It is not expected that a server will ever initiate a transaction by sending a message to a client. As described earlier, the server first calls p_minit to initialise the message system. The server then waits for a message to arrive by calling p_mreceivew (an asynchronous version, p_mreceive, also exists). When the message arrives, the function returns the address of the message slot. The server always removes messages from the front of the queue. Arriving messages are normally inserted at the end of the queue but if the client's priority is 0x80 or more, the arriving message is inserted at the front, overtaking any existing messages, regardless of their sender's priority. When it is necessary to send more than the amount of information allowed for by the (typically short) message length, the message contains the data by reference (ie by address and length). The server then uses p_pepyfr to copy the data from the client's data segment. The message may also contain the address or addresses of buffers to receive data from the server when the service has been completed. Here, the server uses p_pcpyto to copy the data to the client's data segment. When the message has been processed, the server frees the message slot using p_mfree. If the message was sent in such a way that the sender is expecting an acknowledgment of some kind (ie the message was sent using p_msendreceivew OF p_msendreceivea rather than p_msend), the call to p mfree also writes back a completion status value and signals the sender's I/O semaphore. Servers request to be informed of the termination of a client by calling p_logon(pid,ntype) to receive a message of type nType when client pid terminates. Process termination and p_logon are described in the chapter Error Handling. What the client does The client process requests a service of the server by sending it typed fixed length messages (where the message length is determined by the server) using: p_msend to send the message "blind", returning when the message has been deposited (but not necessarily processed) p_msendreceivew to send the message and wait for the server to reply by calling p_mfree p_msendreceivea to send the message, returning when the message has been deposited (as for p_msend) having set up an asynchronous request for a reply If the server does not have any empty message slots, the message sending function waits on a mutual exclusion semaphore until a message slot becomes free. It can therefore be blocked indefinitely, even if using the asynchronous p_msendreceivea. Multi-client servers avoid this prospect by allocating a slot for each potential client (by allocating say E_MAX_PROCESSES minus the number of known processes). The only preparation required by the client is to obtain the process ID of the server. Examples of ways in which this is done are: = the client gets the ID via the process name using p_pidfind (this is normally what is done for multi-client servers) = the client knows the ID because it created the server using p_execc = the server created the client and passed its process ID as a command parameter to p_execc (as in the above example) For multi-client servers, the client typically sends an opening message to connect to the server. An example of a server typedef struct € E_MESSAGE mess; TEXT *bofs; UWORD len; } MESS; 169 PLIB REFERENCE a LOCAL_C VOID RunServer (VOID) € MESS *pmsg; TEXT buf (256) ; FOREVER { p_mreceivew(&pmsg); if (pmsg->mess. type) { p_mfree(pmsg,0); break; } P_pcpyfr(pmsg->mess .pid, pmsg->bofs, &buf [0] ,pmsg->len); p_printf("%*s", pmsg->len, &buf [0] ); p_mfree(pmsg,0); } > LOCAL_C VOID Exec(TEXT *name) { HANDLE pid; TEXT bb[E_MAX_ERROR_TEXT_SIZE}; pid=p_getpid(); pid=p_execc(name, (UBYTE *)&pid,sizeof(pid)); if (pid<0) € p_errs(&bb[0] ,pid); p_printf("Failed to start %s (%s)",name,&bb[0] ); > else € p_printf("Process ID is %xd", pid); p_logon(pid, TRUE); p_presume(pid); RunServer(); > } GLDEF_C INT main(VOID) € TEXT bb[P_FNAMESIZE]; p_minit(1,sizeof(MESS)-sizeof(E_MESSAGE)); while (p_getl("Enter ? ",&bb[0] ,P_FNAMESIZE)) Exec(p_skipwh(&bb[0] )); return(0); } In the above example, main solicits a file specification of an image to run. This image is then (in Exec) loaded using p_exece (passing the created process the process ID of its creator) and then resumed using p_presume. While the image is running, the program acts as a server to it where messages of type FALSE are taken to contain a buffer by reference. The data from this buffer is copied from the process using p_pepyfr and printed using p_printf. Before resuming the process in Exec, the server logs on to the process by calling p_logon. When the process terminates, the server receives a type TRUE message. This causes the program to return from RunServer and solicit another image file specification. Corresponding client code example The following example shows how to write that part of the client side code that corresponds to the above example of server code. 170 12 PROCESSES AND INTER-PROCESS MESSAGING eee GLREF_D UBYTE *DatCommandPtr; LOCAL_D HANDLE pid=0; LOCAL_D TEXT bb[256]; GLDEF_C VOID printf(TEXT *pfmt,...) { struct € TEXT *pbuf; UINT len; > msg; msg. len=p_atob(msg.pbuf=&bb (0) ,pfmt, &pfmt+1); if (!pid) pid=*(HANDLE *)(DatCommandPtr+p_slen(DatCommandPtr)+2); p_msendreceivew(pid, FALSE, &msg); > The printf function behaves in the same way as p_printf except that the printing is performed by the creator of the process in its console window. Note that the call to p_msendreceivew does not return until the server calls p_mfree. This example does not show the sending of the message with a type TRUE on termination of the client process. Asynchronous messaging The server-client example described above uses synchronous messaging in both the server and the client. While this may be sufficient in simple cases, there are situations where such an implementation will prove inadequate. A client that sends messages synchronously is effectively suspended from the moment it calls p_msendreceivew until the server completes processing the message and calls p_mfree. If completion is dependent on an external event, such as the expiry of a timer or the arrival of serial data, the client may be suspended indefinitely. This will, in general, be unacceptable behaviour (particularly if the client must remain responsive to other events, such as user input) and in such a case the client should use asynchronous messaging. A server does not, in general, have a user interface. If its only task is to receive and process messages from its clients, then synchronous server code may be perfectly acceptable. As in the example server code given earlier, the server is effectively suspended until a message is received and returns to the suspended State as soon as it has finished processing the message. If the server also has to respond to other events, such as the expiry of a timer, then the receipt of messages must be handled asynchronously. Suppose a client needs to use asynchronous messaging because completion of the processing of the message may be delayed indefinitely by an external event. This implies that the server itself must respond to at least two events (the receipt of a message and the external event) and so must also handle events asynchronously. The sequence of events that occur during messaging between asynchronous client and server is as follows: = the server makes a call to p_minit = — the server then calls p_mreceive, passing the address of its messaging status word, and later makes a call to p_iowait (usually in its main event-handling loop) = the client calls p_msendreceiva, passing the address of a status word, and later calls p_iowait (usually in its main event-handling loop) u the server is signalled that a message has been received, by noting that its messaging status word is no longer E_FILE_PENDING on a return from p_iowait, and commences processing the message =" on completion of the processing the server calls p mfree which notifies the client of the completion = the client is signalled that processing is complete, by noting that the relevant status word is no longer E_FILE_PENDING on a return from p_iowait If the processing of the message itself involves an asynchronous request, the server may process any number of synchronous messages from any of its clients (including the one sending the asynchronous ee See a 171 PLIB REFERENCE message) while the request is pending. It is therefore quite possible for a synchronous message to complete before an earlier asynchronous message from the same client. On the assumption that clients may send messages asynchronously because they do not want to wait, the server will generally need more than one message slot to avoid the client having to wait for a message slot to become free. How many slots to provide depends on many factors, such as: @ the maximum expected number of clients = the maximum number of outstanding messages allowed per client (rarely more than two) = whether it is acceptable for any client ever to be suspended while waiting for a free slot Message processing order A message sent by a client to a server is placed in a message queue, normally at the end of the queue. The server receives a message by removing the one at the front of the queue (into one of its message slots). Thus, in normal circumstances, messages are processed in the order in which they are sent. As mentioned in the previous section, it is possible for synchronous messages to ‘overtake’ asynchronous messages from the same client. The window server has a requirement, particularly in an overlapping window environment, to give priority to messages from the foreground task. For example, when an area of the screen covering all or part of a number of task windows needs redrawing (after, say, the disappearance of a dialog) the foreground task should be redrawn first. The following scheme has been adopted to satisfy the window server's requirement without causing an unacceptable performance penalty. In all cases, messages from a client process with a priority of 0x80 or above (the foreground task normally has a priority of 0x80, which is higher than the priority of background tasks) are placed at the front of the queue. They therefore overtake all other messages in the queue (including any earlier messages from the same client) irrespective of the priorities of their sending processes. This has the added advantage of reducing the risk that switching to the shell task (which always runs at a priority higher than the foreground task) can be blocked by a ‘rogue’ task. There is, however, one problem, which can be illustrated as follows. Suppose a client with a priority of 0x80 sends an asynchronous message to a server. This message is inserted at the front of the queue. Before the server removes any messages from the queue, the same client sends a cancel message, to cancel its previous request. This second message is again inserted at the front of the queue, overtaking the message it is supposed to be cancelling. For this situation to arise, all the following conditions must be satisfied: «the client must have a priority of 0x80 or above = the client must use asynchronous messaging = the client must be sensitive to the order in which the server processes its messages = the server must not have removed the first message from the queue by the time the second message is queued The last of these conditions is rarely satisfied, since a server normally runs at a higher priority than its clients. As soon as the server is signalled that a message has been queued the server pre-emptively suspends the client. In most cases the client will not resume until the server has completed processing the message and is waiting for another message. It is rare that a server has more than one message in the message queue. The main exception is when the processing of the message requires the server to call (directly or indirectly) p_iowait, for example, to perform file I/O. In such a case you should consider whether the client really needs to use asynchronous messaging. Alternatively you can set the client's priority to be less than 0x80. In this case the mechanism by which the window server adjusts the priority of background and foreground tasks should be disabled (see the description of wConnect in the Window Server manual). SSESS——— ESE — eee Server functions All the server functions except p_minit call p_panic if messages have not been initialised and p minit itself calls p_panic if it is called a second time. 172 12 PROCESSES AND INTER-PROCESS MESSAGING INT p_minitCINT nMess, INT lMess): Initialise a queue of nMess message reception slots of length (Mess and return zero if successful or one of the following negative error numbers: E_GEN_NOMEMORY there is insufficient free memory to allocate the message queue E_GEN_NOSEM there are no more free semaphores (p_minit creates a mutual exclusion semaphore, initialised with nMess) The message length tMess excludes the E_MESSAGE structure that is at the front of all messages. The message queue is allocated as a single cell from the heap. Since this cell remains allocated for the lifetime of the process, programs calling this service should do so early in their initialisation (before any cell has been freed) to avoid heap fragmentation. All message slots are of the same size and the total size of the message queue is nMess*(\Messt+sizeof(E_MESSAGE)) bytes. To avoid senders being blocked by message sending (quite different from waiting for a reply that can be handled asynchronously), multi-client servers should allocate a message slot for each potential client. The constant E_MAX_PROCESSES Contains the total number of processes that can be supported by the system. In practice this can be reduced by at least four, for the null process, supervisor, file server and window server. Only the least significant byte of nMess is significant, limiting the number of message slots to 255. Calls p_panic if p_minit has already been called or if the least significant byte of nMess is zero. VOID p_mreceivew(VOID *pMess); Return when a message has been received, where the address of the message slot containing the message is written to *pMess. The data in the message slot is protected from being overwritten by the receipt of another message until the message slot is freed from the queue using p_mfree. Calls p_panic if messages have not been initialised or if an asynchronous message receive request is pending. Example typedef struct € E_MESSAGE mess; TEXT *bofs; UWORD len; > MESS; MESS *pmsg; p_mrecei vew(&pmsg); P_ Asynchronous VOID p_mreceive(WORD *pStatus, VOID “pMess); Sage reception Make an asynchronous request to receive a message and return immediately without waiting for a message to be received. While the request is pending (and the message queue is empty), *pStatus contains E_FILE_PENDING. When a message is received (or if there is already a received message in the queue) *pStatus is set to zero, the address of the message slot containing the message is written to *pMess and the process I/O semaphore is signalled. The data in the message slot is protected from being overwritten by the receipt of another message until the message slot is freed from the queue using p_mfree. Calls p_panic if messages have not been initialised or if an asynchronous message receive request is already pending. ——— 173 PLIB REFERENCE ee Example typedef struct { E_MESSAGE mess; TEXT *bofs; UWORD len; > MESS; MESS *pmsg; WORD status; p_mreceive(éstatus ,&pmsg); VOID p_mcancel (VOID); Cancel any pending asynchronous request to receive a message (which was previously requested using the p_mreceive Service). It is not considered an error to call this service if no request is pending (since the request may complete at any time). If the cancel is processed before a message is received, the associated status word *pStatus is set tO E_FILE_CANCEL. Note that this service does not cancel a pending p_msendreceivea (which is a client function). A server may support a cancel service but this is invoked, like any other service, by sending the server a message. VOID p_mfree(VOID *“pMess, INT nReply); Free the message slot with address pMess (as returned from p_mreceivew or p_mreceive) and, if the message was sent using p_msendreceivew OF p_msendreceivea, complete the request by signalling the sending client's I/O semaphore and copying nkeply to the client's status word (which is returned by p_msendreceivew). A negative value of nReply normally indicates an error. If the message was sent with p_msend, the message slot is just freed and nReply is ignored. The server should not read the contents of pMess after calling p_mfree. If messages are not freed from the message queue then in due course sending processes will be blocked waiting on the message queue mutual exclusion semaphore. SSS a ee ee ee ee a a i ae ae) Client functions INT p_msend(HANDLE pid, UINT mType, VOID *pMessage); Send message pMessage of type mType to process pid and return zero when the message has been deposited or the negative E_GEN_RECEIVER if pid does not exist or if process pid has not called p_minit. The message is deposited into a free message slot in the server where the member type in the E_MESSAGE header is set to mlype and the message body is copied from pMessage (where the length of the message was specified by the server as a parameter to p minit). If pid does not have a free message slot, p_msend waits (on a mutual exclusion semaphore) until it does. This function is only really suitable for sending messages where the whole information fits in the message. That is, the data at pMessage should not contain the address of further data to be copied (using p_pcpyfr) because there is no way of knowing when the server will copy the data so that pessage may be re-used. Where the message does reference further data, p_msendreceivew OF p_msendreceivea should be used. A client should certainly use p_msendreceivew or p_msendreceivea if the service is to return information (eg success or failure). 174 12 PROCESSES AND INTER-PROCESS MESSAGING INT p_msendreceivew(HANDLE pid, UINT mlype, VOID *pMessage); Send message pMessage of type mType to process pid and wait for the server to reply (which it does by calling p_mfree(pid,nReply)) and return the reply nkeply. Return immediately with the negative E_GEN_RECEIVER if pid does not exist or if process pid has not called p_minit. The message is deposited into a free message slot in the server where the member type in the E_MESSAGE header is set to mlype and the message body is copied from pMessage (where the length of the message was specified by the server as a parameter to p_minit). If pid does not have a free message slot, p_msendreceivew waits (on a mutual exclusion semaphore) until it does. INT p_msendreceiveaCHANDLE pid, UINT mType, VOID *pMessage, WORD *pStatus); Send message pMessage of type mlype to process pid and asynchronously request a reply from the server. Return zero if the message was successfully sent or the negative E_GEN_RECEIVER if pid does not exist or if process pid has not called p minit. When the server completes the request and calls p_mfree(pid,nReply), *pStatus is set to nReply and the client's process I/O semaphore is signalled. While the reply is pending, *pstatus contains E_FILE_PENDING. The message is deposited into a free message slot in the server where the member type in the &_MESSAGE header is set to mlype and the message body is copied from pmessage (where the length of the message was specified by the server as a parameter to p_minit). If pid does not have a free message slot, p_msendreceivea waits (on a mutual exclusion semaphore) until it does. 175 CHAPTER 13 GENERAL SYSTEM SERVICES See ee eee ee ee ee System information UINT p_version(VOID); Return the operating system version number. The returned version number should be interpreted as a 4-digit hexadecimal number of the form: X.YYZ where x is the major release number, yy is the minor release number and z is normally the hexadecimal digit F (internal releases use A and 8 to designate alpha and beta releases, respectively). For example, a return of 0x123F is interpreted as 1.23F. UINT p_romversion(VOID); Return the ROM version number. The ROM contains the operating system and other system components such as, for example, the window server. The tool used to build the ROM requires a version number to be specified and this is the version number that is retrieved by this function. See p_version above for the interpretation of the version number. INT p_getres(VOID); Return a number indicating the cause of the last system shut-down, as follows: E_IS_A_COLD_START The system started up for the first time (or after a period during which all power had been removed, including the Lithium back-up). E_IS_A_POWERFAIL_START The hardware forced a shut-down because the voltage got too low. This should not happen because the system software gets a non-maskable interrupt if the voltage drops below a certain threshold (but not low enough for the hardware to force a shut-down) and this interrupt code automatically switches the hardware off. However, if the clean-up takes too long because of a poorly designed device driver, the hardware will force the machine off before the interrupt completes - in which case p_getres returns E_IS_A_POWERFAIL_START. The environment variables and the contents of m: are preserved. 177 PLIB REFERENCE eee E_IS_A_RESET_START The machine has been reset by pressing the recessed reset button. The environment variables and the contents of M: will have been preserved (a soft Teset). If the Esc key is held down while pressing the reset button, this causes a hard reset. In this case both the environment variables and the contents of mM: will have been cleared. Note that this value will not occur on a Workabout, since this machine does not have a reset button. E_IS_A_KERNEL_FAULT The system was reset because a serious fault occurred while executing in the operating system kernel. This could result from: (1) a bug in the operating system or a system process; (2) a program bug that managed to overcome the operating system's defences or (3) a hardware problem such as a RAM fault. The environment variables and the contents of M: will be preserved (unless the system detects a memory corruption). On the Workabout, this value is also returned if the system is reset by pressing Psion-Ctrl-Del. This is the equivalent of a soft reset on other machine types and preserves both the environment variables and the contents of m:. E_IS_A_NEW_OS START The system was reset after programming a new operating system into the Flash ROM (normally after running the repro program). The environment variables and the contents of m: have been cleared. On the Workabout, this value is also returned if the system is reset by pressing Shift-Psion-Ctrl-Del. This is the equivalent of a hard reset on other machine types and clears both the environment variables and the contents of m:. VOID p_getosd(VOID *pTarget, VOID *pSource, UINT length); Copy length bytes from offset psource in the operating system data space to pTarget. Operating system handles (such as memory segment handles and semaphore handles) are actually addresses in the operating system data space of the appropriate control entry. If you AND a process ID with £_P1D_MASK you get the address in operating system space of the corresponding process control entry - as described in the chapter Processes and Inter-Process Messaging. INT p_getpsuCVOID); Return one of the following values, defined in epoc.h, to indicate the power supply type on a SIBO machine: E_PSU_OLD E_PSU_MAXIM E_PSU_S3 E_PSU_S3_A9 Apart from this service EPOC hides the differences between the various power supplies. Machines in the MC GI range of SIBO computers, for example, may use either the E_PSU_OLD or the —_PSU_MAXIM power supply variants, each of which requires a slightly different variant of the EPOC operating system. The REPRO program that blows a new operating system into the Flash ROM of the MC GI range uses this service to blow the appropriate variant of EPOC. [SS SF ee ee Language and country SIBO machines are produced in a number of language variants, differing in the following respects: = the language code = — the language of the text used by the ROM-based software (for example, the error messages returned by p_errs and the month names returned by p_nmmon) ns Sh ee 178 13 GENERAL SYSTEM SERVICES = character type and conversion tables as described in the chapter Characters, Strings, Buffers and Queues = the keyboard layout « the default country and country-dependent data A particular language variant will always have a different language code and text but may not differ in all the above. The system was designed to be produced in a variety of languages and, as far as the system services are concerned, all the above variations are encapsulated in a single configuration file in the ROM called ROM::SYS$CTRY. CFO. Depending on the machine, there may be further files (for example, "resource files") containing language-dependent data for higher level system components. Unlike the language-dependent data, the country-dependent data may be altered from the language- dependent defaults. P_getlanguage INT p_getlanguage(VOID); Return the language code from the ROM configuration file. The language code can be used by applications that contain the text for more than one language to determine which language to present. The language codes, defined in p_config.h, are as follows: 1 = English 2 = French 3 = German 4 = Spanish 5 = Italian 6 = Swedish 7 = Danish 8 = Norwegian 9 = Finnish 10 = USA 11 = Swiss french 12 = Swiss German 13 = Portuguese 14 = Turkish 15 = Icelandic 16 = Russian 17 = Hungarian 18 = Dutch 19 = Belgian Flemish 20 = Australian 21 = New Zealand 22 = Austrian 23 = Belgian french P.gettext INT p_gettextCINT n, TEXT *pBuffer); Get the nth string from the ROM configuration file and write it to pBuffer. Returns zero if successful or the negative £_GEN_ARG if n is outside the range of the text strings in the configuration file. This function is called by specific text retrieval functions such as p_errs and p_nmon. Applications only need to use p_gettext when retrieving text associated with a higher level of system software (in which case the documentation of the higher level software will list appropriate values of n). 179 PLIB REFERENCE VOID p_getctd(E_CONFIG *pcfg); Write a copy of the system E_CONFIG struct to pcfg where the E_cONFIG struct is defined in P_config.h as: typedef struct € UWORD countryCode; WORD gmtOffset; UBYTE dateType; UBYTE timeType; UBYTE currencySymbol Position; UBYTE currencySpaceRequi red; UBYTE currencyDecimalPlaces; UBYTE currencyNegativelnBrackets; UBYTE currencyTriadsAl lowed; UBYTE thousandsSeparator; UBYTE decimalSeparator; UBYTE dateSeparator; UBYTE timeSeparator; UBYTE currencySymbol [9] ; UBYTE startOfWeek; UBYTE summerT ime; UBYTE clockType; UBYTE dayAbbreviation; UBYTE monthAbbreviation; UBYTE workDays; UBYTE units; UBYTE spare[9]; } E_CONFIG; The countryCode specifies a country by its international dialling code. See the description of p_getctd in the chapter Time, Timers and Dates for a description of the time- related fields: gmtoffset, dateType, timeType, dateSeparator, timeSeparator, startOfWeek, summerTime, clockType, dayAbbreviation, monthAbbreviation and workDays. See the description of p_getctd in the chapter Floating Point for a description of the fields that are related to the display of floating point numbers and currency: currencySymbol, currencySymbotPosition, currencySpaceRequired, currencyDecimalPlaces, currencyNegativelnBrackets, currencyTriadsAl lowed, thousandsSeparator and decimalSeparator. ee B VOID p_setctd(E_CONFIG *pcfg); Sets the country-dependent data from the E_CONFIG structure pointed to by pefg. When changing a particular field or fields you would normally: = use p_getctd to get a copy of the E_CONFIc struct = modify the field or fields, as required = use p_setctd to write back the modified E_conFIc struct FANT ee Se TAS Switching on and off By default, the auto-switch-off period is set to 300 seconds. The auto-switch-off period may be sensed and set by calling p_getauto and p_setauto respectively. Auto-switch-off when a mains adaptor is connected may be disabled by calling p_setautomains, and the corresponding state sensed by p_getautomains. The zero priority null process automatically switches the machine off to reduce power consumption after the system has been inactive for the auto-switch-off period. In situations where the null process does not get an opportunity to run, a process can assume responsibility for allowing the machine to switch off by calling p_allowoff. 180 13 GENERAL SYSTEM SERVICES Some processes are marked as not being significant when it comes to determining what constitutes activity. For example, a continuously running clock program should not stop the system from automatically switching off in the absence of any significant activity. See p_unmarka and p_marka in the chapter Processes and Inter-Process Messaging for additional details on how to avoid keeping the machine switched on by continuously running applications. VOID p_off(UINT uTime); Switch off the machine indefinitely or for any time up to approximately 4.5 hours and return when the machine switches on. If uTime is Oxf fff the machine switches off indefinitely and will stay off until an outstanding absolute timer expires or the user switches on the machine. Equivalent to the machine automatically switching off or being switched off by the user. If uTime is greater than 8, the machine switches off for up to uTime 1/4ths of a second (although it will still switch on if an outstanding absolute timer expires or the user switches on the machine). (If uTime is less than or equal to 8, calling p_off has no effect.) On the IBM PC version of EPOC, calling p_off has no effect. INT p_getauto(VOID); Return the current auto-switch-off period in seconds. If the auto-switch-off period is oxffff, the system does not automatically switch off. VOID p_setauto(INT n); Set the auto-switch-off period to n seconds. Passing an n of -1 (Oxf fff) stops the system from automatically switching off. Calling p_setauto with n less than 15 is equivalent to calling p_setauto(15). INT p_getautomains(VOID); This function is only available in EPOC version 3.18 or later. Return TRUE if auto-switch-off is disabled when mains is present, otherwise return FALSE. VOID p_setautomains(INT flag) This function is only available in EPOC version 3.18 or later. Disable or enable auto-switch-off if mains is present. If flag is TRUE, auto-switch-off is disabled while mains is present and if flag is FALSE, auto-switch-off is enabled. Even if enabled, the machine will not switch off when mains is absent if switch-off has been stopped by use of p_setauto. VOID p_allowoff(VOID); Allow the machine to switch off if the auto-switch-off period has expired. 181 PLIB REFERENCE A compute-bound process which has marked itself as non-active (by calling p_unmarka) does not allow the machine to switch off since the null process will never get an opportunity to run. Such a process should call p_allowoff from time to time. There is no particular advantage in calling p_allowoff more frequently than at intervals of 15 seconds - the shortest auto-switch-off period. On the IBM PC version of EPOC, calling p_al Lowoff has no effect. VOID p_setonevent(INT state) This function is only available in EPOC version 2.28 or later. Enables or disables the event that is sent to the window server when the ON key is pressed. If state is FALSE, the event is disabled, any other value enables the event. aaa ere ee a ee a i ee ee ie a ay Power supply This section describes functions to: = determine the presence or absence of the main battery, the Lithium backup battery or the mains adaptor (p_supplyinfo) = get the voltage level of the main battery (or mains adaptor, if present) and the Lithium backup battery (p_supply) = determine whether the mains adaptor is connected (p_supply) ® get the nominal maximum voltages of the main battery and the Lithium backup battery (p_wsupply) = get the recommended low voltage warning levels for the main battery and the Lithium backup battery (p_wsupply) = get the time and date of insertion of the main battery, and information about main battery usage (p_supplyinfo) = sense and set the main battery type (p_getbat and p_setbat respectively) The main battery type is only significant on machines that can take more than one battery type (such as the MC GI range) and affects the voltages returned by p_wsupply. The main battery types are as follows: E_BATTERY_UNKNOWN the battery type is initially set to this value when EPOC starts up (equivalent in its effect to E_BATTERY_ALKALINE) E_BATTERY_ALKALINE the battery is an Alkaline E_BATTERY_NICAD_600 the battery is a 600 mA hour NiCd rechargeable E_BATTERY_NICAD_1000 the battery is a 1000 mA hour NiCd rechargeable If the hardware is unable to identify automatically the battery type, it is the responsibility of the user to set the battery type to match that actually fitted. The E_BATTERY_UNKNOWN value is intended to trigger the system into prompting the user to identify the battery type. VOID p_supply(E_SUPPLY *pValue); Write the status of the various supplies to the E_sUPPLY struct at pvalue where E_suppLy is defined as: typedef struct { UWORD mainBatteryReading; UWORD LithiumBatteryReading; WORD mainsPresent; > E_SUPPLY; 182 13 GENERAL SYSTEM SERVICES SSS where: mainBatteryReading is the main battery voltage in millivolts (or the mains adaptor voltage if the mains adaptor is present) lithiumBatteryReading is the Lithium backup battery voltage in millivolts mainsPresent is negative if the mains adaptor status cannot be determined (because the SSD pack doors are open); zero if the mains adaptor is not present; one if the mains adaptor is present VOID p_supplyinfo(E_SUPPLY_INFO *pValue); This function is only available in EPOC version 3.18 or later. Write information concerning the various power supplies to the E_SUPPLY_INFO Struct at pValue. The &_SUPPLY_INFO is defined in epoc.h as: typedef struct { UBYTE mainBatteryLevel; UBYTE mainBatteryStatus; UBYTE backupBatteryLevel; UBYTE dcLevel; UWORD warningF lags; ULONG insertionDate; ULONG ticksInUseBattery; ULONG ticksInUseDc; ULONG maTicks; > E_SUPPLY_INFO; where: mainBatteryLevel is the present status of the main battery. It describes the voltage level as being in one of four discrete states: a) E_MBAT_GOOD battery voltage is good b) E_MBAT_Low battery voltage is low C) E_MBAT_VERY_LOW battery voltage is very low d) E_MBAT_ZERO there is no battery present! No precise figures for the actual voltage levels corresponding to these states are given. mainBatteryStatus is the same as mainBatteryLevel described above except that it records the lowest level that the battery has reached. It is reset when the main batteries are removed from their housing. backupBatteryLevel is TRUE if the Lithium backup battery is present and FALse if the backup battery level is low or the battery is not present deLevel is TRUE if the mains adaptor is present and powered up 183 PLIB REFERENCE Ss SSS warningF lags is a set of flags which can be one of: a) E_SUPPLY_SYSTEM_TIME_CHANGED this flag only has meaning when the battery insertion date changes. If set, it means that the system time has changed; if not set, it means that the main battery has been changed. b) E_SUPPLY_SOUND_WARNING this flag is set if the battery power level is too low to operate sound. Its setting implies that the user has been warned at least once before of the failure of an attempt to generate sound. C) E_SUPPLY_FLASH_WARNING this flag is set if the battery power level is too low to operate a flash SSD. Its setting implies that the user has been warned at least once before of the failure of an attempt to write to flash. These warning flags are intended to be used only for supplying information to auser. The last two, in particular, do not necessarily imply that an attempt to generate sound or to write to flash will definitely fail. Even if either or both of these warning flags are set, the attempt may succeed - for example, either because the battery voltage has recovered since an earlier failure, or because the machine is now connected to mains power. An application is not expected to test either of these flags before attempting the corresponding operation - failures should simply be handled by standard error-recovery techniques. insertionDate is the system time when the present main battery was inserted ticksInUseBattery is the total length of time in 'ticks' (1/32 second) for which the machine has been switched on and powered by the main battery. Any period during this time when the machine has been powered by the mains adaptor is excluded from the total. ticksInUseDe is the length of time in 'ticks' (1/32 second) for which the machine has been switched on and powered using the mains adaptor. maT icks is the cumulative current delivered by the present main battery; it is a product of current x time and is measured in units of milliamps x ‘ticks’ where a 'tick' is 1/32 second On machines that use the ASIC1 chip (that is, the Series 3 classic and HC) this information is not available, even if the machines contain version 3.18 or later of EPOC. In such a case, p_supplyinfo writes zero values to all members of the E_SUPPLY_INFO struct. VOID p_wsupply(E_SUPPLY_WARNINGS *pValue); Write the recommended low voltage warning level and the nominal maximum voltage of the main battery and the Lithium backup battery to pvatue. The E_SUPPLY_WARNINGS struct is defined as: typedef struct { UWORD mainBatteryWarning; UWORD lithiumBatteryWarning; UWORD mainBatteryMax; UWORD LithiumBatteryMax; } E_SUPPLY_WARNINGS; where: mainBatteryWarning is the recommended voltage at which to warn the user of a low main battery lithiumBatteryWarning is the recommended voltage at which to warn the user of a low Lithium backup battery mainBatteryMax is the nominal maximum voltage of the main battery LithiumBatteryMax is the nominal maximum voltage of the Lithium backup battery 184 13 GENERAL SYSTEM SERVICES eee All voltages are provided in millivolts. If the host machine can take more than one battery type, the voltages will depend on the battery type set with p_setbat. INT p_getbat(VOID); Return the current battery type. The battery types of the form &_BATTERY_xxx are described at the beginning of this section. On machines whose hardware does not support detection of the battery type, a call to this function should be preceded by a call to p setbat. INT p_setbat(INT nType); Set the battery type to ntype and return zero if successful or E_GEN_NsuP if battery type nType is not supported by the hardware. The battery types of the form &_BATTERY_xxx are described at the beginning of this section. This function has no practical effect on machines, such as the Workabout, whose hardware supports the detection of the battery type. ——SEE—EE—EE_S ee ee ee ee) Keyboard INT p_getscancodes(UWORD *pScan); This function is only available in EPOC version 3.18 or later. Write the current state of all the keys on the keyboard to the array of ten words pointed to by psean. Each key corresponds to a bit in the word array. In the case of the Series 3a, the lowest eleven bits in each of the first eight words are used. All other machines in the SIBO range use the lowest eight bits in all ten words. If a key is depressed the corresponding bit will be set, otherwise it is clear. The mapping between keys and bits in the array varies from machine to machine. In all cases, however, if no key is depressed all ten words in the array will contain zero. The following example, to detect if any key is pressed, is suitable for use on all SIBO machines: GLDEF_C IsKeyDown(VOID) € UWORD *p; UWORD scans[10) ; p=&scans [0] ; p_getscancodes(p); for (;p<=&scans [9] ;pr+) { if (*p) return( TRUE); > return( FALSE); > The mapping between keys and the bits within the array for the Series 3a keyboard is given in in the description of the EPOC twGetScanCodes service, in the Hardware Management chapter of the EPOC O/S System Services manual. 185 PLIB REFERENCE Display INT p_getlcd(VOID); Return the (positive) system display type, where the display types are defined in epoc.h. The function returns -1 if the host is a PC that has an unknown display type. On a SIBO machine the display type is a good way of identifying the model. The appropriate constants are defined in epoc.h. Examples are: E_LCD_640_400 a 640x400 pixel display as on the MC 400 E_LCD_640_200_SMALL a 640x200 pixel display as on the MC 200 E_LCD_160_80 a 160x80 pixel display as on the HC E_LCD_240_80 a 240x80 pixel display as on the Series 3 E_LCD_480_160 a 480x160 pixel display as on the Series 3a E_LCD_240 100 a 240x100 pixel display as on the Workabout On a PC, the display types are: E_PC_HERC Hercules graphics adaptor —_PC_CGA CGA graphics adaptor E_PC_MDA MDA display adaptor E_PC_EGA_MONO EGA monochrome graphics adaptor E_PC_EGA_COLOUR EGA colour graphics adaptor E_PC_VGA_MONO VGA monochrome graphics adaptor E_PC_VGA_COLOUR VGA colour graphics adaptor If p_getlcd returns -1 (which can only happen on a PC), and you choose not to fail, it is recommended that you proceed as if it had returned E_PC_VGA_MONO. VOID p_lcdcontrastdelta(INT nDelta); Step the LCD contrast up or down depending on whether ndeita is positive or negative respectively (the magnitude of nDelta is ignored. The LCD contrast is changed through a sequence of values in a circular fashion. That is, stepping the LCD contrast up when it is already at its maximum value sets it to its minimum value and vice versa. The sequence of values that may be set varies from model to model. On a particular model, the values can be ascertained by using p_getlcdcontrast, described below. INT p_getlcdcontrast(VOID) Return the current LCD contrast setting. INT p_backlight(INT mode); Switch the backlight on or off, depending on mode as follows: E_BACKLIGHT_OFF switch the backlight off (if it is not already off) E_BACKLIGHT_ON switch the backlight on (if it is not already on) and reset the auto-switch-off timer E_BACKLIGHT_TOGGLE switch the backlight on and reset the auto-switch-off timer if it is currently off or switch the backlight off if it is currently on E_BACKLIGHT_QUERY does nothing and is used just to get the current backlight on/off state 186 13 GENERAL SYSTEM SERVICES The function always returns the on/off state (TRUE if on, FALSE if off) as it was as the function was entered. VOID p_setbacklight(UINT flag); Enable or disable the backlight key and set the backlight auto-switch-off interval in ticks. If the most significant bit of flag is set (as given by the bit mask &_BACKLIGHT DISABLE), the operating system will not respond to the backlight key. (However, this does not stop the backlight from being switched by a program calling p_backlight.) The lower 15 bits of flag specifies the backlight auto-switch-off interval in ticks (1/32ths of a second). If this value is zero, the backlight is not automatically switched off by a backlight timer and it will remain on until the machine switches off. For example: p_setbackl ight (96); sets the backlight auto-switch-off interval to 3 seconds and: p_setbackl ight(E_BACKLIGHT_DISABLE |(32*5)); sets the backlight auto-switch-off interval to 5 seconds and disables the backlight key. UINT p_getbacklight(VOID); Return the backlight control value as set by p_setbacklight, described above. ESS SS eee eee ee Sound Most SIBO machines contain a piezo-electric buzzer in addition to a loudspeaker. This section describes how to use the piezo to make a sound, and how to manipulate the set of flags used to control the sound produced by the system. The piezo uses very little power and is an easy way of generating sound, although it is fairly quiet. Consider using the snp: device driver, which drives the loudspeaker, if greater sound complexity or a louder sound is required. The snp: device driver is described in the J/O Devices Reference manual. The bit masks for the flags controlling sound output are: £_SOUND_KEYBOARD keyboard clicks are silenced if clear &_SOUND_BUZZER the piezo sound system is silenced (except for keyclicks) if clear £_SOUND_DEVICE the snd: device driver is silenced if clear E_SOUND_LOUD the piezo will sound louder if set E_SOUND_DISABLE all sound in the system is silenced if set p_sound VOID p_sound(UINT nDuration, UINT nPitch); Make a sound through the piezo for nouration system ticks and at pitch nPitch (where the pitch has frequency 512/nPitch KHz). Shared access to this piezo service is controlled by first waiting on a mutual exclusion semaphore that has been pre-counted with 1 (mutual exclusion semaphores are described in the chapter Asynchronous Requests and Semaphores). The effect of the mutual exclusion semaphore, assuming the piezo is not already in use, is that the first call to p_sound will complete immediately but subsequent calls will wait until the current operation completes. The result is that there could be an indefinite wait before the sound is made. 187 PLIB REFERENCE Eee If nDuration is passed as a negative value the call will always complete immediately, but will fail to make a sound if the piezo is currently in use. For example: p_sound(5, 320); makes a short beep, suitable for accompanying an error notification. The call: p_sound(-5,320); will make the same sound, provided the piezo is not in use. INT p_getsnd(VOID); Return the current setting of the sound flags. The flags are described at the beginning of this section. VOID p_setsnd(INT nFlag); Set the sound flags to nFlag. The flags are described at the beginning of this section. SSS ae aes a a a a a EY Sound on the Series 3a The Series 3a machine does not contain a piezo-electric buzzer, so all sounds are made via the loudspeaker. The Series 3a operating system does, however, contain a buzzer emulator (using the sno: device driver) that supports the sound services described in the previous section. This does, of course, mean that the p_sound service uses more power than in the case of machines that contain a piezo-electric buzzer. Since all sounds are produced via the sup: device driver, sounds that, in other machines, use the buzzer (keyclicks, for example) are disabled while the Series 3a is playing a sound, such as an alarm. Sound files Series 3a sound files are files that contain a 32-byte header and a byte stream of A-Law encoded digital sound. Such files normally have a .wve extension. During recording 13-bit (a sign bit plus 12 magnitude bits) sound samples is converted to an 8-bit data stream at 8000 bytes per second by CODEC hardware, using A-Law encoding. During playback the byte stream is sampled at 8000 bytes per second and decoded to 13-bit sound by the CODEC. In C, the file header is represented by the following struct (defined in epoc.h): #define SignatureSize 16 #define ALawSignature "ALawSoundFile**" typedef struct € TEXT Signature [SignatureSi ze]; UWORD Version; ULONG Samples; UWORD SilenceInTicks; UWORD Repeats; UWORD Spare [3]; } SndFile; This header is written and read by the Series 3a sound services described below. The meanings of the items in the SndFile struct are: Signature the 16-byte (including the zero terminator) string "ALawSoundFile**". 188 13 GENERAL SYSTEM SERVICES —. SSeS Version the Series 3a sound file version number as a 4-digit hexadecimal number of the form xyyz, where x is the major release number, yy is the minor release number and z is normally the hexadecimal digit F. See, for example, the description of p_version. Samples the number of bytes following the header. This must always be size of file less the 32 bytes for the header. Dividing this by 8000 gives the duration of the sound in seconds. SilenceInTicks the number of system ticks of silence appended to each repeat on playback (in practice, you get at least 2 ticks between repeats). Repeats the number of times to repeat the sound on playback (0 and 1 are the same). Spare reserved for future use. A system tick is a 1/32th of a second, equivalent to 250 samples. The program wav2wve.exe, supplied with the SDK (it is installed to the \sibosdk\sys directory) converts . wav sound files to the .wve format. The A-Law encoding scheme The encoding scheme compresses signed, 12-bit magnitude (13 bits in all) samples into an 8-bit representation. A-Law encoding uses a logarithmic compression to reduce the size of sound files whilst preserving the overall sound quality. The use of a logarithmic scale ensures that the low amplitude signals (which contain most of the information in speech signals) are stored and reproduced with a minimal loss of fidelity. The A-Law encoding and decoding schemes are illustrated in the following sections. Encoding If the input data is represented by a normal two's complement signed value, it must first be converted into a 12-bit magnitude, plus a 13th sign bit. For example, a value of -1 (represented by the 16-bit two's complement value of Oxff#f) must be converted to the 13-bit binary representation ¢1)000000000001, where brackets indicate the sign bit. The following table represents the range of possible 13-bit inputs. In this table an x character indicates either a one or a zero (a "don't care” state) and an s character represents the sign bit: AN OMRrRPODO a ae See rPoOoOCCOO0O MrHOODOOCO chest sos oem Momo m om) Qaarmradcde xX ABADHMRO xR RAO DDD xe NM BOD RR ERM BOO aM MM MM OO RMN MRR A-Law compression of the above 13-bit inputs leads to the following range of 8-bit output values: (sls, le ates, ee oe el HHHHHH DH NH FPROORROO vpouvrorurrne anaaanaaa 208082083080 % | | | | a es PRPHROOOO POHOROFSO pepo w The compressed data is computed according to the following rules: = the most significant bit preserves the sign bit of the original data item = the following three bits represent the magnitude of the original data item, by recording the position of the most significant non-zero bit (but note that the smallest magnitude group is treated exceptionally) er 189 PLIB REFERENCE = the remaining four bits record the next four most significant bits of the original data (in all but the first group, these are the four bits that follow the most significant non-zero bit) The compressed data is then manipulated to invert bits 0, 2, 4 and 6. This manipulation brings the ratio of ones to zeroes closer to 50:50 and thus improves analog transmission of the data. Examples of A-Law encoded data are given in the following table, where brackets identify the sign bit. signed input 13-bit data compressed data encoded output -1 (1)000000000001 (1)0000000 (1)1010101 +2 (0)000000000010 (0)0000001 (0)1010100 -371 (1000101110001 (1)1000111 (1)0010010 +2074 (0)100000011010 (0)1110000 (0)0100101 A-Law encoding can be performed most simply and rapidly by means of a look-up table. Note that a 2048-element table is sufficient, since the least significant bit of the input data has no effect on the encoded value. Where speed is not of the essence, an algorithmic method may be used, such as that illustrated by the following code: #define MAGICXOR Ox2a #define ONE 1 #define BIT8 0x80 #define MASK2 0x03 #define MASK3 0x70 #define MASK4 Ox0f #define MASK8 Oxff GLDEF_C INT CompressCINT x) /* Compress input 16-bit 2's complement integer (13-bit magnitude) to 8-bit signed compressed number using A-law. oh { INT p,8,Y; /* convert 2's complement to sign bit and magnitude */ p=BIT8; /* p is the (inverted) sign bit */ if (x&0x1000) € X= -xX3 x&=OxF FF; p=0; > if (x&(MASK4<<8)) /* Find leading '1' using binary search */ € if (x&(MASK2<<10)) S=(x&(ONE<<11)) ? 7 : 6; else s=(x&(ONE<<9)) 25: 4; > else € if (x&(MASK2<<6)) s=(x&(ONE<<7)) ? 3: 2; else s=(x&(ONE<<5)) 7? 1: 0; > if (s==0) yar]; else y=((x>>s )&MASK4) | (s<<4); return((~(Cy|p) “MAGICXOR) )&MASK8) ; } Note that the final bit manipulation is done by adding in the (inverted) sign bit, performing an exclusive or with MAGICXOR (0x2a - inverting bits 1, 3 and 5) and then complementing the result. This exactly corresponds with the formal definition of A-Law compression and also has the effect of restoring the sign bit. 190 13 GENERAL SYSTEM SERVICES It would be marginally more efficient to replace the definition of MAGICXOR with: #define ALTXOR OxD5 and replace the last line of the function with: return((Cy|p) “ALTXOR )&MASK8) ; Decoding Decoding an A-Law compressed data value broadly consists of reversing the process that is described in the previous section. The bit manipulation is first reversed, by re-inverting bits 0, 2, 4 and 6. This results in the range of possible 8 bit values as follows: | poorer ooe HHHNn HD FRPRHOOOO eco neS rs 9 Bas @ Leer Ric Re Decompressing these 8 bit inputs using A-Law decoding leads to the following 13 bit outputs: la fol, la La Se leas ese oe ee ae) a ee eee SS EE eee ee ee ee ee eee OFMBADRHFOA ooornan oy oooornpnan eoo0ooo4rwrnan OO0O0 O0OFRFR uaHHnHHH OH PFPOOOC0O0O00 MrRPODTCO0OO0 oMrPODOOO avMrRPAGD00 Aadaorwm~raod PARaUmrRPOSD oornan um w The decompressed data is computed according to the following rules: = the most significant bit preserves the sign bit of the compressed data item = the following three bits of the compressed data are used to determine the position of the most significant non-zero bit of the decompressed data (but note that the smallest magnitude group is treated exceptionally) = the remaining four bits of the compressed data are used as the next four most significant bits of the decompressed data (in all but the first group, these are the four bits that follow the most significant non-zero bit) = the next most significant bit of the decompressed data is set to one and any remaining trailing bits are set to zero. The resultant value is thus set to the mean of all the possible values which, on compression, give the value that is being decoded If the output data is required in 16-bit two's complement format, then the sign bit must be extracted from the data and, if it is set, the decoded value should be negated. Examples of decoding are given in the following table, where brackets identify the sign bit. encoded data compressed data 13-bit data signed output (1)1010101 (1)0000000 (1)000000000001 -1 (0)1010100 (0)0000001 (0)000000000011 +3 (1)0010010 (1)1000111 (1)000101111000 ~-376 (0)0100101 (0)1110000 (0)100001000000 +2112 The starting values in this table are the results from the encoding examples given in the previous section. Notice that compression followed by decompression loses some of the information, as is expected. As for encoding, A-Law decoding can be performed by means of a look-up table. In this case, a 256- element table is sufficient. It is, however, simple to perform the decoding algorithmically, as illustrated in the following code: 191 PLIB REFERENCE SEE #def ine XORMASK 0x55 #define BIT8 0x80 #define MASK3 0x70 #define MASK4 Ox0f #define MASK8 Oxff GLDEF_C INT ALawDecode(INT x) /* Expand input 8-bit signed compressed number to 16-bit 2's complement integer (13-bit magnitude) using A-law. */ € INT s,y; x=(x° XORMASK )&MASK8; S=(X&MASK3 )>>4; y=0; if (s) € y=0x10; s-=1; > y=(( Cyt (X&MASK4 ) )<<1)4+1)< ———E_:__ SS en ee ee Overview of database files Database files (DBFs) are binary files containing typed, variable length records. Many SIBO applications (for example, the MC Diary and the Series 3 Database) store their data in database files. The data files created and manipulated by OPL are also examples of database files. DBFs are designed to be Flash-friendly, that is, they may be stored and manipulated in Flash SSDs (or any other EPROM medium). A DBF stored on such a medium may be modified by appending, deleting or replacing records without having to make a new copy of the entire file. A freshly formatted SSD has, apart from a short header, all bytes set to Oxff, that is, all bits are set to one. Writing to an SSD consists of selectively clearing bits to zero. On a Flash SSD it is not possible (except by reformatting the whole SSD) to overwrite a zero with a one. Data can be overwritten, provided the new value can be derived from the old one solely by clearing bits to zero. DBFs take advantage of this fact by reserving record type zero to represent a deleted record. This has a number of implications for DBFs. The most fundamental is that deleting a record does not reduce the size of the file, since all that happens is that the record type is overwritten with zero. A DBF containing deleted records can be reduced in size by: = calling DbfCompress, provided the file is stored on a compressible medium = using the DbfCopyFile service to copy the file record by record, since deleted records will not be copied. Furthermore, updating a record can only be performed by deleting the original record and appending the modified version. Thus, updating a record must always move it to the end of the file. The DBF service functions described in this chapter enhance access to database files by providing: = an option to access records via a sparse index, with an index entry for every sixteenth record = read-ahead buffering with, typically, a 4k buffer = optimised searching, to locate a record by content. The optional index table resides in a separate segment (with segment name DBF$nnnn.INX, where nnnn is a 4 digit hexadecimal number derived from the open DBF channel number) so as not to use any of the application's data space. It enables fast access to a record by its record number and allows for fast backwards scanning of the file. The index table consists of a 4 byte address for every sixteenth record. Addresses are appended to the table as necessary when records are added to the file. Deleting a record causes the addresses to be adjusted as necessary so that they continue to point to every sixteenth record. A read call to the file server from a DBF service will fill the read-ahead buffer, typically reading many records. This reduces the number of separate calls to the file server during a sequential scan of the file and increases the speed of operation of many of the DBF services. 195 PLIB REFERENCE In general, DBF services may overwrite the buffer contents. The next read of a DBF record following a modification of the buffer contents will cause the entire buffer to be read in again, inevitably resulting in a loss of performance. Since this process assumes a knowledge of the buffer contents, it is essential that all modifications to the buffer contents are either performed via DBF services or are accompanied by a call to DbfTrash. In this respect it is worth noting that the services in the following list are guaranteed not to alter the buffer: DbfFlush DbfVersion DbfAppend DbfSense DbfCount The file header Database files start with a 22 byte standard header containing the following information: Byte offset in header Information 0-15 Zero terminated file signature. 16, 17 Version of DBF software used to produce the file. 18, 19 Offset from the start of the file to the first record. 20, 21 Minimum version of DBF software required. All 16 bytes of the file signature are used for verification, not just the zero terminated string. It is therefore essential that all file signatures fill the whole 16 bytes. If necessary, you should pad out the signature string with trailing zeros. See the DbfVersion service for the format of the version numbers. The file offset to the first record permits the use of an extended header, with additional application- specific information following the standard header. If an extended header is not used, the value should be 22. Records The records are of variable length, with the type and length contained in a leading word header. In memory a record occupies a DbfRecord struct, defined in p_dbf.h as: typedef struct € UWORD header; /* Used for record header word */ UBYTE data[2]; /* Data to be written... */ } DbfRecord; The most significant four bits of header contain the record type, in the range 0 to 15 (Oxf). The remaining twelve bits store the record length. DBF records are restricted to a maximum length of 4094 bytes, which is one byte less than the theoretical maximum of 4095 (Oxfff) bytes. The record types are classified as follows: 0 Deleted record. These records are ignored by all DBF services. In particular, they are never copied by the DbfCopyFile service. 1 Standard data record, containing a number of fields corresponding to the field sequence specified by the field information record, described below. Most DBFs will contain, apart from deleted records, only type 1 records, one field information record and, optionally, one descriptive record (described below). 2 Field information record, used to store the field structure used by other records. There must be a field information record in each file and it must be the first record in the file. Any subsequent type 2 records will be ignored. The content of this record is described below. 196 14 DATABASE FILES SSeS 3 Descriptive record. A DBF may optionally contain a record of this type, containing file-wide application-specific data (such as the screen font to use). The content of such a record consists of one or more variable length sub- records, with a word header containing the type and length, exactly as for the main records. The sub-record types are specific to the creating application. There is further information about descriptive records in the descriptions of the DbfDescRecordRead and DbfDescRecordwrite services. 4-7 Application-specific records that are copied to a new file, but not appended to an existing file by the DbfCopyFile service. 8-13 Application-specific records that are both copied to a new file and appended to an existing file by the DbfCopyFile service. 14 Reserved for voice records, containing information that is generated and interpreted by a voice device driver. 15 Reserved for internal use - not to be used by applications. The field information record contains up to 32 bytes, each indicating the type of the corresponding field in the data records (it follows that a data record may contain a maximum of 32 fields, but there is an exception, described later). The possible values for each byte are: 0 Word Long 2 Double 3 String 4-255 Reserved The file opening services open a DBF in such a way that only one record type (usually type 1) is visible to the DBF services. There is no requirement for all record types to conform to the structure specified in the field information record, but it is expected that, for normal use, type 1 records will do so. The only service that assumes the record structure matches the content of the field information record is DbfFindRead. Records are not restricted to contain the same number of fields as listed in the field information record. They may contain fewer fields, provided that only trailing fields are omitted. If a record contains more than the number of fields specified in the field information record, it is assumed that the additional ones are string fields. A record that contains only string fields is not restricted by the normal maximum of 32 fields; it may contain any number of fields, subject to the overall 4094 byte limit on the record length. An application that uses several record types may: = open the file for one record type at a time, closing the file and reopening it to access records of a different type = — open the file for one record type and handle the reading and writing of records of other types independently of the DBF services String fields String fields contain leading byte counted text, and thus a normal string field may not contain more than 255 characters. However, longer strings may be stored by making use of continuation sub-fields. In such a case, the first 254 characters of the string and a terminating byte of value 0x14 are stored in an otherwise normal string field, with a count byte containing the value 255. The terminating 0x14 character, coupled with a length byte of 255, indicates that further string characters are contained in an immediately following string field. This following field is considered as a continuation sub-field of the previous one. The same mechanism may be used in a continuation sub-field to extend the string text into a further continuation sub-field. Subject to the overall restriction that a record may not exceed 4094 bytes, there is thus no limit on the length of text that may be stored in a single database string field. 197 PLIB REFERENCE Number of records The DBF services are restricted to files containing a maximum of 65534 records, numbered from 0 to 65533. Since only one record type is visible via the DBF services, a DBF may contain more than this maximum, provided there are not more than 65534 records of any one type. A file containing more than the maximum number of (visible) records will, on opening, be logically truncated to contain the maximum number of records. End of file record When any of the record services attempts to read past the end of the file, the error £_FILE_EoF will be returned. The current record number (the record number returned by DbfSense) will then be the number of the last record plus 1. This (fictitious) record is known as the end of file record. Attempting to read before the first record in the file, with either the pbfBackRead service or the DbfFindRead service, will also result in an €_FILE_EOF error. In this case the current record number will be zero. This will normally refer to the first record in the file, but may, if the file contains no records, refer to the end of file record. If the file contains no records, DbfSense will always return zero, again referring to the end of file record. At any time that the current record number refers to the end of file record, those services that operate on the current record, such as Dbf€raseRead or DbfUpdate, will do nothing to the record and return E_FILE_EOF. Database files and OPL OPL data files, created and manipulated by the OPL data file commands, are database files. They are created with a file signature string of "opLDatabasef ile" and contain, in addition to the leading field information record, only standard data (type 1) records. OPL can open and manipulate database files created by other applications, with the following restrictions: = The file must have a file signature string of "OPLDatabasefF ite" = Records other than the leading field information record and standard data (type 1) records are ignored by OPL aS a eS a Se a ey, eee ee DBF functions INT DbfOpen(INT *pstate, VOID **pFcb, TEXT *fName, UINT mode, DbfHeader *pHead, UBYTE *pbuffer, UINT Len, UINT type); Open a channel to the database file specified by the zero terminated file specification fName and, if successful, return zero and write the channel to «pFcb (pFeb is not written to if the open fails). See also DbfauickOpen. The file specification fName is parsed with a NULL related name (see p_fparse). If this fails, the open fails and retums the return value from p_fparse. The mode in which the file is opened is selected by mode, which should contain one (and only one) of: P_FOPEN P_FCREATE P_FREPLACE P_FAPPEND P_FUNIQUE optionally ored with one of: P_FUPDATE P_FSHARE For the meanings of these flags, see the description of p_open(P_FSTREAM) in the Files chapter. The DBF services make no distinction between files opened with either P_FOPEN or P_FAPPEND. All other required 198 14 DATABASE FILES __ SS eS mode flags are supplied automatically. Opening a DBF with mode equal to p_FUNIQUE will write the unique name to fName. The value of *pstate may be one of: DbfStateDisabled Opens the file with a sparse index. The file is open and the index is fully built when the call to DbfOpen returns, but the call may take an extended time to return. DbfStateOpenNo! ndex Opens the file without an index. The call to pbfopen returns much faster than for the previous case. Not all DBF services may be used on a file opened without an index. See the descriptions of the individual services for further details. DbfStateStart Opens the file with a sparse index. The open process may not be complete when the call to Dbfopen returns, depending on the value written back to *pstate. DbfOpen must be called repeatedly, passing the value written to *pstate by the previous call to Dbfopen, until the value written to *pstate is DbfStateStart. This should be used in cases (such as the need to remain responsive to user input) where an extended time to return is unacceptable. The parameter pHead is a pointer to a DbfHeader struct, defined in p_dbj.h as: typedef struct € UBYTE fileType[DbfHeaderNameSize]; /* 16 byte file signature */ UWORD createVersion; /* software version used to create file */ UWORD dataStart; /* offset in file of first record */ UWORD needVersion; /* minimum software version needed to handle this file */ UWORD firHeader; UBYTE fir ([DbfMaxFirLength) ; >} DbfHeader; When creating a new file or replacing an existing file, all elements of this struct should be pre-filled with the header and field information record data described earlier. Note that there is no gap between the header and the field information record, even if an extended header is required. The file itself, however, will contain a gap for the extended header, the length of which is 22 bytes less than the value in the dataStart field. When opening an existing file, the fileType field must be pre-filled with the file signature. The remainder of the DbfHeader struct will be filled in with the relevant data read from the file. All 16 bytes of the file signature will be verified against the signature in the file and E_FILE_INVALID is returned if the two signatures are not identical. The following checks are made in all cases, regardless of whether the information is provided by the user or read from an existing file. s The needVersion field is checked against the DBF software version number (returned by the DbfVersion service). The major version number in needVersion must not exceed the current DBF software major version number (see the DbfVersion service for the format of version numbers). It is the application's responsibility to perform any further validation of the version number. ® The field information record data is checked to be the correct type and of a length not exceeding the maximum length (32). An €_FILE_INVALID error is returned if any of these checks fail. The address and length of a user-supplied read-ahead buffer are passed in pbuffer and Len respectively. Each read call to the file server from a DBF service will read ten bytes into this buffer. In general, the buffer will contain more than one record. A DBF read service to access a record that, as a result of an earlier read, is already in the buffer will simply locate the record within the buffer. The buffer length, in ten, must be in the range 512 to 16384. Any value outside this range will cause DbfOpen to fail with an E_FILE_RECORD error. In addition, the buffer should be at least as large as the largest record in the file. Opening a file with a sparse index and a buffer which is smaller than the largest record will cause pbfopen to fail with an E_FILE_RECORD error (this error will not be reported when opening a DBF without an index). A buffer of 4096 bytes is guaranteed to be sufficient for all database files. Only records of type equal to type are visible. In most cases, type will be 1 but could, exceptionally, be in the range 4 to 14 inclusive. No check is made on the value of type, but the results of opening a file will be unpredictable if type is 0, 2, 3 or greater than 14. 199 PLIB REFERENCE Immediately after opening the file, the current record number (as returned by DbfSense) will be 0, so a call to DbfNextRead would read record number 1 and DbfEraseRead would erase record 0. To read record 0 you should call pbfFirstRead. No error is returned if the opened file contains more than the maximum number (65534) of records of any one type. The DBF services will treat such a file as if it contained the maximum number of records. Apart from the errors explicitly mentioned above, Dbfopen may fail with any of the errors returned by p_open(P_FSTREAM), p_seek OF p_read. INT DbfQuickOpen(INT *pstate, DbfOpenArgs “pargs, UBYTE *pbuffer, UINT len, UINT type); Open a channel to a database file, as for Dbfopen, except that a number of the parameters are passed in a DbfOpenArgs struct, defined in p dbf.h as: typedef struct { VOID **pFcb; UBYTE *fName; UINT mode; DbfHeader *pHead; } DbfOpendrgs; The meanings of the struct members and the remaining parameters are exactly as described for Dbfopen. DbfQuickOpen returms zero if successful, otherwise it returns errors as for DbfOpen. DbfauickOpen should be used in preference to Dbfopen since it provides more efficient and shorter code. The DbfOpen service is retained for compatibility reasons. INT DbfClose(VOID *pFcb); Close a database file, returning zero for success (the negative error returns are as for p close). The file channel is closed, even if Dbfclose returns an error. May be used on a DBF opened without an index. Calls p_panic if pfeb is not a pointer to valid DBF file channel data, generated by an earlier call to DbfOpen OF DbfQuickOpen. INT DbfFlush(VOID *pFcb); Flush all buffers, ensuring that all modified data is written to the DBF. Returns zero for success (the negative error returns are as for p write). May be used on a DBF opened without an index. Calls p_panic if pFcb is not a pointer to valid DBF file channel data, generated by an earlier call to DbfOpen Or DbfaQuickOpen. VOID DbfTrash(VOID *pFcb); Inform the DBF services that the buffer of the file corresponding to the channel data in pfcb has been overwritten by the caller and that the contents of the buffer can not be relied on. See also DbfCopyDown. May be used on a DBF opened without an index. Calls p_panic if pFeb is not a pointer to valid DBF file channel data, generated by an earlier call to DbfOpen or DbfQuickOpen. 200 14 DATABASE FILES INT DbfCopyDown(VOID *pFcb, UINT offset); Copy a record at offset in the DBF buffer to the start of the buffer and sets a flag to signal that the buffer is no longer valid (i.e. there is no need to call DbfTrash). The length of the record is read from the buffer and is returned by the service. It is assumed that offset is the position in the buffer of a valid record, given by an earlier call to DbfAbsRead, DbfAbsReadSense, DbfNextRead, DbfBackRead, DbfFirstRead, DbfLastRead, DbfEraseRead or DbfFindRead. The results will be unpredictable if this is not the case, or if the caller has written to the buffer since making one of the above calls. May be used on a DBF opened without an index. Calls p_panic if pFcb is not a pointer to valid DBF file channel data, generated by an earlier call to DbfOpen or DbfQuickOpen. abase file INT DbfCompress(UINT *pstate, VOID *pFcb); Recover space used by deleted records (provided the file is stored on a compressible medium), returning zero for success. If the medium is not compressible, this service will do nothing but will still return zero. After calling this service, the current record will be the end of file record (unless the medium was not compressible - in which case the current record is unchanged). The value of *pstate may be one of: DbfStateDisabled The file compression is complete when the call to Dbfcompress returns, but the call may take an extended time to return. DbfStateStart The file compression may not be complete when the call to DbfCompress returns, depending on the value written back to *pstate. DbfCompress must be called repeatedly, passing the value written to *pstate by the previous call to DbfCompress, until the value written to “pstate is DbfStateStart. This should be used in cases (such as the need to remain responsive to user input) where an extended time to return is unacceptable. Should not be used on a file opened without an index. Calls p_panic if pFeb is not a pointer to valid DBF file channel data, generated by an earlier call to DbfOpen or DbfQuickOpen. INT DbfCopyFileCUINT *pstate, VOID “pFcb, TEXT *pTargetName, UINT targetMode, UINT type, INT dir); Copy records (except deleted records) in either direction between the current file (specified by pFcb) and the file named in pTargetName, returning zero for success. This service may be used either to copy records to a new file or to append records to an existing file. The value of *pstate may be one of: DbfStateDisabled The copy is complete when the call to DbfcopyFile returns, but the call may take an extended time to return. DbfStateStart The copy may not be complete when the call to pbfcopyFile returns, depending on the value written back to *pstate. DbfCopyf ile must be called repeatedly, passing the value written to *pstate by the previous call to DbfCopyFile, until the value written to *pstate is DbfStateStart. This should be used in cases (such as the need to remain responsive to user input) where an extended time to return is unacceptable. An estimate of the number of calls required to complete the copy is to divide the source file size by the buffer size and add 2. DbfStateCopyAbort aborts a copy that was started with the state pbfstateStart. The mode in which pTargetName is opened is specified by targetMode, with the same options as for DbfOpen. If targetMode is P_FUNIQUE, the unique file name is written to pTargetName. In all cases, this file is opened without an index. 201 PLIB REFERENCE —_ EEE The direction of the copy is determined by dir: DbfCopyFromHandle copies records from the current file to the file specified by pTtargetName. To copy the current file, targetMode should be P_FCREATE, P_FREPLACE Or P_FUNIQUE. If appending records to an existing file, targetMode should be p_FOPEN or P_FAPPEND. DbfCopyToHandle appends records from the file specified by pTtargetName into the current file. In this case targetMode can sensibly only be P_FOPEN. Appending records to the current file may be slower than a copy or append from the current file because of the need to update the index (if it exists). Which records are copied is determined by type. This may specify either a single type (normally type 1) or (by passing the value DbfRecordTypeAlt) all record types. There are special cases that depend on the nature of the copy: = when copying to a new file (targetMode is P_FCREATE, P_FREPLACE or P_FUNIQUE) the field information (type 2) record is always copied to the new file, regardless of the value of type. = when appending records to an existing file (targetMode is P_FOPEN or P_FAPPEND) records of type 2 to 7 inclusive (which therefore includes the field information record and the descriptive record) are never copied, regardless of the value of type. If the copy is to a new file (targetMode is P_FCREATE, P_FREPLACE Or P_FUNIQUE) the file header (including any extended header) is copied to the new file. If any error occurs during the copy the target file will be deleted, if possible. If the copy appends records to an existing file (targetMode is P_FOPEN Or P_FAPPEND) the signatures of the two files are verified and the field information records are checked to be compatible (either identical, or both containing only string fields). If either test fails the call to DbfcopyFile will return E_FILE_INVALID. May be used on a DBF opened without an index. Calls p_panic if pfcb is not a pointer to valid DBF file channel data, generated by an earlier call to DbfOpen Or DbfauickOpen. In addition to errors explicitly mentioned above, DbfCopyFile error returns are as for p_open, p_read and p write. WARNING: using DbfCopyFile to append records can result in a file containing more than 65534 records of a particular type. No error is given if this occurs. INT DbfFileSizeC(VOID *pFcb, ULONG *pSize); Writes the size of an open database file to *psize, returning zero for success. May be used on a DBF opened without an index. Calls p_panic if pFcb is not a pointer to valid DBF file channel data, generated by an earlier call to DbfOpen Or DbfQuickOpen. Error returns are as for p_seek. INT DbfExtHeaderRead(UINT cont, VOID *pFcb, VOID *buf, UINT len); Read up to len bytes from the extended header of a database file and write the data to buf, returning the actual number of bytes read. If there are fewer than Len bytes left before the end of the extended header, the number of remaining bytes are read and returned. If the current position is already at the end of the extended header the negative number E_FILE_EOF is returned. All other error returns are as for p_read and p_seek. A long extended header may be read in sections. A value of 0 for cont signifies an initial read of the extended header and resets the current file position to the start of the extended header before reading. For subsequent reads, cont should be set to 1. If the whole extended header is read in a single call to DbfExtHeaderRead, cont must be set to 0. May be used on a DBF opened without an index. Calls p_panic if pfcb is not a pointer to valid DBF file channel data, generated by an earlier call to DbfOpen or DbfQuickOpen. 202 14 DATABASE FILES INT DbfExtHeaderWriteCUINT cont, VOID *pFcb, VOID *buf, UINT len); Write up to len bytes of data from buf into the extended header, returning the actual number of bytes written. If there are fewer than ten bytes left before the end of the extended header, the number of remaining bytes are written and returned. If the current position is already at the end of the extended header the negative number E_FILE_EOF is returned. All other error returns are as for p_write and p seek. A long extended header may be written in sections. A value of 0 for cont signifies an initial write of the extended header and resets the current file position to the start of the extended header before writing. For subsequent writes, cont should be set to 1. If the whole extended header is written in a single call to DbfExtHeaderWrite, cont must be set to 0. May be used on a DBF opened without an index. Calls p_panic if pFeb is not a pointer to valid DBF file channel data, generated by an earlier call to DbfOpen Or DbfduickOpen. INT DbfDescRecordRead(VOID *pFcb); Read the descriptive record to offset zero in the file's read-ahead buffer. The descriptive record contains variable length sub-records, in the same format as main records, with the record types being defined by the creating application. The reader should ignore (and not delete) unrecognised sub-record types. Returns the length of the descriptive record, if it exists, otherwise E_FILE_EOF. Returns E_FILE_INVALID if the file was opened without an index. Calls p_panic if pFcb is not a pointer to valid DBF file channel data, generated by an earlier call to DbfOpen or DbfQuickOpen. Other error returns are as for p_seek and p read. INT DbfDescRecordWrite(VOID *pFcb, UINT Len); Write a descriptive record from the data at offset zero in the file's read-ahead buffer, returning zero for success. The data in the buffer be a DbfRecord structure, that is the record content must start at offset 2. The first two bytes are used to construct the header for the record (see DbfAppend). These two bytes should not be included in len, the length of the record. Any existing descriptive record will be erased, so that there is no more than one descriptive record per file. The application should ensure that any unrecognised sub-record types are preserved from any previously existing descriptive record. If len is passed as zero, any existing descriptive record will be erased and no new one will be written out. Returns E_FILE_INVALID if the file was opened without an index. Calls p_panic if pFeb is not a pointer to valid DBF file channel data, generated by an earlier call to DbfOpen OF DbfQuickOpen. Other error returns are as for p_seek, p_read and p write. DbfVersion = = Get the DBF version number UINT DbfVersion(VOID); Return the version number of the DBF software. This will be a hexadecimal number in the form xyvF where: x is the major version number (4 bits) YY is the minor version number (8 bits) F is the release type, either A,B or F for Alpha, Beta or Final respectively (4 bits). 203 PLIB REFERENCE For example, if 110FH is returned, the DBF software version is 1.10F. Note that only the major version number is used to determine whether or not the DBF file system can handle a particular file. May be used on a DBF opened without an index. INT DbfAbsRead(VOID *“pFceb, UINT recnum, UWORD *pOffset); Seek to and read (into the read-ahead buffer) record number recnum, returning the length of the record or a negative error. Record recnum becomes the current record and the offset of the record within the read-ahead buffer is written to *poffset. This offset indicates the start of a DbfRecord struct (including the header) but the returned record length is the length of the record data, excluding the header. If recnum is greater than the last record, the error E_FILE_EOF is returned, the current record is set to the end of file record and *poffset is not valid. May be used on a DBF opened without an index. Calls p_panic if pfcb is not a pointer to valid DBF file channel data, generated by an earlier call to DbfOpen Or DbfQuickOpen. Other error returns are as for p_seek and p read. Re INT DbfAbsReadSense(VOID *pFcb, UINT recnum, UWORD *pOffset, ULONG *pPos); Seek to and read (into the read-ahead buffer) record number recnum, returning the length of the record or a negative error. Record recnum becomes the current record and the offset of the record within the read-ahead buffer is written to *poffset. This offset indicates the start of a DbfRecord struct (including the header) but the returned record length is the length of the record data, excluding the header. The file position of the header at the start of the record is written to *pPos. If recnum is greater than the last record, the error €_FILE_EOF is returned, the current record is set to the end of file record and *poffset is not valid. May be used on a DBF opened without an index. Calls p_panic if pfcb is not a pointer to valid DBF file channel data, generated by an earlier call to DbfOpen or DbfQuickOpen. Other error returns are as for p_seek and p_read. INT DbfNextRead(VOID *pFcb, UWORD *pOffset); Seek to and read (into the read-ahead buffer) the next record, returning the length of the record or a negative error. This record becomes the current record and the offset of the record within the read-ahead buffer is written to *poffset. This offset indicates the start of a ObfRecord struct (including the header) but the returned record length is the length of the record data, excluding the header. If the current record is already the last record, or the file contains no records of the current type, the error £_FILE_EOF is returned, the current record is set to the end of file record and *poffset is not valid. May be used on a DBF opened without an index. Calls p_panic if pFeb is not a pointer to valid DBF file channel data, generated by an earlier call to DbfOpen or DbfQuickOpen. Other error returns are as for p_seek and p_read. _ Read the previo INT DbfBackRead(VOID *pFcb, UWORD *pOffset); Seek to and read (into the read-ahead buffer) the previous record, returning the length of the record or a negative error. 204 14 DATABASE FILES This record becomes the current record and the offset of the record within the read-ahead buffer is written to *poffset. This offset indicates the start of a DbfRecord struct (including the header) but the returned record length is the length of the record data, excluding the header. If the current record is already the first record, or the file contains no records of the current type, the error E_FILE_EOF is returned, the current record is set to record number 0 (which may be the end of file record) and *poffset is not valid. May be used on a DBF opened without an index. Calls p_panic if pFcb is not a pointer to valid DBF file channel data, generated by an earlier call to DbfOpen OF DbfQuickOpen. Other error returns are as for p_seek and p read. INT ObfFirstRead(VOID “pFcb, UWORD *pOffset); Seek to and read (into the read-ahead buffer) the first record, returning the length of the record or a negative error. This record becomes the current record and the offset of the record within the read-ahead buffer is written to *poffset. This offset indicates the start of a DbfRecord struct (including the header) but the returned record length is the length of the record data, excluding the header. If the file contains no records of the current type, the error E_FILE_EOF is returned, the current record is set to record number 0 (which is the end of file record) and *poffset is not valid. May be used on a DBF opened without an index. Calls p_panic if pfcb is not a pointer to valid DBF file channel data, generated by an earlier call to DbfOpen or DbfQuickOpen. Other error returns are as for p_seek and p_read. pbtLa INT DbfLastRead(VOID *pFcb, UWORD *pOffset); Seek to and read (into the read-ahead buffer) the last record, returning the length of the record or a negative error. This record becomes the current record and the offset of the record within the read-ahead buffer is written to *poffset. This offset indicates the start of a DbfRecord struct (including the header) but the returned record length is the length of the record data, excluding the header. If the file contains no records of the current type, the error E_FILE_EOF is returned, the current record is set to record number 0 (which is the end of file record) and *poffset is not valid. Should not be used on a DBF opened without an index. Calls p_panic if pFeb is not a pointer to valid DBF file channel data, generated by an earlier call to DbfOpen OF DbfQuickOpen. Other error returns are as for p_seek and p read. INT DbfAppend(VOID *pFcb, UINT len); DBF record Append a record of the current type, and of length len, to the end of the file and make this the current record, returning zero for success. The record to be appended must be stored at the start of the read-ahead buffer as a DbfRecord struct (defined in p_dbf.h) including the leading two byte header. DbfAppend uses these two bytes to construct the type and length header for the record. This header should not be included in ten, which is the length of the data only. The error E_GEN_OVER is returned if there are already 65534 records of the current type in the file. If the total length of the record (including the two byte header) is greater than the length of the read-ahead buffer then £_FILE_RECORD is returned. Other error returns are as for p_seek and p_write. Should not be used on a DBF opened without an index. Calls p_panic if pFeb is not a pointer to valid DBF file channel data, generated by an earlier call to DbfOpen Or DbfauickOpen. 205 PLIB REFERENCE INT DbfEraseRead(INT *pstate, VOID *pFcb, UWORD *pOffset); Erase the current record and read (into the read-ahead buffer) the following record, returning the length of the record read, or a negative error. This record becomes the current record and the offset of the record within the read-ahead buffer is written to *poffset. This offset indicates the start of a DbfRecord struct (including the header) but the retumed record length is the length of the record data, excluding the header. There are two separate circumstances in which DbfEraseRead may return E_FILE_EOF: s if the current record is already the “end of file" record (or there are no records) « if the current record is the last record. In the first case, the service does nothing. In the second case, the last record is erased and the current record becomes the end of file record. It is the application's responsibility to distinguish, if necessary, between these two cases. This may be done either by checking if the current record is the "end of file" record (using DbfSense and DbfCount) before calling Dbf€raseRead, or by using DbfCount before and after the call to determine if the record count has decreased. The value of *pstate may be one of: DbfStateDisabled The erase and read process is complete when the call returns, but the call may take an extended time to return. DbfStateStart The erase and read process may not be complete when the call returns, depending on the value written back to “*pstate. DbfEraseRead must be called repeatedly, passing the value written to *pstate by the previous call to DbfEraseRead, until the value written to *pstate is DbfStateStart. This should be used in cases (such as the need to remain responsive to user input) where an extended time to return is unacceptable. Should not be used on a DBF opened without an index. Calls p_panic if pcb is not a pointer to valid DBF file channel data, generated by an earlier call to DbfOpen Or DbfQuickOpen. INT DbfUpdateCINT *pstate, VOID “pFcb, UINT Len); Erase the current record and append a new record, of length ten, from the read-ahead buffer, making this the current record. Returns zero for success, or a negative error. The record to be appended must be stored at the start of the read-ahead buffer as a DbfRecord struct (that is, including a leading two bytes). DbfAppend uses these two bytes to construct the type and length header for the record. This header should not be included in ten, which is the length of the data only. Note that the current record is not erased until after the new record has been successfully appended. DbfUpdate will do nothing and return E_FILE EoF if there are no records of the current type, or if the current record is the end of file record. The value of *pstate may be one of: DbfStateDisabled The update is complete when the call returns, but the call may take an extended time to return. DbfStateStart The update may not be complete when the call returns, depending on the value written back to *pstate. DbfUpdate must be called repeatedly, passing the value written to *pstate by the previous call to DbfUpdate, until the value written to *pstate is DbfStateStart. This should be used in cases (such as the need to remain responsive to user input) where an extended time to return is unacceptable. Should not be used on a DBF opened without an index. 206 14 DATABASE FILES Calls p_panic if pFeb is not a pointer to valid DBF file channel data, generated by an earlier call to DbfOpen or DbfQuickOpen. UINT nStrings, UWORD *pOffset, UINT startStr); This function is only available in EPOC version 3.18 or later. Match the wildcard text at ppuffer and of length len (which must not exceed 255 bytes), in the nstrings string fields starting at string field number startstr (a value of zero for startStr starts matching at the first string field). The match is attempted on each record, starting at the current record. Returns the length of the first record found to contain a match or, if no match is found, a negative error. This service must only be used on records which conform with the content of the field information record. No error is reported if any particular record has fewer than nStrings string fields. If nstrings has the value DbfFindal Strings the search for a match will continue through all string fields until the end of the record. The field information record is used to determine the types of up to the first 32 fields. Fields in excess of those defined in the field information record are assumed to be string fields. findMode specifies the type of search and is made up of three parts which must be ored together: The maximum length over which the match is made in any one string field is passed in findMode. String fields are effectively truncated to this length before matching. The maximum length that may be specified is 255, implying no truncation. The starting point and direction of the search is specified by oring one of the following into findMode: DbfFindForwards Search forwards from current record to next match. DbfFindBackwards Search backwards from current record to previous match. DbfFindFirst Search to first match in file. DbfFindLast Search to last match in file. The type of match that is made is specified by oring either of the following into findMode: DbfFindCaselndependent Case independent match. DbfF indCaseDependent Case dependent match. If a match is found, the record containing the match becomes the current record and the offset of the record within the read-ahead buffer is written to *poffset. This offset indicates the start of a DbfRecord struct (including the header) but the returned record length is the length of the record data, excluding the header. If a match is not found, E_FILE_EoF is returned and “poffset is no longer valid. The current record will then be either the first record if the search was backwards, or the end of file record if the search was forwards. The value of *pstate may be one of: DbfStateDisabled The find is complete when the call returns, but the call may take an extended time to return. DbfStateStart The find may not be complete when the call returns, depending on the value written back to *pstate. DbfFindRead must be called repeatedly, passing the value written to *pstate by the previous call to pbfFindRead, until the value written to *pstate is DbfStateStart. This should be used in cases (such as the need to remain responsive to user input) where an extended time to return is unacceptable. May be used on a DBF opened without an index, except for a findMode that specifies pbfFindLast, for which the result is unpredictable. Calls p_panic if findMode is improperly constructed, or if pFcb is not a pointer to valid DBF file channel data, generated by an earlier call to Dbfopen or DbfauickOpen. Other error returns are as for p_seek and p_read. 207 PLIB REFERENCE Finding across continuation sub-fields As described above, a search using DbfF indReadField will not locate text that is contained, in whole or in part, in a continuation sub-field. If it is possible that a database may contain string fields more than 255 characters in length, the values of the Len and findMode parameters must be modified to force the search to extend into continuation sub- fields. Firstly, the value 0x1400 must be ored into Len (whose unmodified value cannot exceed 255). Secondly, the value of findMode must be constructed as follows: = the length component of findMode, representing the maximum length over which the match is made in any one string field, must be set to 255 = the value 0x4000 must be ored into findMode ® only case-independent matching is allowed, so findMode must be ored with DbfF indCaseIndependent ® the starting point and direction of the search is specified, as before, by oring one of DbfFindForwards, DbfFindBackwards, DbfFindFirst Or DbfFindLast into findMode INT DbfFindRead(UINT *pstate, VOID *pFcb, VOID *pBuffer, UINT len, UINT findMode, UINT nStrings, UWORD *pOffset); Match the wildcard text at pBuffer and of length len (which must not exceed 255 bytes), with the first nStrings string fields of each record, starting at the current record. Returns the length of the record containing a match, if found, or a negative error. Calling DbfDindRead is equivalent to calling DbfFindReadField with startStr set to zero. This service must only be used on records which conform with the content of the field information record. No error is reported if any particular record has fewer than nstrings string fields. If nstrings has the value obfF indAl (Strings the search for a match will continue through all string fields until the end of the record. The field information record is used to determine the types of up to the first 32 fields. Fields in excess of those defined in the field information record are assumed to be string fields. findMode specifies the type of search and is made up of three parts which must be OR'ed together: The maximum length over which the match is made in any one string field is passed in findMode. String fields are effectively truncated to this length before matching. The maximum length that may be specified is 255, implying no truncation. The starting point and direction of the search is specified by oring one of the following into findMode: DbfFindForwards Search forwards from current record to next match. DbfF indBackwards Search backwards from current record to previous match. DbfFindFirst Search to first match in file. DbfFindLast Search to last match in file. The type of match that is made is specified by oring either of the following into findMode: DbfFindCaseIndependent Case independent match. DbfF indCaseDependent Case dependent match. If a match is found, the record containing the match becomes the current record and the offset of the record within the read-ahead buffer is written to *poffset. This offset indicates the start of a DbfRecord struct (including the header) but the returned record length is the length of the record data, excluding the header. If a match is not found, £_FILE_EOF is returned and *poffset is no longer valid. The current record will then be either the first record if the search was backwards, or the end of file record if the search was forwards. 208 14 DATABASE FILES —_— eS The value of *pstate may be one of: DbfStateDisabled The find is complete when the call returns, but the call may take an extended time to return. DbfStateStart The find may not be complete when the call returns, depending on the value written back to *pstate. DbfFindRead must be called repeatedly, passing the value written to *pstate by the previous call to DbfFindRead, until the value written to *pstate is DbfStatestart. This should be used in cases (such as the need to remain responsive to user input) where an extended time to return is unacceptable. May be used on a DBF opened without an index, except for a findMode that specifies DbfFindLast, for which the result is unpredictable. Calls p_panic if findMode is improperly constructed, or if pFeb is not a pointer to valid DBF file channel data, generated by an earlier call to Dbfopen or DbfduickOpen. Other error returns are as for p_seek and p_read. Finding across continuation sub-fields As described above, a search using DbfF indRead will not locate text that is contained, in whole or in part, in a continuation sub-field. If it is possible that a database may contain string fields more than 255 characters in length, the values of the len and findMode parameters must be modified to force the search to extend into continuation sub- fields. Firstly, the value 0x1400 must be ored into len (whose unmodified value cannot exceed 255). Secondly, the value of findMode must be constructed as follows: = the length component of findMode, representing the maximum length over which the match is made in any one string field, must be set to 255 = = the value 0x4000 must be ored into findMede = only case-independent matching is allowed, so findMode must be ored with DbfF indCaseIndependent = the starting point and direction of the search is specified, as before, by oring one of DbfFindForwards, DbfFindBackwards, DbfFindFirst Or DbfFindLast into findMode UINT DbfSense(VOID *pFcb); Return the record number of the current record. This will be the record number of the end of file record (0 if there are no records, otherwise the number of records plus one) if an immediately preceding DBF service call returned an €_FILE_EOF error. May be used on a DBF opened without an index. Calls p_panic if pFcb is not a pointer to valid DBF file channel data, generated by an earlier call to DbfOpen Or DbfQuickOpen. UINT DbfCount(VOID *pFcb); Return the number of records of the currently visible type, without altering the current record number. Should not be used on a DBF opened without an index. Calls p_panic if pFeb is not a pointer to valid DBF file channel data, generated by an earlier call to DbfOpen or DbfQuickOpen. 209 CHAPTER 15 OBJECT ORIENTED PROGRAMMING This chapter contains a complete reference description of EPOC's run-time support for object oriented programming (OOP). It does not describe the essential concepts of OOP, the compile-time tools used to build applications using OOP, or any system-supplied object class libraries. This chapter is not suitable as an introduction to the object oriented programming environment on any SIBO machine. SS SSS a ee a EE Ey Classes A class is implemented as: = aclass descriptor (a data structure that resides in a code segment) = a set of functions (normally written in C) that implement the class methods The class descriptor resides in the same code segment as its associated method functions. Class descriptor The C structure for the class descriptor (which may be of use after calling p_cpycat to copy a class descriptor to the data segment) is defined as: typedef { UWORD cat; struct p_class “super; /* superclass class */ UWORD Len; /* length of instance */ UWORD base; /* base function number */ UBYTE sig 6b; /* signature - should be Ox6b */ UBYTE num; /* number of entries in vector table */ UBYTE ncomp; /* number of component objects */ } P_CLASS; where the contents of a loaded and dynamically linked class descriptor is as follows: cat the handle of the code segment that contains the superclass class descriptor super the offset of the superclass class descriptor within segment cat or zero if this is a root class (which has no superclass) len the length of an instance of the class, including the lengths of inherited property (used by eg p_new and p_newlibh to create an instance) base the base method number corresponding to the first entry in the method table that follows the class descriptor num the number of entries in the method table ncomp the number of component objects to be automatically destroyed sig_6b a signature (which should be 0xéb) to guard against a bad class reference 211 PLIB REFERENCE This header is followed by an array of num 16-bit code segment offsets (into the same segment that contains the descriptor) of the method functions. This table contains "holes" (that is, method numbers for which there is no method function) represented by zeros. In the unlinked structure (for example, when the category descriptor resides in an executable file or a DYL), the first two fields cat and super, which identify the superclass, contain different values. These values are overwritten when the segment is loaded and dynamically linked. This is described below. Object instances An instance of a class is implemented as a cell in the heap and is created by calling p_new, f_new, f_newlibh, p_newlibh, f_newsend or f_newl ibhsend. The first two words of the cell contain the code segment handle and class descriptor offset of the class of which that object is an instance (in exactly the same way as for the superclass reference in a linked class descriptor). The remainder of the cell contains the property (if any) of that class, including any property inherited from its superclasses. A subclass contribution to the property comes after its immediate superclass contribution such that (given the restriction of single inheritance) the offset to a property contribution of a class is the same for that class as it is for any subclass of that class. All the functions (p_new etc) that create an object initialise the property with zeros. Object destruction An object is normally destroyed by sending it a message of message number zero (sending a message is described later). A root class (ie a class that has no superclass) normally contains a single method (corresponding to message number zero). This implements the default destroy method and is inherited by all other objects. The default destroy method is designed to destroy the object and all its components (and the components of components and so on) as defined by the ncomp field in the class descriptor. The destroy method function, root_destroy, is normally provided from the PLIB library. Before calling p_free to free the heap cell that represents the object instance, root_destroy scans from the youngest class to the oldest looking for non-zero ncomp values in the corresponding class descriptors. If it finds a non-zero ncomp, it assumes that the property segment contributed by that class begins with an array Of ncomp object addresses or NULLS (by convention, a NULL entry indicates an object that has either not yet been created or has already been destroyed). Each non-NuLL entry is sent a zero destroy message. Since an object is always initially zero-filled, a failure in a partially constructed compound object will have NULLs in all the right places and a single destroy method to the owner should perform the appropriate partial destroy. Object classes which create resources that are not objects (eg an I/O channel, which needs to be closed) or which wish to destroy objects in a specific order, generally subclass the destroy method to clean up the resources introduced by the class in addition to "supersending" the destroy message to the superclass to continue the process. a SS ee a rs) Categories Categories package a collection of classes into load modules and, when loaded, code segments. There are two types of categories: Image categories which contain an entry point at offset zero and are used to implement programs. The name of a code segment that contains an image category has the extension .$sc. An image category code segment is created by loading an executable using p_execc (as described in the chapter Processes and Inter- Process messaging). Dynamic library which contain no entry point, but contain classes that are referenced from categories (DYLs) image categories and other DYLs. The name of a code segment containing a DYL has the extension .pyL. A DYL code segment is created by loading a DYL load module (which may be a separate file or a partition of an executable) using p_loadl ib or p_loadfilel ib (as described in this chapter). 212 15 OBJECT ORIENTED PROGRAMMING SSeS The ROM typically contains a number (depending on the machine) of loaded and linked category code segments. Category code segments are shared - there is only one copy of a particular category in memory, however many processes are executing it. Once loaded into RAM and dynamically linked to external categories on which it depends, a category memory segment is read-only - as is any code segment. A process that accidentally tries to write to a code segment is panicked with panic number 60. A straightforward small to medium sized application typically consists of a single image category that references the built-in ROM DYLs. Programmers may develop their own DYLs for one of the following reasons: = A larger application can choose to be organised into multiple categories to limit its working set by selectively loading transient subsystem categories into memory (analogous to overlays in single-tasking operating systems). = A large application may use DYLs simply to overcome the 64K code segment limit. = An application may wish to develop an open-ended set of "polymorphic" DYLs to implement, for example, a set of different printer drivers. = To develop a general-purpose DYL which supplements the system object libraries. Dynamic libraries have the following advantages over normal (static) libraries: = only one copy of the code is present in memory however many processes are using it = the DYL code does not detract from the 64K segment limit of the application that is using it " provided you don't change the interface to the DYL (or at least make it upward compatible), you don't need to relink the applications that use the DYL when you build a new DYL Category handles A category handle identifies a category code segment, which may be in RAM or the ROM, as follows: = if the category handle is positive, it is the handle of a moveable RAM-based code segment s if the category handle is negative, it is the paragraph address of a ROM category code segment Category numbers A category code segment may also be identified by a category number which is known at compile time (category handles are only known at run time). The local category (that is, the category containing the code that makes the category reference) always has the category number zero. An external category number is the index (from 1) into an array of external category handles in the local category code segment. A category number is mainly used to create an instance of an object class using p_new, f_new Or f_newsend although it is also used by the more obscure functions p_exactsend, p_reclass and p_cpycat. The value of an external category number depends on the composition and (arbitrary) order of the external category array in the local category and different categories will, in general, use different category numbers to refer to the same external category. Because of this fact, a category number should not be passed as a parameter to an external method (for example, to create a component of variable class). When there is a requirement to pass a category as a parameter, the category handle rather than the category number should be used. The category handle may always be obtained from the category number by calling p_getlibh. Dynamic linkage A reference to an external category by category number occurs when: = aclass from an external category is subclassed by the local category = — the local category contains code that references an external category by a category number (most likely to create an instance of an external class using p_new, f_new Or f_newsend although it could also contain calls to p_exactsend, p_reclass and p _cpycat). 213 PLIB REFERENCE Before such references may be made, the category must be dynamically! linked to the external categories it references by category number. The main image category is linked by calling p_linklibc0) and DYLs are linked either by the function that loads the DYL (either p_loadt ib or p_loadfilelib) or subsequently (for reasons discussed below) by calling p_linklib. External categories are referenced by their memory segment names and it follows that, when a category is dynamically linked, all the referenced categories must be loaded. A category is dynamically linked shortly after loading it. Between loading and linking, it may be necessary to load other referenced DYLs. For the predominant case where an application is implemented as a single image category referencing only ROM-based DYLs (which are already loaded), the image may be linked by calling p_linklib at any time (normally early in main). When the application image category loads a DYL which references only those categories that are already loaded (for example, the ROM-based DYLs and the loading image category), the function that loads the DYL (either p_ltoadlib or p_loadfilelib) may be passed a parameter value that causes the function to link the DYL immediately after loading. Referencing by category handle It is possible for a category to reference an external category by category handle - most likely to create an instance of an external class using p_newlibh, f_newlibh or f_newl ibhsend or to call the more obscure p_reclassbyhandle. In this case, the handle is normally obtained independently of dynamic linkage by one of the following means: = the category handle is passed as a parameter to a method = from the segment name by calling p_findl ib (emulating dynamic linkage) = because the local category loaded the DYL using p_toadlib or p_loadfilelib A category (whether an image category or a DYL) may reference any number of external categories (which may also be images or a DYLs). Typically, the following cases occur: = an application image category references one or more DYLs (especially ROM-based DYLs) s aDYL references another DYL = an application-specific DYL references the application image category Although technically possible, the case of an image category referencing another image category is unlikely to be useful. DYLs Like the main image category, the main DYL contains code that may be shared by multiple processes. Also like the main category, DYLs are produced independently of any other category by a (static) linker. In practice, DYLs are of one of the following types: = genuine library DYLs, used by multiple applications (for example, the ROM-based DYLs) ® application-specific DYLs supplementing the application image category and containing classes which could, in principle, equally well be resident in the image category = DYLs conforming to a common interface for use by one or more applications - for example to implement a number of different file transfer protocols with a common interface DYLs that are used by more than one application should not access static data other than the reserved Static variables that are allocated at the beginning of the data segment of all applications (the structure of the data segment is discussed in the chapter Memory Allocation). 1 The term dynamic linkage is used because the link is made at run time - as opposed to normal (static) linkage between code modules, which occurs at compile time (and is used to produce a category load module - for example, an executable). 214 15 OBJECT ORIENTED PROGRAMMING SSeS For example, the ROM-based DYL OLIB.DYL uses the reserved static: GLREF_D VOID *w_am; holding the address of the one and only instance of the application manager object, which schedules the running of multiple active objects (as described in the OLIB Reference manual). Application-specific DYLs may use the seven static variables that are reserved for application programs (in the sense that the system either does not use them or it restores them if it does). These reserved statics (which are initialised by the system to zero) are: GLREF_D VOID *DatApp1; GLREF_D VOID *DatAppe2; GLREF_D VOID *DatApp3; GLREF_D VOID *DatApps; GLREF_D VOID *DatApp5; GLREF_D VOID *DatAppé; GLREF_D VOID *DatApp7; These names may be #define'd to a more descriptive name, depending on the usage, as in, for example: #define PageLayout DatApp1 Note that the code in DYLs may not introduce static variables by using quoted strings in C. For example, the code: p_open(&tcb,"TIM:",-1); is fine in an image category but can't be used ina DYL. DYLs tend to have code fragments such as: WORD b(3]; bO]=('T'<<8)+'T'? bL1]=('M'<<8)+':': b[2]=0; p_open(&tcb, (TEXT *)&b[0] ,-1); which, although hard to read, does at least produce efficient code. When writing application-specific DYLs it is technically possible to use static variables, provided all such variables are declared in a single module, included both in the link of the application image category and the DYL (analogous to FORTRAN COMMON blocks). At the time of writing, we were taking the view that this is a dangerous practice since the accidental introduction of static data - say a quoted string - would misalign the variables in the two links. The tool to build a DYL from the output of the linker fails if it detects any declared static data other than the reserved statics. Category load modules An image category is loaded from an executable by another process calling p_execc - as described in the chapter Processes and Inter-Process Messaging. The executable may have the extension .[MG or .APP but the loaded segment always has the extension . SSC. The image category is not automatically dynamically linked by p_exece because the category might reference DYLs which must first be loaded. After loading any such DYLs, the program calls p_linklib(0) to link itself. A DYL category may be loaded from one of two sources: = from a dynamic library file (which normally has the extension .DYZ) using p_loadlib * from a file containing multiple DYLs (normally an executable with the extension .APP) using p_loadfilelib after having previously opened the file using p_opent ib In the former case, the DYL is identified by its file specification. This is appropriate for genuine library DYLs and replaceable DYLs such as, for example, a printer driver DYLs. In the latter case, the DYL is identified by a number that indexes the DYLs embedded in the executable. This is appropriate for application-specific DYLs. A DYL may either be linked by the function that loads the DYL (either p_toadt ib or p_loadfiletib) or subsequently (if further referenced DYLs need to be loaded first) by calling p_linklib. 215 PLIB REFERENCE The structure of a loaded and linked category A loaded category code segment contains the following: = an external category table containing the segment handles of all externally referenced categories (to convert external category numbers into category handles) = aclass table containing the segment offsets of each class descriptor (to convert class numbers into the segment offset of the corresponding class descriptor) = aclass descriptor for each class = the method functions and other local and global functions Typically, the bulk of the code segment is filled with functions - like any other code segment. The segment offset of the external category table is stored at offset 8 in the segment where the category table consists of: = aword containing the number of entries in the following array ored with 0x8000 = the array of external category handles The segment offset of the class lookup table is stored at offset 6. The class lookup table is immediately followed by the external category table so the length of the class lookup table may be obtained from the difference between the values at offset 8 and 6. In an image category, the entry point is at address zero. The word at offset 4 in an image code segment contains the address in the data segment of the beginning of the uninitialised static variables (and the end of the initialised static variables). The structure of an unlinked category The unlinked category differs from the linked category in the following respects: = there is an external category name table instead of an external category handle table = the superclass category references in the class descriptors are by category number (zero for the local category) = the external superclass class references in the class descriptors are by class number (references to classes in the local category are by segment offset) The external category name table consists of the following: ® aword containing the number of entries in the following array (but not ored with 0x8000) = the array of external category names What happens during dynamic linkage Dynamic linkage consists of the following: = converting the external category name table into the external category handle table (and oring the number of entries word with 0x8000 to indicate that this conversion has taken place) = converting local superclass references (which have category number zero) to the handle of the local segment = using the external category table to convert external superclass references (which have category number greater than zero) to category handles and also to convert the class number to a segment offset (using the class table in the external segment) ee ay a ee Ra ee Message passing In OOP terminology, sending a message to an object means calling a method function of the class or superclass of which that object is an instance. Method functions are called by their method number, which must be between zero and 255. The method number zero is normally reserved for the method that destroys the object (and its components, if any). 216 15 OBJECT ORIENTED PROGRAMMING — eee When called, the method function is passed the address of the object instance (a cell in the heap, as returned by say p_new or p_newlibh) as its first parameter with zero to three additional parameters, depending on the method. The most common way of sending a message is to use p_send, which does the following: = — locate the class descriptor of which that object is an instance (using the category handle and class segment offset at the beginning of the instance) = if the method number is in range of the method table that follows the class descriptor, and the corresponding entry has a non-zero value in it, call the corresponding method function = — otherwise locate the superclass class descriptor and repeat the above If the process of trying to find a corresponding method in successive superclass class descriptors (sometimes called superclass chaining) fails, the sending function panics with panic number 48. The send will also panic (with panic number 55) if the category handle and class segment offset at the beginning of the instance points to a class descriptor that-does not have the correct signature. This catches, amongst other things, the sending of a message to an object that has already been destroyed. As well as p_send, which is most commonly used within method functions to send a message to an object, there is: p_supersend which is used within a method function to send a message of the same method number to the same object but to be handled by a superclass method. It works like p_send except that the search for a method starts at the immediate superclass of the class associated with the method containing the call to p_supersend. It is typically used within a subclass method that adds further processing (before, after or around the call to p_supersend) to the method being replaced. p_entersend which works like a p_send that has been called with a p enter - but more efficiently p_exactsend which can send a message to any method of any class (ignoring the category handle and class segment offset at the beginning of the instance). In OOP, it is normally used within a method function to send a message of the same method number to the same object but to be handled by a superclass method once removed - in effect a super supersend. The message sending functions (p_send etc) represent the only mechanism for calling methods when: # the method is polymorphic (where a particular send may call different method functions depending on the class of the instance to which the method is being sent) = the method function is in an external category (for example, a ROM-based DYL) ® the method function contains a call to p_supersend When writing the method functions of application-specific classes, a message send can be implemented as regular (near) function call when: = the target method function is in the same category as the sending method = the target method is monomorphic = there are no calls to p_supersend in the target method If the target monomorphic method function is in the same category as all actual and prospective sending methods, there is no reason to even include the address of the method function in the method table (which appears after the class descriptor). When writing general-purpose library DYLs, one has to be more careful about calling a local method (rather than using a message sending function such as p_send) because direct calling removes any opportunity for subclassers to divert the send to a subclass method. However, in some cases it may be positively desirable to restrict subclassers. Calling conventions for method functions A method function that is the target of any of the message sending functions (eg p_send, p_supersend or p_entersend) must use one of the following two calling conventions: CDECL where the generated code will take the parameters off the stack 217 PLIB REFERENCE METHOD_CALL where the generated code will take the parameters from the registers (which is more efficient) Note that if you call a method function directly, the prototype must be present and indicate the correct calling convention. Recall from the Error Handling chapter that the target of a p_enter must use one of: CDECL where the generated code will take the parameters off the stack ENTER_CALL where the generated code will take the parameters from the registers Since the ENTER_CALL convention is different from the METHOD_CALL convention, a method function that is a target of both p_send (or any other message sending function, including p_entersend) and p_enter must be declared as cCDECL. Performance of message sending Message sending using p_send takes longer than a direct call for the following reasons: = there is a far call via an 8086 software interrupt to get to the message sending code in the ROM = it has to find the class and index a method table to find the address of the function to call (possibly more than once if the method is found after superclass chaining) ® it performs complex stack manipulation to take the parameters from the call to p_send to pass to the method (note that it removes the method number) = it has to handle the fact that the target method may be in a moveable code segment (as well as the return to the caller which is also, in general, in a moveable code segment) The following table of timings (in seconds) for a million calls to various library functions was produced from a test program running on an MC400 running at 8MHz. Empty loop.....cccscccccenccne 8 p_dunmmy........ Sais See cse tees 13 P_OSCUMTY..-...ccccececccrcces 24 P_isprint('A')......cecee weeee 44 P_SLEMC DL Lecce nec eeeccene 74 PYSONGS Fe ote e ciese cite roie eseisiete erste, 134 P_SEND. 2... cence e cece eene 145 p_send2 (subclass)............ 152 P_ioyield........ cece cece n none 165 p_iosignalt+p_jowait........... 230 The following code segment indicates the basic mechanism that was used to obtain the above numbers. LOCAL_D ULONG c1=1000000L; GLDEF_C VOID Time(VOID (*call)(VOID), TEXT *name) { ULONG t1,t2; p_print("%-.30s",name); p_sleep(5L); /* to allow redraws to complete */ ti=p_date(); (*call)¢); t2=p_date(); P_printf("%4ld",t2-t1); > GLDEF_C VOID z_dummy(VOID) { ULONG c; for (c=c1;c--;p_dummy()); > where main contained calls of the form: Time(z_dummy,"p_dummy") ; 218 15 OBJECT ORIENTED PROGRAMMING SEES The function p_dumny consists only of a return. The 13 second result includes the 8 seconds for the ULONG loop overhead to call the function a million times. The function p_osdumny calls the minimal ROM interrupt service, which just returns. The additional 11 seconds over the time for p_dunmy represents the overhead of making a far call to the ROM and handling the return to a moveable code segment. The test for p_iosignal also includes a call to p_iowait. The functions p_isprint, p_slen, p_ioyield, p_iosignal and p_jowait are all described in this manual. Note that the time taken by a p_ioyield or a p_iowait will depend on what wait handlers are installed. The tests for message sending all send to a method that just returns. The relative difference between the time for p_send2 (which has no additional parameters) and the time for p_send5 (which has the maximum of three additional parameters) shows that there is little overhead to passing more parameters. The test labelled p_send2 (subclass) shows the time taken for one iteration of superclass chaining. Here, the message was sent (with no additional parameters) to a instance of a class that relies on its superclass to provide the method. These results show that message sending has about 20 times the overhead of a local function call. The results also show that calling any ROM-based service via a software interrupt has an overhead of 3 to 5 times that of a call to a local function. At several thousand sends per second, the additional overhead of message sending is only going to degrade performance when it occurs in the innermost loops of an application. Where performance is key?, speed critical sections of code should clearly avoid using p_send or any other far call to ROM code. The main benefit of object oriented programming is in promoting well-designed programs. Since a well- designed program is presumably understood by its author, there should be no difficulty in identifying the critical code sections that need to be written efficiently (and where the avoidance of far calls is just one factor contributing to that efficiency). It is most certainly possible to produce poorly-designed programs using object oriented programming and the above timings show that a program which bumbles along will go a lot slower using p_send rather than direct function calls. An assertion along the lines of "look after the pennies and the pounds look after themselves" is a good rule provided it is not taken too far - it should not ruin the design and make future maintenance a nightmare. It should certainly not be the only basis on which performance is delivered, nor should it be considered as an alternative to understanding the program. Using, where possible, direct function calls in place of message sending functions (such as p_send) does no harm to the design and can only improve performance. The correct approach is to consider performance where it is specified to be important in the original design. The above timings are provided to guide that consideration. SSS SS ee eee) DLLs The functions in this chapter, described in the context of object oriented programming, may also be used to implement re-entrant dynamic link libraries (sometimes called DLLs on PCs) with the following benefits: = application code can break the 64K code segment limit # there is only one copy of the DLL in memory at a time however many processes are accessing it DLLs may be loaded and linked using p_loadlib and p_linklib. The DLL functions would be organised into one or more groups (root classes in OOP) of up to 255 functions (methods in OOP). The functions may be called (without having to create an object instance) using p_exactsend (where the object instance parameter has no special significance). 2In a professional development environment, this should be stated in a Software Requirements Specification. 219 PLIB REFERENCE NN a ee a a el Category functions This section describes the following functions: p_loadlib which loads a DYL from a file (which normally has the extension .DYZ). p_openlib which are used in conjunction to load DYLs that have been combined into a p_loadfilelib multiple DYL file (mormally an executable) p_untoadlib which frees the memory occupied by a DYL (provided another process is not using it) p_linklib which is used by an image category to link itself and may also used to link DYLs which reference another DYL that has just been loaded p_findlib which is used to obtain the handle of a loaded category from its name p_getlibh which converts a category number to a category handle p_cpycat, which may be used to copy data from a category into the data segment p_ccpy An application that uses a DYL to implement a transient subsystem should call p_unload! ib as soon as it has finished using it so that the memory is returned to the system. Image categories are loaded by calling p_execc - described in the chapter Processes and Inter-Process Messaging. An application process must connect to the file server before calling p_toadlib, p_openlib or p_loadfilelib. However, this is normally taken care of by the C startup module (the code that precedes main) supplied as standard for use with the PLIB library. INT p_loadlib¢(TEXT *pName, HANDLE *pCatHandle, INT Link); Load (and optionally link) the DYL with the zero terminated file specification pName and, if successful, write the category handle to *pCatHandle and return zero. If Link is TRUE, the DYL is automatically linked after loading. In this case, all externally referenced categories must already have been loaded (otherwise p_panic is called). You would normally only set link to FALSE if the DYL externally references another DYL which you have yet to load (where the DYL would be linked subsequently by calling p_linklib). The file specification pName is parsed with a related name of ".pyL". The name component from pName is used to name the category segment - the segment name has the extension ".pYL" regardless of pName. If the parse fails, p_loadt ib returns with one of the negative error numbers returned by p_fparse. Other possible error returns are: & FILE_NXISTS pName does not exist E_GEN_IMAGE pName is not a valid DYL E_GEN_OPEN pName has already been loaded by the caller E_FILE_EXIST a different DYL (ie with a different checksum) with the same name already exists If the same DYL has already been loaded by another process, it is shared and not re-loaded. A loaded DYL remains in memory until the process terminates or until the process unloads the DYL by calling p_unloadlib. Note that DYLs that are in the ROM are deemed to be loaded by default. There is therefore never any need to call p_loadlib for such a DYL. 220 15 OBJECT ORIENTED PROGRAMMING Open a channel to the file containing multiple DYLs as specified by the zero terminated pName and, if successful, write the file channel to *pfcb and return zero. The file specification pName is parsed with a related name of ".1Mc". If the parse fails, p_opent ib returns with one of the negative error numbers returned by p_fparse. Most commonly pName is an image file to which the multiple DYLs have been added using the emake program. If the file is not a valid multiple DYL file, the error E_GEN_IMAGE is returned. The returned *pfcb is passed to subsequent calls to p_loadfilelib, described next, to load the DYLs. The file channel *pfcb is obtained by an internal call to p_open and when access to the file is no longer required, it should be closed by calling p close. The fact that *pfcb is a regular binary file handle (opened with p_FRANDOM but not P_FUPDATE) may be exploited to read any other data from the file. Any error that may be returned by p_open may also be returned by p_openlib. Pp _lcadfilelib bea INT p_loadfilelib(VOID *fcb, UINT n, HANDLE *pCatHandle, INT Link); Load (and optionally link) the nth DYL from the multiple DYL file channel fcb and, if successful, write the category handle to *pcatHandle and return zero. The number n selects the DYL to be loaded from the file in the order that DYLs were originally added by emake. To load the first DYL from the file, n should be zero. As well as storing the DYLs themselves, the multiple DYL file stores the original DYL file names. These file names are used to name the segment in the same way as if the DYL had been loaded directly using p_loadl ib. If Link is TRUE, the DYL is automatically linked after loading. In this case, all externally referenced categories must already have been loaded (otherwise p_panic is called). You would normally only set link to FALSE if the DYL externally references another DYL which you have yet to load (where the DYL would subsequently be linked by calling p_l inkl ib). The file channel fcb is obtained by previously opening a multiple DYL file using p_opentib, described above. If the DYL has already been loaded by another process, it is shared and not be re-loaded. If, however, the library has already been loaded by the caller, p_loadfilelib fails and returns the negative E_GEN_OPEN. A loaded DYL remains in memory until the process terminates or until the process unloads the DYL by calling p_unloadl ib. INT p_unloadl ibCHANDLE catHandle); Unload DYL catHandte from memory and return zero if successful. If the caller has not loaded the DYL, the negative error number E_GEN_NOTOPEN is returned. A loaded DYL may be shared by multiple processes. Each time a process loads a particular DYL an access count is incremented. Unloading the library decrements the access count and if this takes the access count to zero, the DYL memory segment is deleted. Ce rt—~w—OC«CisaiNCNONCNiN‘CNCCC_NCOC VOID p_linklib(HANDLE catHandle); Link the loaded image category or DYL with handle catHandle or, if catHandle is zero, link the image category containing the call to p_linklib. All externally referenced categories must already have been loaded (otherwise p_panic is called). 221 PLIB REFERENCE Calling this function is harmless if the category has already been linked. Since categories are shared, a category may have already been linked because another process has previously loaded and linked it. Applications programs which reference external DYLs by category number must call p_linklib¢0) to link themselves early in their initialisation (but after loading any referenced DYLs). Applications that do not access any external DYLs, or only access DYLs that are in the ROM, may contain the call to p_linktib(O) as the first line of their main¢) function. In many cases, DYLs can be linked by the call to p_loadlib or p_loadfilel ib that loads them - you only need to use p_linklib on a DYL in the relatively rare case where the link has to be deferred because the DYL references another DYL which has yet to be loaded. INT p_findlLib(TEXT *pName, HANDLE *pHandle); Write the category handle of the loaded category segment with the zero terminated name pName to *pHandle and return zero or, if no such category exists, return the negative error E_FILE_NXISTS. The category may be in the ROM or in a RAM memory segment. The name pointed to by pName is a category segment name, not a file specification. Thus form.dyl is a valid name, but rom:.form.dyl is not. The name should include an extension - image categories have the extension .$sc and DYLs have the extension .dyl. Although p_findl ib will find a RAM-based DYL, it does not do anything to keep the DYL loaded. Normally, you would only use p_findl ib to get the handle of a ROM-based DYL. To get the handle of a RAM-based DYL, you should use p_loadl ib or p_loadfilelib which will not load the DYL if it is already loaded and will increment the usage count to keep it loaded until the program calls p_unloadl ib (or until the program exits). HANDLE p_getlibhC(INT catNum); Return the handle of the external category specified by the category number catNum. If catNum is Zero, p_getlibh returns the handle of the local category. Any number greater than zero indexes the external category table to get the external category handle. The function calls p_panic if catNum is outside the range of the external category table or if the local category has not been linked. ory VOID p_cpycatC(UINT catNum, VOID “pTarget, VOID *pSource, UINT count); Copy count bytes of data from offset pSource in the segment of the category specified by the category number catNum, to pTarget in the caller's data segment. Calls p_panic if catNum is outside the range of the external category table or if the local category has not been linked. VOID p_ccpy(VOID *pTarget, VOID *“pSource, UINT count); Copy count bytes of data from offset psource in the code segment containing the call to p_ccpy, to ptarget in the caller's data segment. 222 15 OBJECT ORIENTED PROGRAMMING -_ Ss eee SE eee ee eS a Object functions This section describes the following functions: p_new, f_new, which create an instance of an object, given its class p_newlibh, f_newlibh p_send, which send a message to an object instance, given its address p_supersend, p_entersend, p_exactsend f_newsend, which create an object and send it an initialisation message f_newlibhsend p_reclass, which change the class of an instance p_reclassbyhandle VOID *p_newCINT catNum, INT classNum); VOID *f_newCINT catNum, INT classNum); Create an instance of class classNum from the category specified by the category number catNum, returning the address of the object, or NULL if there is insufficient memory to allocate the instance from the heap. This function converts catNum to a category handle by calling p_getlibh and then calls p_newl ibh (described next). The class number classNum specifies the class by indexing the class table in the category catNum. Except for the instance header (which points to the specified class descriptor), the rest of the property is initialised to zero. Calls p_panic if catnum is outside the range of the external category table, if the local category has not been linked or if classNum is outside the range of the class table. The function f_new is identical except that it calls p_leave(E_GEN_NOMEMORY) rather than return NULL. VOID *p_newlibhCHANDLE catHandle, INT classNum); VOID *f_newlibh(HANDLE catHandle, INT classNum); Create an instance of class classNum from the category specified by the category handle catHandle, retuming the address of the object, or NuLL if there is insufficient memory to allocate the instance from the heap. The class number classNum specifies the class by indexing the class table in the category catHandle. Except for the instance header (which points to the specified class descriptor), the rest of the property is initialised to zero. Calls p_panic if catHandle is not the handle of a valid external category or if classNum is outside the range of the class table. The function f_newtibh is identical except that it calls p_leave(E_GEN_NOMEMORY) rather than return NULL. 223 PLIB REFERENCE INT p_send(VOID *pObject, INT methodNum, ...); INT f_send(VOID *pObject, INT methodNum, ...); INT p_send2(VOID *pObject, INT methodNum); INT f_send2(VOID *pObject, INT methodNum); INT p_send3(VOID *pObject, INT methodNum, VOID *p1); INT f_send3(VOID *pObject, INT methodNum, VOID *p1); INT p_send4(VOID *pObject, INT methodNum, VOID *p1, VOID *p2); INT f_send4(VOID *pObject, INT methodNum, VOID *p1, VOID *p2); INT p_send5(VOID *pObject, INT methodNum, VOID *p1, VOID *p2, VOID *p3); INT f_send5(VOID “pObject, INT methodNum, VOID *p1, VOID *p2, VOID *p3); Call the method function corresponding to methodNum of the object instance pobject, passing the function from zero to three additional parameters and return the value (which should be the size of an INT) returned by the selected method function. You can either use p_send, which presents the stack-based cect calling convention, or one of the p_send? variants, which use a more efficient register calling convention. Each ¢_ variant is identical to the corresponding p_ variant except that, if the method returns a negative value err, it calls p_leavecerr) rather than returning err. The method function is called with the same parameters as passed in the p_send except that the methodNum parameter is removed. The method function must use either the stack-based cpEct or the more efficient register-based METHOD_CALL calling convention. The following example illustrates the form of a method function declaration, using the METHOD_CALL calling convention, together with the corresponding p_send? method function call. #pragma save #pragma METHOD CALL GLDEF_C VOID myobject_mymethod_one(VOID *self,TEXT *buf,UINT Len) € } GLDEF_C VOID myobject_mymethod_two(VOID *self,TEXT *buf,UINT len) { p_send4(self,O_MYMETHOD_ONE, buf, len); } #pragma restore The symbol 0_MYMETHOD_ONE, representing the method number of the method function myobject_mymethod_one, is generated by the category-building tools. The search for a method function starts from the class pointed to by the object header (and which was used to create the object). If this class does not contain a method corresponding to methodNum, the search continues with the superclass and so on until a method is found. If the search fails (that is, it fails to find a method in the root class), p_send calls p panic. age INT p_supersend(VOID *pObject, INT methodNum, ...); INT p_supersend2(VOID *pObject, INT methodNum); INT p_supersend3(VOID *pObject, INT methodNum, VOID *p1); INT p_supersend4(VOID *pObject, INT methodNum, VOID *p1, VOID *p2); INT p_supersend5(VOID *pObject, INT methodNum, VOID *p1, VOID *p2, VOID *p3); Behaves exactly as for p_send except that the search for a method function corresponding to methodNum Starts at the superclass of the class of the method containing the call to p_supersend (ignoring the object header of pobject). The class of the method is determined by retrieving (from the stack) the last class descriptor that provided the path to the method containing the call to p_supersend. This can go wrong if the calling method was reached via a direct function call. If there is a possibility of the method being called directly, consider 224 15 OBJECT ORIENTED PROGRAMMING _—— SS SSSSSFSSSSSSSSSSSSMMhFhFeFeseseeee using p_exactsend as an alternative to p_supersend or, if possible, a direct function call (which is, in any case, better for performance). Within reason, pObject must be the same as that passed to the method calling the p_supersend. In nearly all cases, methodNum is also the same as that which selected the calling method (and in many cases the remainder of the parameters, if any, are the same too). This function is typically used when a method function wishes to call the method function corresponding to the same methodNum of a superclass (when a method function includes the superclass method function's processing in its own processing). Very rarely, it is used to call a different method number of a superclass. @ messaq! INT p_entersend(VOID *pObject, INT methodNum, ...); INT p_entersend2(VOID *pObject, INT methodNum); INT p_entersend3(VOID *pObject, INT methodNum, VOID *p1); INT p_entersend4(VOID *pObject, INT methodNum, VOID *p1, VOID *p2); INT p_entersend5(VOID *pObject, INT methodNum, VOID *p1, VOID *p2, VOID *p3): Behaves exactly as for p_send except that the method function called is entered as if it had been called with p_enter. Note that, as for p_send, the target method function must use either cDECL or METHOD_CALL calling convention (and not ENTER_CALL as for functions entered via p_enter). If p_leave(err) is called before the entered method function returns, the stack is unwound and the call to p_entersend returns err. The call to p_leave may occur in the entered function or in a sub-function and so on. The enter and leave mechanism (which is commonly used to implement structured error recovery) is described in the chapter Error Handling. INT p_exactsend(HANDLE catHandle, INT classNum, VOID *pObject, INT methodNum, ...); Behaves as for p_send except that the search for a method function corresponding to methodNum starts at the class specified by catHandle and classNum (ignoring the object header of pobject). Like p_supersend, this function is typically used to access a superclass method corresponding to the same methodNum. It is typically used for one of the following two reasons: * — to call a first generation method of a superclass (obscured by an intervening second generation method) from a third generation method = to call a superclass method when the calling method may be called by a direct function call VOID *f_newsend(INT catNum, INT classNum, INT methodNum, ...); Create and initialise an object by: = creating an instance of class classNum from the category specified by the category number catNum ® calling the method function corresponding to methodNum of the created object, passing the function from zero to three additional parameters The function returns the address of the created object. Behaves as for f_new followed by a p_send of methodNum to the created object where methodNum is typically an initialisation method that calls p_leave if an error occurs. If there was insufficient memory to create the object, it calls p_leave(E_GEN_NOMEMORY) - just like f_new. What makes f_newsend more valuable than an apparently equivalent call to ¢_new followed by a p_send is its error handling following a successful f_new: if there is a call to p_leave with a negative parameter before methodNum returns, the partially initialised object is sent a destroy message and the call to p_leave is propagated. The net effect is that the function is either successful (in which case it returns the address of the created and initialised object) or it leaves having cleaned up the partially created object. 225 PLIB REFERENCE There is no requirement for the method function methodNum to return zero (as there normally is for entered functions) and the method function may be declared as a voip or otherwise (any value returned by the method function is lost). Calls p_panic if catNum is outside the range of the external category table, if the local category has not been linked or if classNum is outside the range of the class table. VOID *f_newlibhsend(HANDLE catHandle, INT classNum, INT methodNum, ...); Behaves exactly as for f_newsend, described above, except that the category containing the class of the object to be created is identified by its category handle rather than its category number. In fact, f_newl ibhsend is more primitive than f_newsend (which calls p_getlibh to convert the category number to a category handle before calling f_newlibhsend). VOID p_reclass(INT catNum, INT classNum, VOID *pObject); Change the class of which pobject is an instance to that specified by the category number catNum and classNum. Within reason, the new class is a subclass or a superclass of the original class, with the same property. The function calls p_panic if you attempt to reclass to a class with a different property length. Calls p_panic if catNum is outside the range of the external category table; if the local category has not been linked or if classNum is outside the range of the class table. VOID p_reclassbyhandle(HANDLE catHandle, INT classNum, VOID *pObject); Change the class of which pobject is an instance to that specified by the category handle cathandle and classNum. Within reason, the new class is a subclass or a superclass of the original class, with the same property. The function calls p_panic if you attempt to reclass to a class with a different property length. Calls p_panic if catHandle is not the handle of a valid external category or if classNum is outside the range of the class table. 226 INDEX #pragma 4 call 6 restore 6 -wve 188 80286 63 80386 63 80486 63 8086 1, 63 8087 emulator 39, 46 avoiding 40, 46 A-Law decoding 191 encoding 189 absolute timer 104 active objects 10 ADDFILE 159 alloc heaven 67 arccosine 45 architecture SIBO 1 arcsine 45 arctangent 45 array 23 binary search 23 sort 24 ASIC 1 asynchronous event 86 file server operations 122 image,load 162 message 171 sound,play back 193 sound,record 192 timer 105 asynchronous request 80 building 82 cancelling 82 cancelling,simulation 122 file,cancel 150 0 90 status word 81 wait 82, 86 attached driver 91 1/0 devices 84 auto switch off 180 battery backup 182 type 182 voltage level 182 binary file access 140 close 143 flush 145 open 142 position 144 read 144 seek 144 write 144 binary search 23 buffer 11 align 13 character,repeat 13 checksum 14 compare 18 compare,case independent 19 copy 11 functions 11 locate byte 19 locate character,case independent 20 locate sub-buffer 21 locate sub-buffer,case independent 21 multiple arguments,convert 30 pattern match 21 pattern match,case independent 22 replicate 12 searching 19 swap 13 BYTE 5 Cc floating point 39, 89 prototype 4 startup module 7, 117 C compiler Microsoft 4 TopSpeed 3 Turbo C 4 calling convention 4, 6 register_based 7 stack_based 6 case conversion table 15 category 212 data,copy from 222 data,copy from local 222 DYL 212, 214 DYL,load 220 DYL,unload 221 dynamic library 212 dynamic linkage 216 external 213 handle 213 handle to number,convert 222 handle,find 222 handle,referencing 214 image 212 linkage 213 load module 215 loaded & linked,structure 216 loaded DYL, link 221 loaded,link 221 multiple DYL,load 221 multiple DYL,open 221 number 213 number,external 213 PLIB REFERENCE ae number,local 213 unlinked,structure 216 CDECL 6, 59, 218 cell 66 allocate 67 change contents 68 change size 68 free 68 get length 69 channel cancel request 97 close 96 high speed serial 118 1/O functions 92 opening to device 92 operations 90 read from 96 services,file server 121 timer,close 106 timer,open 105 to device,opening 90 write to 97 character alphabetic,test 15 alphanumeric,test 16 case conversion table 15 classification 14 classification table 14 control,test 16 conversion 14 fold 17 fold table 15 hexadecimal,test 16 lower case,convert 17 lower case,test 15 non-whitespace,skip 17 numeric,test 16 printable graphic,test 16 printable,test 16 punctuation,test 16 upper case,convert 17 upper case,test 15 whitespace,skip 16 whitespace,test 16 Clarion Software 3 class 10, 211 descriptor 211 instance,create 223 property 212 root 212 specific,message send 225 superclass 212 superclass chaining 217 time 109 classification table 14 CLIB library 1,8 client 169 file server 91 functions 174 message,asynchronous send & wait message,send 174 message,send & wait 175 message,send and wait 175 client-server 168 clock 1,118 real-time 1 code segments 2, 64 CON: 99 config.h 179, 180 connect file server 117 console 8, 9 arguments,convert and write 101 change mode 100 change size 100 character,get 101 character,write 101 device driver 99 1/0 99 redirecting writes 100 string,get 101 string,get with prompt 101 string,write 101 contro! blocks process 152 coordinates 34 cosine 44 country code 178 data segments 2, 64 database 195 close 200 compress 201 continuation sub-fields 197 copy 201 descriptive record,read 203 descriptive record,write 203 extended header,read 202 extended header,write 203 file header 196 first record,read 205 flush 200 index 195 last record,read 205 next record,read 204 open 198 open quickly 200 OPL 198 overwritten buffer,notify 200 previous record,read 204 record 196 record number,sense 209 record type,application specific 197 record type,deleted 196 record type,descriptive 197 record type,field information 196 record type,standard 196 record,append 205 record,copy down 201 record,count 209 record,end of file 198 record,erase 206 record,find 207, 208 record,number 198 record,update 206 size,find 202 specific record,read 204 specific record,read & sense 204 version number,fetch 203 databse string fields 197 -DatApp1 157 DatApp2 157 DatApp3 157 DatApp4 157 DatApp5 157 228 INDEX DatApp6 157 DatApp7 157 DatATFlag 157 DatClassHandle 156 DatClassPtr 156 DatCommandPtr 157 DatCountrySeg 156 DatDialogPtr 157 date am/pm suffixes,get 110 day in month suffixes,get 110 day name abbreviation,get 110 day name,get 109 format string 112 language dependent 110 month name abbreviation,get 110 month name,get 110 preferences 111 string generation 112 text form 109 DatEClassHandle 156 DatEClassPtr 156 DatEnterFramePtr 156 DatGate 157 DatHandNext 156 DatHandPrev 156 DatHeapLocked 157 DatLocked 157 DatOsFramePtr 157 DatProcessNamePtr 157 DatStatusNamePtr 157 DatTest 157 DatUsedPathNamePtr 158 DatWordDead 156 DbfAbsRead 204 DbfAbsReadSense 204 DbfAppend 205 DbfBackRead 204 DbfClose 200 DbfCompress 201 DbfCopyDown 201 DbfCopyFile 201 DbfCount 209 DbfDescRecordRead 203 DbfDescRecordWrite 203 DbfEraseRead 206 DbfExtHeaderRead 202 DbfExtHeaderWrite 203 DbfFileSize 202 DbfFindRead 208 DbfFindReadField 207 DbfFirstRead 205 DbfFlush 200 DbfHeader 198, 199 DbfLastRead 205 DbfNextRead 204 DbfOpen 198 DbfOpenArgs 200 DbfQuickOpen 200 DbfRecord 196 DbfSense 209 DbfTrash 200 DbfUpdate 206 DbfVersion 203 delta queue 27, 79, 154 add entry 27 remove entry 27 device driver 89 attached 91 CON: 99 delete 98 external 89 FIL: 90 find 99 floating point 89 functions 98 logical 89 logical,load 98 PAR: 90 physical 89 physical,load 98 query units supported 98 SND: 187 TIM: 90 TTY: 90 devices 126 console 8, 9 default 126 formatting 131 information 129 list 128 local SSD,direct read 133 local,media information,read 132 opening channel to 90, 92 operations 90, 127 parallel port 9 serial port 9 sound driver 9 digitiser 152 digitising pad 1 directory 126 create 138 default 126 delete 137 existence,test 136 list 133 operations 133 rename 136 DLL 219 DOUBLE 5 from string,convert 42 random 46 to string,convert 41 doubly linked queue 25 add entry 26 remove entry 27 drives flash EPROM 118, 119 masked ROM 118 once programmable ROM 118 SSD 118 static RAM 118, 119 DYL 212, 214 ROM 213 E BATTERY_ALKALINE 182 E BATTERY _NICAD_1000 182 E BATTERY_NICAD 600 182 E BATTERY UNKNOWN 182 E CONFIG 43, 108, 111, 180 E_CPB 162, 163 E_CURRENCY_AFTER 43 E_CURRENCY BEFORE 43 E FILE PENDING 81 E FILE xxx 56 E GEN_xxx 56 E IMPERIAL 44 229 PLIB REFERENCE E MAX_ENV_SIZE 75 E MAX_GROWBY 51 E_MAX_PRIORITY 154 E MAX_PROCESSES 151 E MESSAGE 168 E METRIC 44 E_ MIN_PRIORITY 154 E NORMAL_EXIT 50 E NOSPACE BETWEEN 43 E PANIC_EXIT 50 E PROC 153 E_ SEGMENT DEVICE 51 E SEGMENT_HIGH 51 E SEGMENT LOCKED 51 E SEGMENT LOW 51 — SPACE BETWEEN 43 E SUPPLY 182 E SUPPLY_INFO 183 E_SUPPLY_WARNINGS 184 E TASK_PANIC_EXIT 50 edump.exe 159 EM$ 39 emake.exe 158 end of file 146 enter a function 58, 59 ENTER_CALL 59, 218 environment variables 63, 75 delete 76, 77 EM$ 39 find 77 get value 75, 76 set value 76 EPOC 2, 63 device drivers 89 files 117 1/0 system 89 multi-tasking 151 operating system 2 PC 2, 182 program environment 2 ROM 2 single-user 151 system services reference 9 system tables 14 timer and delta queue 27 epoc.h 50, 69, 104, 132, 153, 158, 159, 163, 168, 178, 183, 186, 188 EPROM flash 118, 119 error codes 50 handling 49 number to string,convert 56 returning 56 event 9, 10, 79, 81, 85, 94, 105, 151, 155, 171, 182 asynchronous 86 executable 151 exit to DOS 194 exponential 45 external device driver 89 external memory segments 3, 71 f_alloc 67 f fparse 123 f leave 60 fnew 223 f_newlibh 223 f_newlibhsend 226 230 f_newsend 225 f_open 92 f read 96 f_realloc 68 f_seek 144, 149 f send 224 f_write 97 FIL: Tile 90 117 MG 158 asynchronous request,cancel 146, 150 attributes,set 138 binary 140 binary,close 143 binary,open 142 binary,position 144 binary,read 144 binary,seek 144 binary,write 144 buffers,flush 145 create date,set 139 database 195 delete 137 end of,set 146 image 158 information,get 135 label medium,set 138 operations 133 record text 147 record text,open 148 record text,position 149 record text,read 148 record text,seek 149 record text,write 149 rename 136 shared access 140 sound 188 stream text access 146 stream text,open 147 text,close 148 text,flush 150 text,set end 150 file server 7,91, 117, 168, 195 file asynchronous operations 122 channel services 121 client 91 connect 117 default device 126 default directory 126 default node 126 default path 121 file specification 120 file specification,change directory 125 file specification,manipulation 123 file specification,parse 123 non-channel services 121 process default path,get 127 process default path,set 126 process-id default path,get 127 shared access 140 system default path,set 126 unattended applications 118 systems 117 LOC:: 63, 71, 117, 128, 144, 146 MSDOS 120, 144, 146 REM:: 117 INDEX ROM:: 117 UNIX 146 Flash filing system 119 Flash-friendly 195 floating point add 46 assignment 46 avoiding emulator 46 Cc 39 compare 47 divide 47 double to integer,convert 48 double to long,convert 48 emulator data space 65 integer part 48 integer to double,convert 48 long to double,convert 48 modulus 48 multiply 47 negate 47 subtract 47 fold table 15 format 131 formatting device 131 dual density 131 functions buffer 11 category 220 channel I/O 92 character classification 14 character conversion 14 client 174 database 198 DBF 198 device driver 98 enter 59 integer conversion 29 leave,on error 60 leave,standard 60 long integer 44 object 223 rectangle 34 scientific 44 semaphore,primitive 84 server 172 string 11 wait handlers 87 GLDEF_C 5 GLDEF D 5 GLREF C 5 GLREF_D 5 granularity 69 growing the heap 66 HANDLE 5 hardware interrupts 84, 154, 177 hardware protection 3 header files config.h 179, 180 epoc.h 50, 69, 104, 132, 153, 158, 159, 163, 168, 178, 183, 186, 188 p_config.h 43, 111 p_date.h 106 p_dbf.h 196, 199, 200, 205 p_file.h 56, 81, 90, 105, 106, 107, 121, 123, 128, 129, 134 p_gen.h 32, 56 p_graf.h 35, 100 p_math.h 41 p_que.h 25 p_std.h 5 plib.h 4 heap 66 alloc heaven 67 allocate failure 66 allocator 66 cell 66 cell,allocate 67 cell,change contents 68 cell,change size 68 cell,free 68 cell,get length 69 fragmentation 67 granularity 69 growing 66 integrity,check 70 potential free space 70 set granularity 69 shrinking 66 structure 66 walk 69 high speed serial channel 118 hook notifier interface 58 1/0 asynchronous request 90 channel!,cancel request 97 channel,close 96 channel,read from 96 channel,write to 97 console 99 device drivers 89 device,opening channel to 90 external device driver 89 LDD 89 logical device driver 89 operations on open channel 90 PDD 89 physical device driver 89 reference 9 semaphore 81, 85, 90 semaphore,signal 85 semaphore,signal process 85 semaphore,wait 86 start operation 93, 94 start operation & wait 95 system 89 identify machine type 186 image files 158 ImgHeader 158 inactive process 164 index table 65 INT 5 integer ULONG random 44 integer conversion functions 29 INT to decimal char 29 LONG from signed decimal 32 LONG to decimal char 29 UINT to char 30 ULONG from string 33 ULONG to char 30 UWORD from string 32 WORD from signed decimal 32 integrated circuit 1 inter process messaging 58, 168 interrupt 231 PLIB REFERENCE disabling 3 hardware 84, 154 software 4, 218 vectors 63 introduction 1 ISDN combo sound system 1 keyboard 185 language code 178 LCD display 1 LDD 89, 91 leave onerror 60 standard 60 leave a function 58 libraries 1 CLIB 1,8 object dynamic 10 PLIB 1 TopSpeed C 8 window server 1 WLIB 1,9 LOC:: 63, 71, 117, 128, 144 changing 128 LOCAL C 5 LOCAL D 5 logarithm 45 logarithm,natural 45 logical device driver 89, 91 LONG 5 long integer functions 44 loudness 193 machine type 186 macros 41 magic statics 65, 156 main() 7 mains adaptor 182 manuals 8 MC400 10 media type 132 memory allocate failure 66 allocation 63 available 71 heap 66 moving 3 RAM disk usage 71 segment 64, 71 segment,adjust size 74 segment,close 74 segment,copy from 73 segment,copy to 73 segment,create 72 segment,decrease usage count 75 segment,delete 73 segment,device 64 segment,dynamic 64 segment,find by name 74 segment,get size 74 segment,increase usage count 75 segment,lock 75 segment,open 73 segment,unlock 75 system usage 63, 71 message 168 free 174 processing order 172 queue 172 reception 173 reception,asynchronous 173 reception,cancel 174 reception,wait 173 send 174 send & wait 175 slots 168, 174 Microsoft C 4 monomorphic 217 mouse 152 multi-tasking 2, 7, 151 polling 84 waiting 84 mutual exclusion semaphore 80 name of process 155 natural logarithm 45 nodes 117, 126 default 126 information 128 list 127 operations 127 notify error & response 58 hook interface 58 message & response 57 service 56 state get 58 state,set 58 unhook interface 58 null process 164, 180 number representation preferences 43 object by category handle,create 223 by category handle,create & init 226 by category handle,reclass 226 by category number,create 223 by category number,create & init 225 by category number,reclass 226 class 10 classes 211 destruction 212 dynamic libraries 10 instance 212 message specific class,send 225 message to superclass,send 224 message,entersend 225 messages 216 message,send 224 messages,performance 218 method function 217 method function,convention 217 method number 216 monomorphic 217 polymorphic 217 property 212 reference 10 root class 212 superclass 212 object-oriented programming 10 panic 53 OLIB reference 10 OLIB.DYL 10 opening channel to device 90 operating system data space 63 overview 2 operations on any process 165 current process 163 232 INDEX nr a devices 127 directories 133 files 133 nodes 127 open channel 90 OPL 15, 56, 157, 195 database 198 overview system memory 63 p_absrec 37 p_acos 45 p_adjust 68 p_alen 69 p_allchk 70 p_alloc 67 p_ -allowoff 181 p _allspce 70 p_allwalk 69 p_asin 45 p_atan 45 p_atob 30 p_atos 31 p_backlight 186 p_bemp 18 p_bcmpi 19 p_bepy 11 p_bfil 13 p_bloc 19 p_ bloci 20 p_bmatch 21 p_bmatchi 22 p_brep 12 p_bsrch 23 p_bsub 21 p_! ~bsubi 21 p_bswap 13 p_cepy 222 P_CD PARENT 125 P_CD ROOT 125 P_CD SUBDIR 125 p chdir 125 P-CLASS 211 p_close 96, 143, 148 p_config.h 43, 111 pcos 44 p_cpycat 222 p_cre 14 p_date 103, 107, 115 p_date.h 106 p_dayinm 108 P_DAYSEC 106, 107, 108, 115 p_dbf.h 196, 199, 200, 205 P DECLAREQ 25 p_delenv 76 p_delenviron 77 p_delete 137 P_DELTA 27 p_deque 27 p_dequed 27 p_devdel 98 p_devfnd 99 p_devqu 98 P_ ~dinfo 129 p_ds2str 115 p_ - dstodt 107 p_dstost 107 p_dt2str 115 p_dtob 41 P_DTOB EXPONENT 42 P_DTOB_FIXED 42 P_DTOB GEN LIM 42 P_DTOB GENERAL 42 p_ ~dttods 108 p_emprec 36 p_enque 26 p_enqued 27 p_enter 59 p_entersend 225 P_ENVMAX 75 p_errs 56 p_exactsend 225 p_execc 160 p_execcasync 162 p_exit 53 p_exp 45 P FABS 144 P FABSOLUTE 105 p_fadd 46 P_FADIR 134 P_FAHIDDEN 134, 138 P_FAMOD 134, 138 P_FAPPEND 143, 198 P_FASYSTEM 134, 138 P_FATEXT 134 P_FAVOLUME 134 P_FAWRITE 134, 138 P_ FBLKSIZE 144 P_FCANCEL 90, 96, 97, 105, 146, 147, 150 P_FCLOSE 90 p_femp 47 P_ FCREATE 142, 198 P_FCUR 145 p_fdate 139 Po FDEVICE 121, 128 P_FDIR 121, 133 p_ fdiv 47 P_FEND 145 P_FFLUSH 91, 96, 145, 147, 150 P_FFORMAT 121, 131 p_file.h 56, 81, 90, 105, 106, 107, 121, 123, 128, 129, 134 p_findenviron 77 p_ - findlib 222 p_ finfo 135 p_fld 46 P_FMAXRSIZE 148 P_FMAXSSIZE 140, 144 P_FMEDIA_COMPRESSIBLE 130 P_FMEDIA_DUAL DENSITY 130 P_FMEDIA_DYNAMIC 130 P_FMEDIA_FLASH 130 P_FMEDIA_ FLOPPY 129 P_FMEDIA FORMATTABLE 130 P_FMEDIA_ HARDDISK 129 P_FMEDIA_INTERNAL 130 P_ FMEDIA RAM 130 P FMEDIA ROM 130 P FMEDIA_UNKNOWN 129 P_FMEDIA_WRITEPROTECTED 130 p_fmul 47 P_FNAMESIZE 121 p_fndenv 77 p - fneg 47 P_FNODE 121, 127 P_FOPEN 142, 198 233 PLIB REFERENCE LL p_fparse 123 p_frand 46 P_FRANDOM 143 P_FREAD 90, 93, 147 p_free 68 P_FRELATIVE 105 P_FREPLACE 142, 198 P_FREWIND 149 P_FRSENSE 149 P_FRSET 149 P_FSENSE 91, 96 P_FSET 91, 96 P_FSETEOF 146, 147, 150 P_FSHARE 140, 143, 198 P_FSTREAM 121, 142 P FSTREAM TEXT 121, 147 p_fsub 47 P_FSYSTYPE FLAT 128 P_FSYSTYPE_HIER 128 P_FTEXT 121, 148 P_FUNIQUE 143, 198 P_FUPDATE 143, 198 P_FWRITE 90, 93, 147 p_gen.h 32, 56 Pp_getampmtext 110 p_getauto 181 p_getautomains 181 p_getbacklight 187 p_getbat 185 p_getch 101 p_getctd 43, 111, 180 p_getenv 75 p_getenviron 76 p_getl 101 p_getlanguage 179 p_geticd 186 p_geticdcontrast 186 p_getlibh 222 p_getnotify 58 p_getosd 178 p_getowner 166 p_getpid 164 p_getpri 165 p_getpsu 178 p_getpth 127 p_getpthbyid 127 p_getram 71 p_getres 177 p_gets 101 p_getscancodes 185 p_getsnd 188 p_getsuffixes 110 p_gettext 179 p_gitob 30 p_graf.h 35, 100 p_gtob 30 p_hgran 69 p_hwexit 194 PINFO 133, 134, 135 PLINITQ 25 p_insrec 35 p_int 48 p_inti 48 p_intl 48 p_ioa 93 p_ioc 94 p_ioc(P_FABSOLUTE) absolute timer 106 p_ioc(P_FRELATIVE) relative timer 105 p_iosignal 85 p_iosignalbypid 85 p_iow 95 p_iow(P_FCANCEL) 97, 106, 146, 150 p_iow(P_FFLUSH) 145, 150 p_iow(P_FSETEOF) 146, 150 p_iowait 86 p_ioyield 86 p_isalnum 16 p_isalpha 15 p_iscntrl 16 p_isdigit 16 P_ISEMPTYQ 26 p_isgraph 16 p_islower 15 p_isprint 16 p_ispunct 16 p_isspace 16 p_isupper 15 p_isxdigit 16 p_itob 29 p_itof 48 P_JCENTRE 13 P_JLEFT 13 P_JRIGHT 13 p_jtob 13 p_Icdcontrastdelta 186 p_leave 60 p_linklib 221 p_In 45 p_loadfilelib 221 p_loadidd 98 p_loadlib 220 p_loadpdd 98 p_locchg 128 p_locdevice 132 p_locreadpdd 133 p_log 45 p_logoff 55 p_logoffa 55 p_logoffx 55 p_logon 55 p_logona 54 p_longtof 48 p_Itob 29 p_marka 164 p_math.h 41 P_MAXSYSIO 101 p_mcancel 174 p_mfree 174 p_minit 173 p_mkdir 138 p_mod 48 p_mreceive 173 p_mreceivew 173 p_msend 174 p_msendreceivea 175 p_msendreceivew 175 p_new 223 p_newlibh 223 P_NINFO 127, 128 p_nmday 109 p_nmdaya 110 p_nmmon 110 p_nmmona 110 p_notify 57 234 INDEX _ eee p_notifyerr 58 p_notifyhook 58 p_notifyunhook 58 p_now2str 115 P_NSECDAY 106 p_off 181 p_offrec 35 p_onterminate 54 p_open 92 p_open("TIM:") 105 p_open(P_FDEVICE) 128 p_open(P_FDIR) 133 p_open(P_FFORMAT) 131 p_open(P_FNODE) 127 p __open(P | FSTREAM) 142 p_open(P_FSTREAM TEXT) 147 p_open(P_FTEXT) 148 p_openlib 221 p_panic 53 p_pepyfr 167 p_pcpyto 167 Pp_pcreate 162 p_pfind 166 p_pidfind 166 P_pinrec 36 p_piscpyfr 167 p_pkill 53 p_playsounda 193 p_playsoundcancel 193 p_playsoundw 193 p_pname 165 P_POINT 35, 100 p_pow 46 P_ppanic 54 p_prename 166 p_presume 165 p_print 101 p_printé 101 p_psuspend 165 p_pterminate 53 p_putch 101 p_puts 101 P-PWILD ANY 124 P_PWILD_EXT 124 P_PWILD_NAME 124 p_qsort 24 P_QUE 25, 26, 27 p_que.h 25 p_rand 46 p_rand] 44 p_read 96, 144, 148 p_realloc 68 p_recilass 226 p_reclassbyhandle 226 p_recordsounda 192 p_recordsoundcancel 192 p_recordsoundw 193 P_RECT 35, 100 P_rename 136 Pp_romversion 177 p_scap 18 p_scat 12 p_scatm 12 p_scmp 19 p_scmpi 19 p_sconf 17 p_scpy 11 p_scpyf 17 P_scpym 12 p_sdate 103 p_seek 144, 149 p_semcrt 84 p_semdel 84 p_send 224 p_setauto 181 p_setautomains 181 p_setbacklight 187 p_setbat 185 p_setctd 180 p_setdefaultpath 126 p_setenv 76 p_setenviron 76 p_setnotify 58 p_setpri 165 p_setpth 126 p_setsnd 188 p_sfstat 138 p_sgadjust 74 p_sgclose 74 p_sgcopyfr 73 P_sgcopyto 73 p_sgcreate 72 p_sgdelete 73 p_sgofind 74 p_sgfree 71 p_sglock 75 p_sgopen 73 p_sgramdisk 71 p_sgsize 74 p_sguniock 75 p_signal 85 P_SIGNAL_DISABLE 87 P_SIGNAL ENABLE 87 P_SIGNAL_UNUSED 87 p_signaln 85 p_signainr 85 p_sin 44 p_skipch 17 p_skipwh 16 p_sleep 104 p_sleepa 105 p_sleept 104 p_slen 11 p_sloc 20 p_sloci 20 p_slocr 20 p_slocri 20 p_smatch 22 p_smatchi 22 p_sound 187 p_sqrt 46 p_srep 13 p_ssub 21 p_ssubi 21 p_st2str 115 p_std.h 5 p_stoa 33 p_stod 42 p_stog 32 p_stog! 33 p_stoi 32 p_stol 32 p_sttods 107 p_supersend 224 p_supply 182 p_supplyinfo 183 235 PLIB REFERENCE p_svecadd 87 p_sveccall 88 p_svecrem 88 p_tan 45 p_testpth 136 p_tickle 164 p_tofold 17 p_tolower 17 p_totalK 71 p_toupper 17 p_unirec 36 p_unloadlib 221 p_unmarka 164 p_version 177 p_wait 85 p_waitstat 86 p_watchall 56 p_weekno 108 p_wkday 108 p_write 97, 144, 149 p_wsupply 184 panic 50 numbers 50 object-oriented programming 53 OLIB 53 process,by ID 54 window server 53 PAR: 90 paragraph 65 parallel port 9 parse file specification 123 PDD 89 physical device driver 89 piezo-electric 187 sound,make 187 PLIB C startup module 7 header files 4 library 1 plib.h 4 polymorphic 217 power 46 power supply 178, 182 pragma 4 call 6 restore 6 save 6 preemptive scheduling 151, 155 preferences 43 priority process 154 process 151 active,mark 164 activity,register 164 control block 152 creating 160, 162 current 163 data segments 64, 65, 167 data,copy from 167 data,copy to 167 find all 166 find owner 166 heap 65 ID 152 ID,fetch 164 image,load 160 image,load asynchronously 162 indirected string,copy from 167 236 messaging 168 name by ID,fetch 165, 166 names 155 non-active,mark 164 null 164, 180 on terminate,notify 50 operations 165 priorities 154 priority,get 165 priority,set 165 queues 154 rename 166 resume 165 scheduling 79 shared code segments 158 state 154 subsidiary 155 suspend 104, 160, 165 suspend until 105 system 152 terminate 49, 53, 159 terminate word 50, 55 usage count 65 zero priority 164, 180 processor 80286 63 80386 63 80486 63 8086 1, 63 stack 65 program environment 2 smail 7 small model 2, 65 queue delta 27, 79, 154, 165 delta,add entry 27 delta,remove entry 27 doubly linked 25 doubly linked,add entry 26 doubly linked,remove entry 27 process 154 ready 79, 154, 165 semaphore 79, 154, 165 time delta 79, 103 quicksort 24 r 157 raise to power 46 RAM addressable size in paragraphs 71 disk,memory used 71 drive 63 static 118, 119 total size in kilobytes 71 random number 44, 46 re-schedule 85 real-time clock 1 record text file access 147 rectangle absolute,convert 37 displace 35 empty,test 36 inset 35 intersection 36 point inside,test 36 union 36 rectangle functions 34 reference manuals 8 INDEX rn register based convention 7 registers segment 3, 63 relative timer 104 REM:: 117 reserved statics 65, 156 reset 152 ROM 2, 63 configuration file 179 DYL 213 masked 118 once programmable 118 system software 1 system tables 14 version 177 ROM:: 117 SYSSCTRY.CFO 179 scheduling preemptive 151, 155 process 79 scientific functions 44 segments .$nn 64 .$SC 64 .DYL 64 .LDD 64 -PDD 64 address 64 adjust size 74 available memory 71 close 74 code 2, 158 copy from 73 copy to 73 create 72 data 2 decrease usage count 75 delete 73 device 64 dynamic 64 external memory 3, 71 find by name 74 get size 74 handle 64 increase usage count 75 index table 65 lock 75 memory 64, 71 name 64 open 73 process data 65, 167 registers 3, 63 shared code 158 size 64 unlock 75 usage count 65 semaphores 79, 90 create 84 delete 84 /O 81, 85 /O,signal 85 1/O,signal process 85 /O,wait 86 mutual exclusion 80 primitive functions 84 process scheduling 79 queue 79, 154 serialised access 80 shared resource 80 signal 85 signal,multiple 85 signal,no re-schedule 85 status word 81 wait 85 serial port 9 serialised access 80 server 169 asynchronous message reception 173 functions 172 message reception,cancel 174 message reception,initialise 173 message reception,wait 173 message,free 174 shared code segments 158 shared resource 80 shrinking the heap 66 SIBO 1, 63, 89, 104, 118, 131, 185 architecture 1 sine 44 single-user 151 small model program 2, 65 small program 7 SND: 187 SndFile 188 software interrupts 4, 218 solid state disk 1 sound 187 duration 193 files 188 flags,fetch 188 flags,set 188 loudness 193 piezo-electric 187 play back,asynchronously 193 play back,cancel 193 play back,synchronously 193 record,asynchronously 192 record,cancel 192 record,synchronously 193 Series 3a 188 Series 3a,services 192 sound driver 9 square root 46 SSD 1, 144, 195 drives 118 flash EPROM 118, 119, 145 local,direct read 133 masked ROM 118 once programmable ROM 118 static RAM 118, 119 stack 2, 7, 65 stack based convention 6 status word 81 stray signal 81 stream text file access 146 string 11 arguments,convert 33 capitalise 18 compare 19 compare,case independent 19 concatenate 12 copy 11 copy and fold 17 fold 17 from double,convert 41 functions 11 237 PLIB REFERENCE length of,fetch 11 locate character 20 locate character,case independent 20 locate last match character 20 locate last match character,folded 20 locate sub-string 21 locate sub-string,case independent 21 multiple arguments,convert 31 multiple,concatenate 12 multiple,copy 12 pattern match 22 pattern match,case independent 22 replicate 13 searching 19 to double,convert 42 structures DbfHeader 198, 199 DbfOpenArgs 200 DbfRecord 196 E CONFIG 43, 108, 111, 180 E CPB 162, 163 E MESSAGE 168 E PROC 153 E SUPPLY 182 E SUPPLY_INFO 183 E SUPPLY_WARNINGS 184 ImgHeader 158 P_CLASS 211 P_DATE 107, 108, 115 P_DAYSEC 106, 107, 108, 115 P_DELTA 27 P_DINFO 129 P_DTOB 41 P_FPARSE 123 PLINFO 133, 134, 135 P_NINFO 127, 128 P_POINT 35, 100 P QUE 25, 26, 27 P_RECT 35, 100 SndFile 188 subsidiary process 155 superclass chaining 217 supervisor 168 moving memory 3 suspended process 160, 165 switch off 104, 164, 180 switch on 104, 164, 180 synchronous serial interface 1 SYS$8087.LDD 39 SYSSCTRY.CFO 179 SYSS$FSRV 91, 117 SYSSFSRV.$03 152 SYSSMANG 64 SYSS$MANG.$02 3, 152 SYSSNULL 64 SYS$NULL.$01 152 SYS$SHLL.$05 152 SYSSWSRV.$04 152 system addressable RAM in paragraphs 71 asynchronous request 80 auto-switch-off period,set 181 auto-switch-off,allow 181 available segmented memory 71 backlight control,set 187 backlight off 186 backlight on 186 238 battery type,set 185 country-dependent data,set 180 exit 194 1/0 89 LCD contrast,change 186 memory usage 63, 71 ON key event,disable 182 ON key event,enable 182 panic numbers 50 processes 152 reset 152 ROM 63 services 4 services reference 9 sound flags,set 188 sound,piezo-electric 187 switch off 181 switch-off if mains,disable 181 switch-off if mains,enable 181 tables 14 ticks 151 time 103 time to string,convert 115 time,fetch 103 time,set 103 total RAM in kilobytes 71 system information 177 auto-switch-off period,fetch 181 backlight enablement,fetch 187 battery type,get 185 battery warning 184 country code 178 country-dependent data,fetch 180 display type,fetch 186 keyboard 185 language code 178, 179 last shutdown,cause 177 LCD contrast,get 186 maximum levels 184 o/s data 178 o/s text 179 o/s version 177 power status additional,fetch 183 power status,fetch 182 power supply 182 power supply type 178 ROM version 177 Series 3a,sound 188 sound 187 sound flags,fetch 188 state of keys,fetch 185 switch off 180 switch on 180 switch-off if mains,fetch 181 system wide default path 126 T 157 tangent 45 task 152, 155 terminate notify 50 notify,all processes 56 notify,cancel 55 notify,request 54, 55 process 49, 53, 159 process,by ID 54 process,this 53 process,unilaterally 53 process,unrecoverable error 53 INDEX OC eee receive message 54 special notify,cancel 55 word 50, 55 TEXT 5 file,close 148 file,flush 150 file,sset end 150 record file access 147 record file,open 148 record file,position 149 record file,read 148 record file,seek 149 record file,write 149 record termination 146 stream file 146 stream file,open 147 ticks 151 TIM: 90 time class 109 converting representations 106 current to string,convert 115 day to day in week,convert 108 days in month 108 format string 112 P_DATE to P_DAYSEC,convert 108 P_DATE to string,convert 115 P_DAYSEC to P_DATE,convert 107 P_DAYSEC to string,convert 115 P_DAYSEC to system,convert 107 preferences 111 string generation 112 system 103 system to P_DAYSEC,convert 107 system to string,convert 115 system,fetch 103 system,set 103 text form 109 week number 108 time delta queue 79, 103 timer 103 absolute 104 absolute,start 106 asynchronous 105 cancel 106 channel,close 106 channel,open 105 relative 104 relative,start 105 suspend process 104 suspend process until 105 watchdog 3 TopSpeed C library reference 8 compiler 3 TTY: 90 Turbo C 4 UBYTE 5 UINT 5 ULONG 5 unattended applications 118 unhook notifier interface 58 usage count 65 UWORD 5 variables environment 63, 75 environment,delete 76, 77 environment,find 77 environment,get value 75, 76 environment,set value 76 magic statics 65 reserved static 65 static,initialised 65 static,uninitialised 65 version 177 volume label setting 139 w_am 157 w_ws 156 wait handlers 83 activate/deactivate 88 add 87 application 84 attached I/O devices 84 device 84 functions 87 let run 86 polling vs waiting 84 remove 88 watchdog 1, 3 wav2wve.exe 189 wClientData 157 WIMP.DYL 10 window server 168 library 1 panic 53 reference 8 WLIB library 1,9 WORD 5 working set 7 wserv_channel 157 zero priority process 164, 180 239