88
Academiejaar 2013–2014 Faculteit Ingenieurswetenschappen en Architectuur Valentin Vaerwyckweg 1 – 9000 Gent Omzetten van een Windows C++ MFC telefonie servertoepassing naar Linux Promotoren: Ann VAN OVERBERGE Ing. Filip HOSTE (MyForce) Tibault DAMMAN Masterproef voorgedragen tot het behalen van het diploma van Master in de industriële wetenschappen: informatica Powered by TCPDF (www.tcpdf.org)

Omzetten van een Windows C++ MFC telefonie servertoepassing … · 2014. 10. 16. · Academiejaar 2013 2014 Faculteit Ingenieurswetenschappen en Architectuur Valentin Vaerwyckweg

  • Upload
    others

  • View
    2

  • Download
    0

Embed Size (px)

Citation preview

  • Academiejaar 2013–2014 Faculteit Ingenieurswetenschappen en Architectuur

    Valentin Vaerwyckweg 1 – 9000 Gent

    Omzetten van een Windows C++ MFC

    telefonie servertoepassing naar Linux

    Promotoren: Ann VAN OVERBERGE

    Ing. Filip HOSTE (MyForce)

    Tibault DAMMAN

    Masterproef voorgedragen tot het behalen van het diploma van

    Master in de industriële wetenschappen: informatica

    Powered by TCPDF (www.tcpdf.org)

  • Voorwoord

    Vooreerst mijn dank aan iedereen bij MyForce voor een bijzonder aangename stage, ik weetniet wat ik zonder de voortdurende aanmoediging (“en, compileert het al?”) gedaan zouhebben. In het bijzonder wil ik Filip Hoste en Bert Reyntjes bedanken voor hun hulp enuitleg bij de code.

    Daarnaast wil ik ook mijn promotor Ann Van Overberge bedanken voor het nalezen van dezescriptie en geven van bruikbare feedback. Ook aan Rudy Stoop, tweede lezer en docent C++,een woord van dank.

    Ten slotte dank ik ook Hannes, Arne en Bas voor hun tips en het verdragen van mijn onge-twijfeld eindeloos geklaag.

    Tibault Damman

    De Pinte, 28 augustus 2014

  • Omzetten van een Windows C++ MFCtelefonie servertoepassing naar Linux

    door

    Tibault DAMMAN

    Masterproef voorgedragen tot het behalen van het diploma van

    Master in de industriële wetenschappen: informatica

    Academiejaar 2013–2014

    Interne promotor: Ann VAN OVERBERGE

    Externe promotor: Ing. Filip HOSTE (MyForce)

    Faculteit Ingenieurswetenschappen en Architectuur

    Universiteit Gent

    Abstract

    MyForce is een bedrijf dat zich specialiseert in het ontwikkelen van oplossingen voor bedrijven

    waar automatisatie van telefonie centraal staat, zoals bij callcenters en bureaus voor markt-

    onderzoek. HardwareClient is het component dat instaat voor de communicatie tussen het

    publieke telefonienetwerk (via ISDN en VoIP) en het interne bedrijfsnetwerk.

    Momenteel werkt deze software enkel op Microsoft Windows, maar de gebruikte middleware

    en drivers voor dit platform worden minder actief ontwikkeld dan hun Linux variant.

    Deze thesis heeft als doel de HarwareClient software beschikbaar te maken op Linux, om op

    deze manier gebruik te kunnen maken van alle nieuwe mogelijkheden die dit platform met

    zich meebrengt.

    Om dit resultaat te verkrijgen moesten de verschillen tussen Windows en Linux onderzocht

    worden wat betreft de drivers, de sockets, de multithreading, het aanspreken van het bestands-

    systeem, tot zelfs de verschillen tussen de compilers. Een groot deel van de tijd werd besteed

    aan het vervangen van het MFC framework, een Microsoft alternatief voor de STD library,

    dat bovendien een schil rond een groot deel van de Win32 functies biedt. Voorzichtigheid was

    ook geboden om de compatibilteit met bestaande Windowssystemen niet te breken.

    Trefwoorden

    Linux – porting – MFC – C++ – telefonie

  • Porting a Windows C++ MFC

    telephony server application to Linuxby

    Tibault DAMMAN

    Master thesis submitted to obtain the degree of

    Master of Science in Information Engineering Technology

    Academic year 2013–2014

    Internal promotor: Ann VAN OVERBERGE

    External promotor: Eng. Filip HOSTE (MyForce)

    Faculty of Engineering and Architecture

    University Gent

    Abstract

    MyForce is a company specialising in the development of solutions for companies with a focus

    on telecommunication, such as call centres and market researchers. HardwareClient is their

    software component responsible for linking the public telephony network (through ISDN or

    VoIP) with the internal company network.

    Currently this software only runs on Microsoft Windows, but the used middleware and drivers

    for this operating system are less actively developed than their Linux counterpart.

    The goal of this thesis is to enable HardwareClient to run on Linux, so that it can make use

    of the new possibilities and increased third party support this platform offers.

    Various differences between Windows and Linux were researched, varying from drivers, to

    sockets, multithreading, file systems, and even the intricate differences between compilers.

    The majority of the work consisted of replacing the MFC framework, a Microsoft alternative

    to the C++ STD, that also offers an easy wrapper around many Win32 functions. Great care

    was also taken not to break compatibility with the existing Windows clients and servers.

    Keywords

    Linux – porting – MFC – C++ – telephony

  • INHOUDSOPGAVE i

    Inhoudsopgave

    1 Inleiding 1

    2 Toolchain 22.1 Besturingssysteem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22.2 C++ compiler en standard library . . . . . . . . . . . . . . . . . . . . . . . . . 22.3 Buildsystem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2

    2.3.1 CMake . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32.3.1.1 Cross compiling . . . . . . . . . . . . . . . . . . . . . . . . . 42.3.1.2 Lijst van cpp-bestanden bekomen . . . . . . . . . . . . . . . 5

    2.4 IDE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52.5 Version Control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5

    3 Middleware (Driver/SDK) installatie 63.1 Dialogic Powermedia HMP . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63.2 Aculab . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7

    3.2.1 libTiNG . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8

    4 MFC 94.1 Wat is MFC? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94.2 MFC alternatieven . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9

    4.2.1 Wine . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104.2.2 Qt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104.2.3 wxWidgets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114.2.4 Boost . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114.2.5 C++11 STD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11

    4.3 Eigen MFC implementatie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11

    5 Het porting proces 135.1 Overzicht van de uiteindelijke mappenstructuur . . . . . . . . . . . . . . . . . 14

    6 Datatypes en -structuren 156.1 Datatypes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156.2 Array, List en Map . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156.3 String . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16

    6.3.1 Unicode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176.3.2 Implementatie CString . . . . . . . . . . . . . . . . . . . . . . . . . . . 19

  • INHOUDSOPGAVE ii

    6.3.3 Extra stringfuncties . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216.3.4 Unicode in praktijk . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

    6.3.4.1 Vergelijken van tekens . . . . . . . . . . . . . . . . . . . . . . 24

    7 Time 267.1 GetTickCount . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 267.2 CTime . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 277.3 GetSystemTime en GetLocalTime . . . . . . . . . . . . . . . . . . . . . . . . 287.4 COleDateTime en COleDateTimeSpan . . . . . . . . . . . . . . . . . . . . . . 29

    8 IO 318.1 Sockets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31

    8.1.1 TCP keepalive . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 318.1.2 Asynchrone sockets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34

    8.1.2.1 select(2) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 348.1.2.2 poll(2) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 348.1.2.3 epoll(7) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 348.1.2.4 libevent, libev, libuv . . . . . . . . . . . . . . . . . . . . . . . 358.1.2.5 (C++11 / boost) Asio . . . . . . . . . . . . . . . . . . . . . . 35

    8.2 Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 358.2.1 CFile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 368.2.2 CFileFind . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 398.2.3 Unicode BOM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42

    8.3 CRC-32 en Zip . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42

    9 Multithreading 449.1 Threads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 449.2 Locking (critical sections en mutexen) . . . . . . . . . . . . . . . . . . . . . . 44

    9.2.1 CSingleLock . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 449.2.2 Named Mutex . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45

    9.3 CEvent . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 469.4 Compiler intrinsieke functies . . . . . . . . . . . . . . . . . . . . . . . . . . . . 479.5 Thread Local Storage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48

    10 Daemon 4910.1 GetModuleFileName . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5010.2 Bepalen of de daemon al draait . . . . . . . . . . . . . . . . . . . . . . . . . . 5010.3 Het Windowsregister . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51

    11 Link met middleware 5211.1 Importeren van functies uit bibliotheken . . . . . . . . . . . . . . . . . . . . . 52

    11.1.1 set invalid parameter handler . . . . . . . . . . . . . . . . . . . . . . 5311.2 Headerconflicten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5511.3 Ontbrekende functionaliteit . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55

    11.3.1 Dialogic file API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5611.3.2 NCM API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5711.3.3 ALGO LOUD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57

  • INHOUDSOPGAVE iii

    11.3.4 SR MODELTYPE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5711.3.5 SWMODE CTBUS SCBUS . . . . . . . . . . . . . . . . . . . . . . . . 58

    12 Struikelblokken 5912.1 64-bit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59

    12.1.1 M X64 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5912.1.2 Pointer als int . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5912.1.3 sizeof(long) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60

    12.2 Het bestandssysteem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6012.3 Striktheid van de compiler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6112.4 Structured Exception Handling . . . . . . . . . . . . . . . . . . . . . . . . . . 6212.5 Problemen met macro’s . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62

    12.5.1 Slechte ifdefs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6312.6 Het nut van typename bij templates . . . . . . . . . . . . . . . . . . . . . . . 64

    13 Varia 6613.1 Het aantal elementen van een array op de stack . . . . . . . . . . . . . . . . . 6613.2 StrFormatByteSize . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6613.3 AfxIsValidAddress . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67

    14 Conclusie 69

    Bijlage A Installatie drivers/sdk 70A.1 Dialogic HMP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70A.2 Aculab . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74

    Bibliografie 77

  • LIJST VAN CODEFRAGMENTEN iv

    Lijst van codefragmenten

    2.1 Verkorte versie van het gebruikte CMake projectbestand . . . . . . . . . . . . 32.2 Cross compiling met CMake . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42.3 Bash instructie om codebestanden uit een Visual Studio project te filteren . . 53.1 CMake - Dialogic headers en bibliotheken . . . . . . . . . . . . . . . . . . . . 73.2 CMake - Aculab headers en bibliotheken . . . . . . . . . . . . . . . . . . . . . 83.3 CMake - libTiNG preprocessor symbool . . . . . . . . . . . . . . . . . . . . . 85.1 STUBBED macro [Gordon 2014] . . . . . . . . . . . . . . . . . . . . . . . . . 136.1 Enkele voorbeelden van MFC typedefs . . . . . . . . . . . . . . . . . . . . . . 156.2 Voorbeeld van het itereren over een CList met POSITION . . . . . . . . . . . 166.3 Gespecialiseerde datastructuren . . . . . . . . . . . . . . . . . . . . . . . . . . 166.4 Typedefs voor C-strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166.5 Constructors en operator overloads van CString . . . . . . . . . . . . . . . . . 196.6 CString non-member operator overload . . . . . . . . . . . . . . . . . . . . . . 196.7 Een selectie van enkele CString methodes . . . . . . . . . . . . . . . . . . . . 206.8 CString Format . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 206.9 ttol parst een long naar een CString . . . . . . . . . . . . . . . . . . . . . . . 216.10 implementatie vsnprintf s, strncpy s en memcpy s . . . . . . . . . . . . . . . 216.11 CWString een UTF-16 CString klasse . . . . . . . . . . . . . . . . . . . . . . 236.12 Conversiefuncties tussen UTF-8 en UTF-16 . . . . . . . . . . . . . . . . . . . 236.13 Problemen bij multi byte strings . . . . . . . . . . . . . . . . . . . . . . . . . 246.14 Tekens vergelijken met UTF-8 aan de hand van std::mbrtowc . . . . . . . . . 257.1 Implementatie van GetTickCount . . . . . . . . . . . . . . . . . . . . . . . . . 267.2 CTime constructor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 277.3 Enkele CTime methodes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 277.4 de SYSTEMTIME struct . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 287.5 Implementatie van GetSystemTime . . . . . . . . . . . . . . . . . . . . . . . . 297.6 COleDateTime en COleDateTimeSpan implementatie . . . . . . . . . . . . . 298.1 TCP keep-alive activeren met WSA voor m hSocket . . . . . . . . . . . . . . 318.2 TCP keep-alive activeren op Linux voor socket s . . . . . . . . . . . . . . . . 328.3 De resulterende TCP keep-alive wrapper . . . . . . . . . . . . . . . . . . . . . 328.4 de header van CFile en CFileStatus . . . . . . . . . . . . . . . . . . . . . . . . 368.5 CFile::Open methode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 378.6 Enkele CFile methodes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 388.7 CFileFind implementatie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 408.8 (UTF-16) BOM is op Linux niet nodig . . . . . . . . . . . . . . . . . . . . . . 429.1 Voorbeeld van de scope van een CCriticalSection . . . . . . . . . . . . . . . . 45

  • LIJST VAN CODEFRAGMENTEN v

    9.2 Implementatie CCriticalSection::Lock met pthread mutex t . . . . . . . . . . 459.3 Voorbeeld van het gebruik van CEvent . . . . . . . . . . . . . . . . . . . . . . 469.4 Voorbeeld van het gebruik van pthread conditions . . . . . . . . . . . . . . . 469.5 GCC compatibele implementatie van InterlockedIncrement en -Decrement . . 479.6 Thread Specific Storage wrapper . . . . . . . . . . . . . . . . . . . . . . . . . 4810.1 Opvragen van het executable pad op Windows . . . . . . . . . . . . . . . . . 5010.2 Opvragen van het executable pad op Linux . . . . . . . . . . . . . . . . . . . 5010.3 Controlleren of een applicatie al draait . . . . . . . . . . . . . . . . . . . . . . 5011.1 Dynamische bibliotheek oproepen op Windows . . . . . . . . . . . . . . . . . 5211.2 Dynamische bibliotheek oproepen op Linux . . . . . . . . . . . . . . . . . . . 5211.3 Wrapper voor dynamisch linken . . . . . . . . . . . . . . . . . . . . . . . . . . 5311.4 Instellen van een invalid parameter handler op Windows . . . . . . . . . . . . 5311.5 Dialogic file API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5611.6 Ontbreekt: ALGO LOUD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5711.7 Ontbreekt: SR MODELTYPE . . . . . . . . . . . . . . . . . . . . . . . . . . 5712.1 M X64 definiëren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5912.2 Code die enkel in Visual Studio compileert . . . . . . . . . . . . . . . . . . . . 6112.3 Code die overal compileert . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6112.4 Een macro aanroep . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6212.5 De macrodefinitie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6212.6 De macroaanroep na de preprocessor . . . . . . . . . . . . . . . . . . . . . . . 6312.7 Een lege define . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6312.8 Defines kunnen ook voor problemen zorgen . . . . . . . . . . . . . . . . . . . 6312.9 Problematische implementatie CMap::Lookup . . . . . . . . . . . . . . . . . . 6412.10’typename’ voor een gekwalificeerd afhankelijk type . . . . . . . . . . . . . . . 6513.1 Een macro voor de grootte van een array op de stack . . . . . . . . . . . . . . 6613.2 FormatBytes maakt gebruik van StrFormatByteSize . . . . . . . . . . . . . . 6613.3 Implementatie CStaticTools::FormatBytes (StrFormatByteSize) . . . . . . . . 6713.4 Microsofts implementatie van AfxIsValidAddress . . . . . . . . . . . . . . . . 68

  • INLEIDING 1

    Hoofdstuk 1

    Inleiding

    MyForce (vroeger MI4C) specialiseert zich in innovatieve oplossingen op gebied van Com-puter Telephony Integration (CTI), waaronder het pakket genaamd CTArchitect. Hiermeekunnen gewone servers omgetoverd worden tot complete telefonieoplossingen voor bedrijvenwaar automatisatie van telefonie centraal staat (zoals callcenters, bureaus voor telefonischmarktonderzoek, helpdesks, etc). Dit beperkt zich niet tot traditionele ISDN telefoonlijnen:ook het flexibelere en puur softwarematige VoIP wordt ondersteund. VoIP heeft als voordeeldat het – naast een USB headset voor elke agent – geen kostelijke extra hardware vereist, diedan ook niet vervangen moet worden bij falen. Bijkomend voordeel is dat het erg schaalbaaris en naar de cloud kan worden verhuisd.

    Zo’n volledig telefonienetwerk wordt beheerd door één CTArchitect server, die op zijn beurtcommuniceert met enkele HardwareClient servers. Op deze servers staat een (software) VoIPserver gëınstalleerd, en/of een (hardware) ISDN kaart die met het vaste telefonienetwerk ver-bonden is. De HardwareClient software stelt zijn beschikbare VoIP/ISDN telefoonlijnen danbeschikbaar op een gestandaardiseerde manier op het netwerk (via sockets). Eén Hardware-Client server kan zo verantwoordelijk zijn voor enkele honderden telefoonlijnen.

    CTArchitect kan dan aan de hand van enkele speciaal ontwikkelde algoritmen op een intelli-gente manier de meest optimale verbindingen maken tussen persoon en lijn.

    De onderliggende ISDN/VoIP hardware/software (van onder andere netwerkcommunicatie-bedrijf Dialogic) die in deze oplossingen gebruikt wordt, bestaat reeds in een Linux- en Win-dowsversie, waarvan de Linuxversie het best onderhouden is en de meeste features biedt.MyForce ondersteunt tot nu toe enkel Windows, maar is erg gëınteresseerd in een Linuxversie om van de bijkomende functionaliteit te kunnen genieten.

    Deze thesis behandelt de vele stappen die nodig zijn om een project als HardwareClient naarLinux te brengen.

    We starten met het opzetten van een ontwikkelingsomgeving met alle benodigde onderdelenen bekijken vervolgens hoe we het omzetten van de code best aanpakken. We onderzoekenhoe Windows en Linux zich verschillen op gebied van sockets, multithreading en meer, enhoe zich dat vertaalt in code. Ook verborgen complexiteiten en verschillen in de compilersworden besproken.

  • TOOLCHAIN 2

    Hoofdstuk 2

    Toolchain

    Vooraleer aan de code van de Linuxversie kan worden begonnen, moeten er eerst een aantalkeuzes worden gemaakt omtrent de programmeeromgeving.

    2.1 Besturingssysteem

    “Linux” op zich is geen besturingssyteem, maar een kernel. De keuze uit Linuxgebaseerdebesturingssystemen is groot: Ubuntu, Debian, Fedora, RHEL, CentOS, SuSe, ...

    Voor HarwareClient werd (in overleg met MyForce) gekozen voor CentOS 6.5 als doelplatform,om drie redenen:

    ervaring met het platform

    de stabiliteit

    het wordt door alle gebruikte middleware ondersteund

    2.2 C++ compiler en standard library

    De twee meest populaire compilers op Linux zijn momenteel GCC en LLVM/CLang, metrespectievelijk libstdc++ en libc++ als verkozen standard library. Rekening houdend met hetdoelplatform en de middleware, was de combinatie GCC met libstdc++ de beste keuze.

    2.3 Buildsystem

    In een project met heel veel codebestanden en verschillende externe bibliotheken, is het nietevident om elke keer handmatig de compiler aan te geven in welke volgorde de bestandengecompileerd moeten worden, en welke onderling gelinkt moeten worden.

  • 2.3 Buildsystem 3

    Dit kan geautomatiseerd worden door deze informatie vast te leggen inMakefiles, of project-bestanden. Op Windows worden hier vaak solution files (en bijhorende filters) voor gebruikt,dit zijn de projectbestanden van de Microsoft Visual Studio IDE.

    Elke IDE op Linux heeft zo ook een eigen gelijkaardig projectbestand, maar die zijn weinigpopulair. De klassieke Makefiles genieten nog altijd de voorkeur.

    Makefiles zijn eenvoudige tekstbestanden waarin aan de hand van een declaratieve syntaxregels worden opgesteld voor de compiler. Wanneer de gebruiker in de commandolijn maketypt, zal de Makefile automatisch gevonden en uitgevoerd worden.

    Makefiles zijn erg populair in de Open Source wereld, omdat het geen verplichtingen oplegtwat betreft de te gebruiken IDE. Gezien iedereen de code kan compileren door make te typen,is het gebruik van een IDE niet eens verplicht.

    Makefiles schrijven is een complexe platform- en compilerafhankelijke opdracht, om dit tevereenvoudigen en te automatiseren kan gebruik gemaakt worden van CMake.

    2.3.1 CMake

    CMake is een populair systeem om platform- en compileronafhankelijke projectbestanden teschrijven. Aan de hand van een eenvoudige syntax wordt opgegeven waar de code staat, waarextra headers te vinden zijn, met welke bibliotheken gelinkt moet worden, enzovoort. CMakekan aan de hand van deze gegevens automatisch een projectbestand genereren, zoals onderandere een Microsoft Visual Studio solution of Unix Makefile.

    cmake_minimum_required (VERSION 2.6)project (HWClient) #set project name

    SET(EXECUTABLE_OUTPUT_PATH ${CMAKE_SOURCE_DIR }/bin)SET(LIBRARY_OUTPUT_PATH ${CMAKE_SOURCE_DIR }/lib)

    SET(CMAKE_CXX_FLAGS "${xcomp} -std=c++0x -pthread -g -Wall")

    # Necessary symbol to comile hardware client (cfr HWClientManager.cpp)add_definitions("-DCLIENTMANAGER -DMANAGERDIALOGIC -D__NCM_DISABLED")

    INCLUDE_DIRECTORIES (. ../ CTArchitect ../ CTDesign ../ ProjectCopy)

    SET(SRC StdAfx.cppHWClient.cpp...)

    add_executable(${PROJECT_NAME} ${SRC})target_link_libraries(${PROJECT_NAME} dl rt m z)

    Codefragment 2.1: Verkorte versie van het gebruikte CMake projectbestand

    Codefragment 2.1 toont een minimale versie van het CMake configuratiebestand voor Hardware-Client. We zien hoe de naam van het project ingesteld wordt, hoe de mappen gekozen worden

  • 2.3 Buildsystem 4

    waarin de binaries terecht zullen komen, en hoe we extra vlaggen aan de compiler (GCC) mee-geven.

    -std=c++0x activeert C++11, -pthread voegt ondersteuning voor pthreads toe, -g zorgt voorhet toevoegen van debugsymbolen, en -Wall activeert alle compiler warnings.

    add definitions wordt gebruikt om preprocessor symbolen in te stellen, CLIENTMANAGERen MANAGERDIALOGIC moeten bijvoorbeeld verplicht gedefinieerd zijn voor de compilatie vanHardwareClient.

    Met INCLUDE DIRECTORIES worden alle mappen opgegeven waar zich headers in kunnen bevin-den. De SRC variabele vullen we met SET op met alle .cpp bestanden die gecompileerd moetenworden. Dit wordt vervolgens met de gewenste bestandsnaam voor de binary (de project-naam ingesteld in het begin) doorgegeven aan add executable. CMake zal zelf bepalen watde afhankelijkheden zijn en in welke volgorde het compileren en linken moet gebeuren.

    Het resultaat wordt met target link libraries dan nog gelinkt met externe bibliotheken:dl (dynamic linking), rt (real time), m (math), en z (zlib).

    Om meerdere besturingssystemen, architecturen en compilers te ondersteunen met hetzelfdeCMake bestand kan gebruikt gemaakt worden van conditionele logica.

    2.3.1.1 Cross compiling

    Om een onderscheid te kunnen maken tussen 32-bit en 64-bit (zie later ook in hoofdstuk 3),en om op een 64-bit systeem een 32-bit binary te kunnen produceren maken we gebruik vancross compiling.

    Om in CMake onderscheid te kunnen maken tussen deze twee architecturen voegen we on-derstaande code toe:

    if(DEFINED arch AND (arch EQUAL 32 OR arch EQUAL 64))message("Selecting ${arch} bit architecture ...")

    else()if(CMAKE_SIZEOF_VOID_P EQUAL 8)

    set(arch 64)else()

    set(arch 32)endif()message("Defaulted to native ${arch} bit architecture ...")

    endif()

    # crosscompile if 32b target on 64bif(arch EQUAL 32 AND CMAKE_SIZEOF_VOID_P EQUAL 8)

    set(xcomp "-m32")else()

    set(xcomp "")endif()

    Codefragment 2.2: Cross compiling met CMake

  • 2.4 IDE 5

    Dit introduceert een arch variabele die we instellen op 64 wanneer een pointer (CMAKE SIZEOFVOID P) 8 byte groot is, of 32 indien hij 4 byte groot is. In de CMake debugoutput wordt viamessage geprint welke architectuur gekozen is, zodat we bij het genereren van een buildfileof projectbestand in de debugoutput kunnen nagaan of de juiste architectuur geselecteerdwerd.

    Indien je wil cross compilen naar 32-bit op een 64-bit systeem kan je de waarde van archoverschrijven door op voorhand de variabele zelf als volgt in te stellen: set(arch 32).

    In dit geval wordt de xcomp variabele ingevuld met de GCC compilervlag “-m32”, die crosscompilen activeert. In codefragment 2.1 was al zichtbaar hoe deze variabele gebruikt wordt.

    2.3.1.2 Lijst van cpp-bestanden bekomen

    Om snel een lijst van alle nodige cpp-bestanden te bekomen kan een eenvoudig scriptje geschre-ven worden om deze uit de moeilijk leesbare Visual Studio solution bestanden te parsen:

    cat HWClient.v11.vcxproj.filters | sed "s/Filter Include/workaround/" \| grep " Include=" | sed "s/.*/\1/"

    Codefragment 2.3: Bash instructie om codebestanden uit een Visual Studio project te filteren

    2.4 IDE

    Een goede IDE helpt aan de hand van onder andere syntax highlighting en een ingebouwdedebugger om snel te vinden welke code niet werkt en aangepast moeten worden. Op Windowswerd Microsoft Visual Studio gebruikt, op Linux werd gekozen voor QtCreator.

    Naast een uitgebreide IDE werd ook VIM gebruikt om snelle aanpassingen te maken.

    2.5 Version Control

    Bij MyForce wordt SVN gebruikt. Experimentele Linux aanpassingen worden beter nietdirect in de hoofdrepository opgenomen, daarom werd voor de Linux versie een afzonderlijkerepository gebruikt. Uit persoonlijke voorkeur werd hier voor Git gekozen.

  • MIDDLEWARE (DRIVER/SDK) INSTALLATIE 6

    Hoofdstuk 3

    Middleware (Driver/SDK)installatie

    MyForce ondersteunt de telefonietechnologie (ISDN en VoIP) van meerdere leveranciers zoalsDialogic, Aculab en Amtelco. Deze bedrijven leveren naast hardware ook software, zoals dedrivers nodig voor de hardware, VoIP servers ter vervangen van hardware, en bibliothekendie het ontwikkelen van telefonietoepassingen in het algemeen vereenvoudigen. Dit soortsoftware wordt ook middleware genoemd, omdat het zich tussen het besturingssysteem en degebruikersapplicatie bevindt.

    Voor de Linuxversie werd de ondersteuning beperkt tot de technologie van Dialogic en Aculab.

    3.1 Dialogic Powermedia HMP

    Over het bedrijf:

    Dialogic, the Network Fuel® company, inspires the world’s leading service provi-ders and application developers to elevate the performance of media-rich commu-nications across the most advanced networks. [Dialogic 2014a]

    Dialogic PowerMedia Host Media Processing (HMP) laat toe om grootschaalse telefonie-applicaties te bouwen voor gewone servers, zonder verplicht gebruik te moeten maken vangespecialiseerde hardware. Dit is een zeer belangrijk onderdeel van HardwareClient.

    Dialogic® PowerMedia HMP for Linux (HMP Linux) is scalable, feature-richmultimedia processing software for building innovative and cost-effective voiceand video solutions suitable for enterprise or service provider deployment. HMPLinux can enable basic SIP or hybrid connectivity, audio and video play/record,multimedia streaming, transcoding, fax, automated interactive audio and videosolutions (IVR and IVVR), and complex live interactions, such as contact centersand audio and video conferencing or video portals. [Dialogic 2014b]

    Om HardwareClient te kunnen compileren is linken met de Dialogic HMP SDK vereist. Dezeis inbegrepen in de installatie van de HMP server, en is vrij te downloaden van de Dialogic

  • 3.2 Aculab 7

    website.

    Bij het starten van de installatie merken we een paar problemen op:

    $ tar -xzvf lnxHMP_4.1_161.tgz$ cd lnxHMP_4.1_161$ su -c "./install.sh"

    The 32-bit libstdc++ package, required by HMP, was not found.Please correct the issue before attempting HMP installation again.

    De software ondersteunt officieel 64-bit besturingssytemen, maar de installer zelf vereist blijk-baar wel nog steeds een 32-bit omgeving. Om verder te gaan installeren we de gevraagde 32-bitcompatibiliteitsbibliotheek:

    su -c "yum install libstdc++.i686"

    De installatie kan nu zoals hiervoor gestart worden. Een volledig verslag van het installatie-proces, met alle in- en output is terug te vinden in bijlage A.1.

    Na het herstarten van de computer kan de Dialogic server aangesproken worden via telnet,op de “CLI1 Agent” poort gekozen in de installer (standaard 23).

    De headers zijn te vinden in /usr/dialogic/inc, de bibliotheken in /usr/dialogic/lib en/usr/dialogic/lib64. In het CMake projectbestand kunnen deze dan als volgt toegevoegdworden:

    INCLUDE_DIRECTORIES (/usr/dialogic/inc)

    if(arch EQUAL 64)LINK_DIRECTORIES (/usr/dialogic/lib64)

    else()LINK_DIRECTORIES (/usr/dialogic/lib)

    endif()

    Codefragment 3.1: CMake - Dialogic headers en bibliotheken

    3.2 Aculab

    Over het bedrijf:

    Aculab provides deployment proven telephony products to the global communi-cations market. [...] With over 35 years of experience in helping to drive ourcustomers’ success, our technology is used to deliver multimodal voice, data andfax solutions for use within IP, PSTN and mobile networks – with performancelevels that are second to none.

    Companies worldwide have adopted our technology for a wide variety of busi-ness critical services and solutions. These can include, for example, high perfor-

    1Command Line Interface

  • 3.2 Aculab 8

    mance inbound/outbound contact centre applications, speech enabled IVR andself-service systems, fax and voice broadcast, conferencing, unified communicati-ons, messaging, and hosted or cloud-based services. [Aculab 2012]

    Net als bij Dialogic hebben we ook van Aculab de headers en bibliotheken nodig om Hardware-Client te kunnen compileren.

    De installatie gebeurt dit keer grafisch, via de Aculab Installation Tool (AIT). Deze installervereist opnieuw een 32-bit omgeving, en de installatie van twee extra bibliotheken die op eenstandaard CentOS 6.5 systeem ontbreken (libXext.so.6 en libz.so.1):

    su -c "yum install libXext.i686 libz.i686"

    Een compleet verslag van het installatieproces is terug te vinden in bijlage A.2.

    In CMake worden de headers en bibliotheken als volgt toegevoegd:

    INCLUDE_DIRECTORIES (/usr/local/aculab/v6/include)

    if(arch EQUAL 64)LINK_DIRECTORIES (/usr/local/aculab/v6/lib64)

    else()LINK_DIRECTORIES (/usr/local/aculab/v6/lib)

    endif()

    Codefragment 3.2: CMake - Aculab headers en bibliotheken

    De Dialogic en Aculab headers beide op deze manier includeren zal voor problemen zorgen.De reden waarom (en de oplossing) wordt besproken in hoofdstuk 11.2.

    3.2.1 libTiNG

    Een onderdeel van de Aculab installatie is de TiNG bibliotheek, die ook in HardwareClientgebruikt wordt. Om deze header te kunnen gebruiken is de declaratie van een preprocessorsymbool nodig. [Aculab 2014]

    In CMake kunnen we hier volgend lijntje voor toevoegen:

    add_definitions(-DTiNGTYPE_LINUX =1)

    Codefragment 3.3: CMake - libTiNG preprocessor symbool

  • MFC 9

    Hoofdstuk 4

    MFC

    4.1 Wat is MFC?

    De Microsoft Foundation Class Library (MFC) werd gëıntroduceerd in de Microsoft C/C++

    7.0 compiler in 1992 (voor de duidelijkheid: deze predateert Microsoft Visual C++ 1.0).[Blaszczak 1997] C++ was in die tijd nog relatief jong als voorkeurstaal voor de ontwikkelingvan commerciële applicaties: van een gestandaardiseerde bibliotheek met datastructuren enalgoritmes was toen zelfs nog geen sprake. MFC vormde hiervoor een oplossing, en encap-suleert bovendien een groot deel van de Windows API in object georiënteerde C++ klassen.[Microsoft 2014d]

    In MFC dus klassen te vinden voor strings, lijsten, mappen, maar ook voor sockets, threads,files, mutexen, ... en GUIs.

    Microsoft staat niet gekend om zijn ondersteuning voor andere platformen, MFC is hier geenuitzondering op en is bijgevolg enkel beschikbaar op Windows.

    4.2 MFC alternatieven

    Een naieve oplossing om de code Linuxcompatibel te maken is de code rechtstreeks aante passen door elke MFC datastructuur te vervangen met de moderne gestandaardiseerdevariant (std::string, std::map, . . . ). Naief, omdat dit in een codeproject van letterlijk meerdan 100 000 lijnen ongelofelijk veel vervangwerk creëert. Het gebruik van de alternatievenkan bovendien erg veel verschillen, waardoor het meer herschrijven dan vervangen wordt.Daarenboven hergebruikt MyForce vaak code, het gedrag in één bestand aanpassen kan duseen effect hebben op meerdere projecten. De verschillende socketimplementaties gebruikt inHardwareClient komen bijvoorbeeld uit twee andere projecten. Enkel de code aanpassen inde bestanden nodig voor HardwareClient zou alle andere projecten kunnen breken. Het kanniet de bedoeling zijn om alle code van MyForce te herschijven, dus is een andere aanpak aande orde.

    Een API-compatibele herimplementatie van de gebruikte MFC klassen en methodes, aan de

  • 4.2 MFC alternatieven 10

    hand van wrappers rond bestaande alternatieven zou betekenen dat de bestaande Hardware-Client code zonder aanpassingen (op een paar defines na) behouden kan worden.

    Een bespreking van de beschikbare alternatieven die hiervoor gebruikt kunnen worden:

    4.2.1 Wine

    Wine1 is een open source herimplementatie van de Win32 API op Unix. Het laat toe om.exe programma’s zonder aanpassingen uit te voeren op Linux, BSD en Mac OS X. Indienje toegang hebt tot de broncode van een Windowsapplicatie, dan kan je deze rechtstreekscompileren naar Linuxcompatibele code door te linken met Winelib. Gezien de broncode vanMFC meegeleverd wordt met Visual Studio, zouden we deze kunnen proberen compilerenmet Wine, om zo een bibliotheek te krijgen die we op Linux via een wrapper zouden kunnengebruiken.

    Er zijn echter meerdere goede redenen om dit niet te doen:

    de legaliteit van het hercompileren en distribueren van deze Microsoft code is onduidelijk[Wine 2014b]

    Wine rendert vensters zoals in Windows 98, wat in een Linux omgeving niet goed past

    alle code zou zo steunen op een automatische vertalingslaag (Wine) waar we geen con-trole over hebben, zonder enige garantie van correctheid of stabiliteit

    het maken van de wrapper en het corrigeren van de headers is een complexe opdracht[Wine 2014a]

    een groot deel van MFC is terug te vinden in de vorm van macro’s in de eigen code,ook deze moeten werkend gemaakt worden [Wine 2014a]

    Het lijkt veiliger om de code zonder deze emulatielaag te doen werken.

    4.2.2 Qt

    Qt2 is een populair platformonafhankelijk applicatie- en GUI-framework voor C++. De eerstepublieke versie kwam uit in 1995. Het won snel aan populariteit als het de facto GUI frame-work voor C++ applicaties op Linux nadat het gebruikt werd om de populaire desktopomge-ving KDE3 mee te bouwen. [Qt 2014] Qt wordt gebruikt door onder andere Nokia, Canonical(Ubuntu), Skype en VideoLan (VLC).

    Qt is beschikbaar onder de GPL (General Public License) en een commerciële licentie. Omeen closed source applicatie te maken met Qt moet een licentie aangekocht worden.

    1http://winehq.org2Uitgesproken als /hkjuqt/ (”cute”) – http://qt-project.com3http://kde.org

  • 4.3 Eigen MFC implementatie 11

    4.2.3 wxWidgets

    wxWidgets4 is een ander gratis en vrij platformonafhankelijk C++ GUI- en applicatieframe-work. Het project werd gestart aan de universiteit van Edinburgh in 1992 omdat de bestaandecommerciële oplossingen te duur waren. [wxWidgets 2014c]

    Dit framework is in gebruik erg gelijkaardig aan MFC. Beide gebruiken een eventgebaseerdestructuur geconfigureerd met macro’s, hebben klassen voor datastructuren, sockets, threads,GUIs, . . . maar in tegenstelling tot MFC compileert code geschreven met wxWidgets op zowelWindows, Mac OS als Linux.

    wxWindows is beschikbaar onder de “wxWindows Library Licence”, in essentie de LGPL(Library General Public License) met een uitzondering die de distributie van afgeleide binariestoelaat. [wxWidgets 2014d] Concreet betekent dit dat commerciële closed source applicatiesdie met wxWidgets zijn geschreven zonder extra licentiekosten kunnen worden verkocht (integenstelling tot Qt).

    4.2.4 Boost

    Boost5 is een populaire verzameling van platformonafhankelijke C++ libraries van hoge kwa-liteit. De code is beschikbaar onder de Boost Software License, een open source licentie diegebruik in propriëtaire software toelaat. Veel van deze Boost libraries vinden uiteindelijk ookhun weg tot inclusie in de officiële C++ standaard. [Schäling 2011]

    Boost maakt onder andere multithreading, asynchrone sockets, unicode en werken met hetbestandssysteem een stuk eenvoudiger en platformonafhankelijk.

    4.2.5 C++11 STD

    De STD (STanDard library) – vaak ook verkeerdelijk STL (Standard Template Library)genoemd6 – biedt een alternatief voor MFC strings en datastructuren. Sinds 2011 bevat deSTD in de nieuwe C++11 standaard ook ondersteuning voor onder andere threads, mutexenen conditions.

    4.3 Eigen MFC implementatie

    wxWidgets lijkt het meeste op MFC, en was daarmee de eerste keuze voor dit project. Hoewelveel klassen bijna 1:1 konden vervangen worden, werd na een aantal weken al snel duidelijk datdit verre van altijd het geval was. Desondanks de gelijkenissen moesten er vaak uitgebreidewrappers geschreven worden om de verschillen in de API te kunnen overbruggen. Bepaaldeonderdelen waren zelfs helemaal onbruikbaar: datastructuren zoals de list en map werktenvolledig anders dan de MFC variant, waardoor ze niet gewrapt konden worden.

    4http://wxwidgets.org; Vroeger stond het project bekend onder de naam wxWindows, maar onder drukvan Microsoft is het in 2004 van naam moeten veranderen. [wxWidgets 2014a]

    5http://boost.org6De STD is gebaseerd op de STL, maar is veel uitgebreider

  • 4.3 Eigen MFC implementatie 12

    In de oorspronkelijke masterproefbeschrijving behoorde het porten van de GUI ook tot deopdracht. Deze vereiste is in overleg met MyForce vrij snel weggevallen eens de preciezegrootte van de opdracht duidelijk werd. De GUI kon al los van de HardwareClient server opeen andere (Windows)computer gestart worden om via het netwerk met de server te verbinden.Het is voor de Linuxversie dus voldoende om dit communicatieprotocol te ondersteunen.

    Veel van de voordelen en redenen om wxWidgets te gebruiken vielen hierdoor weg. Halverwegede stage werd beslist om deze dependency te laten vallen, en werd alles herschreven met nativeLinux functies en C++11 STD.

  • HET PORTING PROCES 13

    Hoofdstuk 5

    Het porting proces

    We hebben voor de code een buildfile geschreven met CMake, en beslist van MFC zo veelmogelijk te vervangen met C++11. Hoe gaan we nu te werk?

    In essentie is het eenvoudig: we starten het compileren door het commando make uit te voeren,of door op de “build”-knop in de IDE te klikken. Vervolgens wachten we tot dit faalt, enzoeken we in de compileerfouten en -waarschuwingen naar de oorzaak.

    Hierin vinden we informatie over welke headers, klassen, functies en macro’s ontbreken. Zo-als reeds vermeld in hoofdstuk 4.2 is deze ontbrekende functionaliteit vervangen met eenplatformonafhankelijk alternatief onbegonnen werk.

    Om te weten wat een ontbrekende functie hoort te doen kunnen we de Microsoft documentatieop de MSDN site raadplegen1. Een alternatief kan dan via Google gevonden worden, of doorgericht te zoeken in de Linux manual pages.

    Wanneer in dit document Linuxfuncties vermeld worden zal dit op de gebruikelijke manier uitde vakliteratuur gebeuren: met tussen haakjes de manual sectie waarin het teruggevonden kanworden. Bij printf(3) zijn we bijvoorbeeld gëınteresseerd in de versie uit de C bibliotheek(man 3 ), niet die uit Bash (man 1 ).

    Het is eenvoudiger om eerst lege wrappers toe te voegen zonder implementatie, en deze pas inte vullen eens het project compileert. Een macro zoals in codefragment 5.1 helpt hierbij.

    #define STUBBED(x) do { \static bool seen_this = false; \if (! seen_this) { \

    seen_this = true; \fprintf(stderr , "STUBBED: %s at %s (%s:%d)\n", \

    x, __FUNCTION__ , __FILE__ , __LINE__ ); \} \

    } while (0)

    Codefragment 5.1: STUBBED macro [Gordon 2014]

    1Tip: de zoekfunctie op de MSDN site lijkt zo goed als nooit de juiste pagina terug te vinden, gebruikdaarom Google met de zoekterm voorafgegaan door “msdn”.

  • 5.1 Overzicht van de uiteindelijke mappenstructuur 14

    5.1 Overzicht van de uiteindelijke mappenstructuur

    Opdat de #include lijnen in de code zouden blijven werken, werden de namen en indelingvan de MFC headers2 gelijk overgenomen.

    afx_linux/|-- afx.cpp|-- afx.h|-- afxio.cpp|-- afxio.h|-- afxmt.cpp|-- afxmt.h|-- afxsock.cpp|-- afxsock.h|-- afxtime.cpp|-- afxtime.h|-- algo/| |-- crc32.cpp| `-- crc32.h|-- datastruct/| |-- StdArray.h| |-- StdList.h| |-- StdMap.h| |-- StdString.cpp| `-- StdString.h`-- win32/

    |-- win32_dll.cpp|-- win32_dll.h|-- win32_io.cpp|-- win32_io.h|-- win32_mt.cpp|-- win32_mt.h|-- win32_str.cpp|-- win32_str.h|-- win32_time.cpp|-- win32_time.h`-- win32_types.h

    De algo, datastruct en win32 mappen volgen geen officiële structuur of naamgeving, en zijnpuur voor de overzichtelijkheid toegevoegd.

    2Alle MFC headers worden geprefixt met “afx”, een restant van het Application Framework X, een oudproject dat uiteindelijk is uitgegroeid tot MFC [Wingo 1996].

  • DATATYPES EN -STRUCTUREN 15

    Hoofdstuk 6

    Datatypes en -structuren

    6.1 Datatypes

    In MFC-code wordt zelden rechtstreeks gebruik gemaakt van native datatypes, zo goed alsalles heeft een eigen typedef die op Linux zal moeten worden toegevoegd.

    Enkele voorbeelden:

    typedef int INT;typedef int BOOL;typedef long LONG;typedef unsigned char BYTE;typedef void* HANDLE;typedef unsigned short WORD ,* LPWORD;

    Codefragment 6.1: Enkele voorbeelden van MFC typedefs

    In het laatste voorbeeld zie je hoe Microsoft Hongaarse notatie1 gebruikt. LPWORD staatvoor Long Pointer to WORD, waarmee hier een 32-bit pointer naar een 16-bit processorwoordbedoeld wordt: deze naamgeving dateert duidelijk uit een tijd waar processorarchitecturennog 16-bit waren.

    6.2 Array, List en Map

    CArray, CList en CMap uit MFC komen grotendeels overeen met de C++11 std::array,std::list en std::unordered map. Het grootste verschil zit in het itereren over elementen,wat hier niet met een klassieke iterator gebeurt, maar (zoals in codefragment 6.2 te zien is)met een POSITION pointer.

    Deze drie datastructuren waren eerder al door een MyForce programmeur herschreven metSTD datastructuren, deze wrappers konden met een minimum aantal aanpassingen op Linux

    1Een naamgevingconventie uitgevonden door Dr. Charles Simonyi, Chief Architect bij Microsoft, en geborenin Hongarije. [Simonyi 1999] Bij deze naamgeving worden prefixen gebruikt die aangeven wat het type is.

  • 6.3 String 16

    hergebruikt worden.

    CList list;...POSITION pos = list.GetHeadPosition ();while (pos){

    int item = list.GetNext(pos);...

    }

    Codefragment 6.2: Voorbeeld van het itereren over een CList met POSITION

    In MFC komen ook een aantal gespecialiseerde array-, list- en mapimplementaties voor, dezewerden met een eenvoudige typedef ondersteund:

    typedef CArray CStringArray;typedef CArray CDWordArray;typedef CList CPtrList;typedef CMap CMapStringToPtr;typedef CMap CMapStringToString;

    Codefragment 6.3: Gespecialiseerde datastructuren

    6.3 String

    In hedendaagse standaard C++ vinden we 2 soorten strings: de klassieke C-string (een pointernaar een char of wchar_t array, afgesloten met ‘\0’), en de std::string (of std::wstring),een wrapper die onder andere het geheugenbeheer op zich neemt. In MFC vinden we in plaatsvan die laatste de gelijkaardige (maar erg verwarrend genaamde) CString.

    typedef char CHAR;typedef char* LPSTR;typedef const char* LPCSTR;

    typedef wchar_t WCHAR;typedef wchar_t* LPWSTR;typedef const wchar_t* LPCWSTR;

    #ifdef UNICODEtypedef WCHAR TCHAR;typedef LPWSTR LPTSTR;typedef LPCWSTR LPCTSTR;

    #elsetypedef CHAR TCHAR;typedef LPSTR LPTSTR;typedef LPCSTR LPCTSTR;

    #endif // unicode

    Codefragment 6.4: Typedefs voor C-strings

  • 6.3 String 17

    De klassieke C-string wordt natuurlijk ook in MFC gebruikt, hiervoor zijn een groot aantaltypedefs voorzien, zoals te zien in codefragment 6.4.

    Interessant is dat MFC naast de voorspelbare CHAR en WCHAR typedefs ook een TCHAR definieert.Wanneer UNICODE gedefinieerd is, worden wide characters gebruikt in plaats van de standaard1 byte grote characters. De prefix t of _t zal in nog veel functie- en methodenamen terugkerenmet dezelfde betekenis.

    Dat “Unicode” volgens Microsoft betekent dat wide characters gebruikt moeten worden iseen misvatting, en creëert bovendien problemen op andere besturingssystemen.

    6.3.1 Unicode

    Om tekst op te slaan op computers worden karakters gemapt op getallen. De klassieke manierwaarop dit gebeurde was de 7-bit ASCII tabel. In deze tabel worden alle tien cijfers, enkelespeciale tekens en de Engelse (accentloze) letters gemapt op de getallen 32-127. De eerste32 getallen komen overeen met onprintbare tekens zoals ‘\0’ en ‘\n’. Gezien een byte 8 bitsgroot is liet dat OEMs toe om met de overblijvende bit naar eigen keuze nog 128 extra tekenstoe te voegen. Hier werden initieel nooit afspraken rond gemaakt, waardoor het delen vanbestanden met speciale tekens voor veel problemen zorgde.

    De ANSI standaard loste dit probleem deels op door de verschillende manieren om de laatste128 tekens in te delen vast te leggen in code pages (zoals latin-1). Bij het openen van eentekst wordt de bijhorende codepage geselecteerd om de correcte vreemde tekens te kunnenweergeven. Dit was natuurlijk erg onhandig, en werkte nog steeds niet voor talen als Chinees,waar er duizenden tekens zijn. [Spolsky 2003]

    In 1988 publiceerde Joseph D. Becker het eerste Unicode voorstel. Hierin werd uitgegaan vanhet idee dat een verdubbeling van de char-grootte naar 16 bits voldoende zou zijn om allemogelijke karakters voor te stellen. [Radzivilovsky, Galka en Novgorodov 2014]

    In Unicode worden karakters gemapt op “theoretische” code points, die losstaan van hoe zeop de schijf worden opgeslagen. Dit wordt genoteerd als “U+” (voor Unicode), gevolgd doorhexadecimale cijfers. De letter A wordt bijvoorbeeld voorgesteld door U+0041.

    De string “Hallo” komt overeen met vijf code points: U+0048 U+0061 U+006C U+006CU+006F.

    Om dit op te slaan legt Unicode geen specifieke encodering op: er zijn meerdere mogelijkheden,de meest gekende/gebruikte zijn UCS-2, UTF-16, UTF-32 en UTF-8.

    In de eerste versie van de standaard, gepubliceerd in 1991, werd verondersteld dat een verdub-beling van de 8 bits char-grootte naar 16 bits voldoende zou zijn. Deze 2 bytes grote charsworden wide characters genoemd, en worden onder Windows opgeslagen in het wchar t type.De 2 bytes die in deze UCS-2 encodering gebruikt werden (16 bits, of 65536 mogelijke codepoints) bleken echter al snel niet genoeg om alle mogelijke code points in op te slaan2.

    2Op het moment van schrijven bevat de huidige versie van Unicode (7.0) al zo’n 252603 toegewezen codepoints, waarvan 112804 grafische tekens zijn. [West 2014]

  • 6.3 String 18

    UTF-32 verdubbelt om deze reden de wide character grootte nog eens naar 4 bytes. Ditis wat op Unix verstaan wordt onder wchar t. Merk op dat wchar t dus niet zomaar kanuitgewisseld worden tussen Windows (2 bytes) en Unix (4 bytes)!

    4 bytes per code point is bijzonder geheugenintensief en in de meeste gevallen onnodig groot.In 1996 ontstond hierdoor UTF-16, waarbij de code points een variabele grootte kregen.Standaard is dit nog steeds een wide character van 2 bytes groot, maar indien nodig kan eenextra wide character gebruikt worden, zodat een code point uiteindelijk 4 bytes kan innemen.UTF-16 wordt vooral op Windows veel gebruikt.

    Op Unix koos men voor een andere oplossing: aan het einde van het jaar 1992 ontwikkeldenKen Thompson en Rob Pike uit ongenoegen met de bestaande Unicode encoderingen UTF-8.UTF-8 gebruikt zoals UTF-16 ook code points van variabele grootte, maar slaat die op inchars in plaats van wchars. UTF-8 is hierdoor byte geöriënteerd en verschilt in tegenstellingtot UTF-16 en UTF-32 niet in gebruik op big of little endian systemen. [Pike 2003]

    Een groot voordeel van deze encodering is dat bij Engelse tekens de eerste bits altijd 0zijn en deze kunnen worden weggelaten; ze nemen hierdoor maar één byte in. Een Engelsetekst geëncodeerd in ASCII is hierdoor identiek aan diezelfde tekst geëncodeerd in UTF-8!Hierdoor konden Unixsystemen op een vrij eenvoudige manier Unicode adopteren, zondercompatibiliteit met oudere ASCII scripts of configuratiebestanden te verliezen. UTF-8 is watop het web en in de Unixwereld gebruikt wordt.

    In sommige gevallen kan de UTF-8 encodering ook iets groter dan UTF-16 uitvallen.

    Figuur 6.1: Het verschil tussen de UTF-8 en UTF-16 representatie van ‘AC’ [wxWidgets 2014b]

    Wat we hieruit leren is dat Windowscode die gebruik maakt van UTF-16 wide characters nietgebruikt kan worden op Linux: zowel de grootte van de datastructuren als de encodering vande data verschillen. In Windows worden native system calls voorzien in een variant voor 8-bitASCII chars en een variant voor UTF-16 wchar_ts. Op Linux vind je voornamelijk systemcalls voor UTF-8 chars, plus enkele voor UTF-32 wchar_ts.

  • 6.3 String 19

    Bij het porten moet bijgevolg gezorgd worden dat op Linux en Windows andere datatypesgebruikt worden. In de MFC code wordt gelukkig voornamelijk gebruik gemaakt van deLPCTSTR typedef. Op Linux laten we deze naar const char* wijzen in plaats van naarconst wchar t*.

    6.3.2 Implementatie CString

    CString kan worden gëımplementeerd door std::string over te erven en extra functies toete voegen. Overerving negeert constructors en operator overloads, die moeten dus allemaalopnieuw gedefinieerd worden.

    CString () : std:: string () {}CString(const CString &str) : std:: string(str) {}CString(const std:: string &str) : std:: string(str) {}CString(const char* cstr) : std:: string(cstr) {}

    using std:: string :: operator +=;

    CString& operator =( CString& str){

    return this ->operator =( static_cast (str));}CString& operator =(const char* str){

    return this ->operator =(str);}CString& operator =(const std:: string& str){

    return this ->operator =(str);}

    Codefragment 6.5: Constructors en operator overloads van CString

    Ook non-member function overloads zoals operator== moeten opnieuw buiten de klasse ge-definieerd worden:

    bool operator == (const CString& a, const CString& b){

    return (static_cast (a) == static_cast (b));}bool operator == (const char* a, const CString& b){

    return (a == static_cast (b));}bool operator == (const CString& a, const char* b){

    return (static_cast (a) == b);}

    Codefragment 6.6: CString non-member operator overload

  • 6.3 String 20

    Dit moet natuurlijk ook voor alle andere relationele operators (!=, =) gebeuren.Daar komt dan nog ondersteuning voor een cast naar const char* bij en een hoop eenvoudigewrapper methodes:

    operator const char *() const{

    return c_str ();}

    const char& GetAt(size_t n) const{

    return at(n);}

    size_t GetLength () const{

    return length ();}

    BOOL IsEmpty () const{

    return empty() ? TRUE : FALSE;}

    Codefragment 6.7: Een selectie van enkele CString methodes

    Sommige methodes vereisen iets meer werk, zoals onderstaande Format. Deze gebruikt de-zelfde formaatbeschrijving van printf(3), maar slaat het resultaat rechtstreeks in de CStringzelf op. We kunnen hiervoor de vsnprintf(3) variant gebruiken, die zijn resultaat naar eenbuffer in plaats van stdout schrijft.

    void Format(LPCTSTR fmt , ...){

    va_list args;va_start(args , fmt);

    // determine buffer size (use _vscprintf on Windows !)int bufsize = vsnprintf(NULL , 0, fmt , args) + 1; //+1 for '\0'

    //make bufferchar* buf = (char*) malloc(bufsize*sizeof(char ));

    //reset arglistva_end(args);va_start(args , fmt);

    // formatvsnprintf(buf , bufsize , fmt , args);

    //copy buffer to stringthis ->operator =(buf);

  • 6.3 String 21

    //free buffer and arglistfree(buf);va_end(args);

    }

    Codefragment 6.8: CString Format

    6.3.3 Extra stringfuncties

    Naast CString methodes moeten ook een aantal stringgerelateerde functies gëımplementeerdworden. Gezien we op Linux mogen veronderstellen dat alle tekst UTF-8 is, moeten wevoor prefix-“ t” functies (besproken in de inleiding van dit hoofdstuk) zoals ttol geen widecharacter variant voorzien.

    long _ttol(const CString& str){

    return atol(str.c_str ());}

    Codefragment 6.9: ttol parst een long naar een CString

    vsntprintf s heeft een gelijkaardig gedrag als de standaard vsnprintf(3), maar heeft nogeen aantal extra veiligheidscontroles ingebouwd. Naast de buffergrootte wordt bijvoorbeeldnog een extra argument voor het aantal te kopiëren karakters meegegeven.

    Eenzelfde vergelijking kan gemaakt worden voor strncpy s en strncpy(3), en memcpy s enmemcpy(3).

    int _vsnprintf_s(char *buffer , size_t sizeOfBuffer , size_t count ,const char *format , va_list argptr)

    {if (buffer == 0 || format == 0 || count sizeOfBuffer){

    buffer [0] = '\0';return -1;

    }else{

    sizeOfBuffer = count +1;}

    }

    return vsnprintf(buffer , sizeOfBuffer , format , argptr );}

  • 6.3 String 22

    errno_t strncpy_s(char *strDest , size_t numberOfElements ,const char *strSource , size_t count)

    {if (strDest == NULL){

    return EINVAL;}if (strSource == NULL){

    strDest [0] = 0;return EINVAL;

    }if (count == 0 || numberOfElements == 0){

    return EINVAL;}if (count

  • 6.3 String 23

    return 0;}

    Codefragment 6.10: implementatie vsnprintf s, strncpy s en memcpy s

    Het gedrag van al deze implementaties is gebaseerd op de officiële MSDN documentatie.

    6.3.4 Unicode in praktijk

    Gezien de implementatie van CString nu intern UTF-8 gebruikt, is een andere klasse nodigvoor het werken met UTF-16 strings. Omdat een wchar op Linux 4 bytes groot is in plaatsvan 2 bytes, is een std::wstring of een std::vector niet correct.

    In C++11 is de ambigüıteit rondom de grootte van wchar opgelost door de introductie vantwee nieuwe types: char16 t en char32 t. De conversie tussen UTF-8 en UTF-16 maakt ookdeel uit van de C++11 standaard (in de vorm van std::codecvt utf8 utf16), maar was opmoment van schrijven nog niet beschikbaar in GCC.

    De compacte UTF-CPP bibliotheek3 werd als alternatief gekozen. Deze gebruikt unsignedshort – altijd 2 bytes groot – als WCHAR type, en std::vector als UTF-16 string.Gebaseerd hierop, werd een CWString (geen echte MFC klasse) gemaakt:

    typename unsigned short WCHAR;class CWString : public std::vector {public:

    CWString () {}CWString(LPCWSTR str);

    CWString& operator =(std::vector & str);

    operator LPCWSTR () const;};

    Codefragment 6.11: CWString een UTF-16 CString klasse

    In HardwareClient werd al onderscheid gemaakt tussen WCHAR (UTF-16) strings en CHAR(ANSI) strings. Waar nodig worden ze omgezet met de functies CW2A (Convert Wide toANSI) en CA2W (Convert ANSI to Wide). In de Linux implementatie bevat de CHAR stringUTF-8, en kunnen de functies CW2A en CA2W dus gebruikt worden om de conversie tussenUTF-8 en UTF-16 uit te voeren.

    const char* CW2A(const CString& str){

    //our CString isn't wide , and is already UTF -8, so do nothingreturn str.c_str ();

    }

    3http://utfcpp.sourceforge.net/

  • 6.3 String 24

    // LPCSTR (Long Pointer Const STRing ): const char*CWString CA2W(LPCSTR str){

    std:: string s_utf8(str);CWString s_utf16;

    utf8:: utf8to16(s_utf8.begin(),utf8:: find_invalid(s_utf8.begin(),

    s_utf8.end()),back_inserter(s_utf16 ));

    return s_utf16;}

    // LPCWSTR (Long Pointer Const Wide STRing ): const WCHAR*CString CW2A(LPCWSTR str){

    LPCWSTR end = str;while (*end != 0)

    end++;

    CString out8;

    utf8:: utf16to8(str , end+1, back_inserter(out8 ));

    return out8;}

    Codefragment 6.12: Conversiefuncties tussen UTF-8 en UTF-16

    6.3.4.1 Vergelijken van tekens

    Een extra complicatie doet zich in het volgende geval voor:

    CString s = _T("caf é");assert (s.GetLength () == 4);assert (s.at(3) == ' é');

    Codefragment 6.13: Problemen bij multi byte strings

    Onder Windows zullen deze asserts slagen, want CString gebruikt intern wide characters.Onze Linux implementatie gebruikt multi byte characters, s.GetLength() zal 5 teruggeven,en s.at(3) een onprintbaar teken, omdat char 3 en 4 enkel samen ‘é’ vormen. De code zalop Linux zelfs niet compileren, omdat ‘é’ niet in een const char past.

    In codefragment 6.14 is te zien hoe we dit probleem aanpakken. Om in de UTF-8 versie eenteken te kunnen vergelijken wordt het eerst omgezet naar een wide character. De volledigestring omzetten (met bijvoorbeeld std::mbstowcs) en pas daarna de tekens één voor éénvergelijken vereist het aanmaken van een buffer (van een op voorhand ongekende grootte)en dat 2 keer door de string moet worden gelopen. Deze verspilling van geheugen en tijd iste vermijden door maar 1 teken (code point) per keer om te zetten naar een wide character

  • 6.3 String 25

    (zoals hier met behulp van std::mbrtowc).

    #ifdef _WIN32CString strCopy = strString;const TCHAR* ch = strCopy.GetBuffer ();while ((*ch) != _T('\0')){

    //we only accept space and printable charactersif (! _istspace (*ch) && !_istprint (*ch) &&

    *ch != _T('\b') && *ch != _T('\f') &&*ch != _T('\a') && *ch != _T('\u20ac')) //u+20ac:EUR

    {return false;

    }ch++;

    }return true;

    #else// We have to do this a bit different because we use UTF -8const char* mbStr = strString.GetBuffer ();

    std:: locale l(""); // defaults to system localestd:: mbstate_t state = std:: mbstate_t ();const char* end = mbStr + std:: strlen(mbStr);int len;wchar_t wc;

    while ((len = std:: mbrtowc (&wc, mbStr , end -mbStr , &state)) > 0){

    if (!std:: isspace(wc,l) && !std:: isprint(wc,l) &&wc != L'\b' && wc != L'\f' &&wc != L'\a' && wc != L'\u20ac') //u+20ac:EUR

    {return false;

    }mbStr += len;

    }return len == 0; //else the parsing failed , so def. invalid

    #endif

    Codefragment 6.14: Tekens vergelijken met UTF-8 aan de hand van std::mbrtowc

  • TIME 26

    Hoofdstuk 7

    Time

    7.1 GetTickCount

    GetTickCount is een Windowsfunctie die de milliseconden sinds het opstarten van het sys-teem teruggeeft. Het wordt voornamelijk gebruikt voor benchmarking (het verschil tussen 2oproepen).

    Het aantal milliseconden sinds het opstarten kunnen we op Linux niet opvragen, maar geziendit enkel gebruikt wordt om het verschil te bepalen, zouden we hier ook de systeemklok voorkunnen gebruiken. De standaard gettimeofday(2) gebruiken we hier beter niet, gezien dieklok bëınvloed wordt door het Network Time Protocol (NTP). Als de klok tussen 2 aanroepenzou synchroniseren zou dit de meting (het verschil) ongeldig maken. [Habets 2010]

    long GetTickCount (){#ifdef CLOCK_MONOTONIC_COARSE //check if available

    struct timespec ts;if (! clock_gettime(CLOCK_MONOTONIC_COARSE , &ts)) {

    return (ts.tv_sec * 1000) + (ts.tv_nsec / 1000000);}

    #endif// fallbackstruct timeval tv;gettimeofday (&tv, NULL);return (ts.tv_sec * 1000) + (tv.tv_usec / 1000);

    }

    Codefragment 7.1: Implementatie van GetTickCount

    Een MONOTONIC klok geeft het aantal (nano)seconden sinds een onbepaald tijdstip, zondersprongen in de tijd bij NTP synchronisaties. De man page van de clock gettime(2) Unixfunc-tie vermeldt twee interessante Linuxspecifieke addities: CLOCK MONOTONIC RAW (vanaf Linux2.6. 28) en de snellere, maar minder precieze CLOCK MONOTONIC COARSE (vanaf Linux 2.6.32).Gezien GetTickCount als eenheid milliseconden in plaats van nanoseconden gebruikt, volstaatde verminderde precisie van de COARSE variant voor onze implementatie. [RedHat 2014]

  • 7.2 CTime 27

    Wat niet direct duidelijk is in de documentatie is dat de inhoud van de timespec of timevalstruct samengeteld moet worden om de correcte tijd te krijgen. tv_nsec is geen precie-zere versie van tv_sec, maar het aantal nanoseconden die bij tv_sec moeten worden toege-voegd.

    7.2 CTime

    CTime is een MFC datastructuur voor kalendertijdstippen. Er zijn een aantal belangrijkeverschillen met de Unixfuncties, wat al direct merkbaar is in de implementatie van de con-structor:

    CTime::CTime(int year , int month , int day ,int hour , int minute , int second)

    {struct tm t;

    t.tm_year = year -1900;t.tm_mon = month;t.tm_mday = day;t.tm_hour = hour;t.tm_min = minute;t.tm_sec = second;

    t.tm_isdst = -1; //auto

    time_t time = mktime (&t);if (time != -1)

    m_time = time;}

    Codefragment 7.2: CTime constructor

    Op te merken: tm year bevat het aantal jaren sinds 1900, tm isdst bepaalt of Daylight SavingTime (DST) in effect is. Positief voor zomertijd, 0 voor wintertijd. Bij een negatieve waardewordt dit automatisch bepaald aam de hand van de opgegeven datum.

    De klasse bevat verder enkele voordehandliggende getters en operator overloads. Te vermeldenwaard zijn de volgende methodes:

    CTime CTime:: operator+ (const CTimeSpan& span){

    struct tm t = *localtime (& m_time );t.tm_mday += span.m_days;t.tm_hour += span.m_hours;t.tm_min += span.m_mins;t.tm_sec += span.m_secs;t.tm_isdst = -1; //auto

    return CTime(mktime (&t));}

  • 7.3 GetSystemTime en GetLocalTime 28

    int CTime:: GetYear (){

    return localtime (& m_time)->tm_year + 1900;}

    int CTime:: GetDayOfWeek (){

    return localtime (& m_time)->tm_wday + 1;}

    CString CTime:: Format(const char* fmt){

    char buf [1024];

    if (std:: strftime(buf , sizeof(buf), fmt , localtime (& m_time )) == 0){

    // either strlen is actually supposed to be 0, or//the buffer was too small. I can't predict this ,//so in case of bugs: make the buffer bigger!

    buf[0] = '\0';}

    return CString(buf);}

    Codefragment 7.3: Enkele CTime methodes

    Bij het optellen met een timespan (CTimeSpan is een klasse met een int voor elke tijdgroot-heid) moeten we geen rekening houden met het “overflowen” (90 seconden is 1 minuut en 30seconden), mktime(3) zal dit zelf doen. We mogen wel niet vergeten van isdst terug op autote zetten, want dit kan door het optellen veranderen.

    Om het jaar te krijgen moeten we natuurlijk weer 1900 bijtellen. Op Linux is maandag-zondag 0-6, op Windows is het 1-7. De formattering vlaggen komen na het vergelijken vande documentatie gelukkig volledig overeen.

    7.3 GetSystemTime en GetLocalTime

    GetSystemTime en GetLocalTime zijn twee native win32 functies die de tijd teruggeven in eenSYSTEMTIME struct.

    typedef struct _SYSTEMTIME {WORD wYear;WORD wMonth;WORD wDayOfWeek;WORD wDay;WORD wHour;WORD wMinute;

  • 7.4 COleDateTime en COleDateTimeSpan 29

    WORD wSecond;WORD wMilliseconds;

    } SYSTEMTIME , *LPSYSTEMTIME;

    Codefragment 7.4: de SYSTEMTIME struct

    GetLocalTime geeft zoals de naam doet vermoeden de lokale tijd, GetSystemTime geeft de tijdin UTC (zie codefragment 7.5).

    void GetSystemTime(LPSYSTEMTIME lpSystemTime){

    struct timeval tod;struct tm lintime;

    gettimeofday (&tod , NULL);gmtime_r (&(tod.tv_sec), &lintime ); //UTC

    lpSystemTime ->wYear = static_cast ( lintime.tm_year );lpSystemTime ->wMonth = static_cast ( lintime.tm_mon );lpSystemTime ->wDayOfWeek = static_cast ( lintime.tm_wday );lpSystemTime ->wDay = static_cast ( lintime.tm_mday );lpSystemTime ->wHour = static_cast ( lintime.tm_hour );lpSystemTime ->wMinute = static_cast ( lintime.tm_min );lpSystemTime ->wSecond = static_cast ( lintime.tm_sec );lpSystemTime ->wMilliseconds = static_cast (tod.tv_usec /1000);

    }

    Codefragment 7.5: Implementatie van GetSystemTime

    GetLocalTime wordt op dezelfde manier geimplementeerd, maar gebruikt localtime r(3) inplaats van gmtime r(3).

    7.4 COleDateTime en COleDateTimeSpan

    Een andere MFC tijdklasse is COleDateTime. In HardwareClient worden hier niet veel me-thodes van gebruikt, dus de implementatie ging vrij snel. C++11 introduceerde een nieuwegestandaardiseerde manier om in C++ met tijd te werken: std::chrono. COleDateTime enCOleDateTimeSpan werden hiermee gëımplementeerd.

    class COleDateTime{private:

    std:: chrono :: system_clock :: time_point m_dt;public:

    COleDateTime (){}

    COleDateTime(const std:: chrono :: system_clock :: time_point& tp){

    m_dt = tp;}

  • 7.4 COleDateTime en COleDateTimeSpan 30

    COleDateTime(const COleDateTime& dt);{

    m_dt = dt.m_dt;}static COleDateTime GetCurrentTime (){

    return COleDateTime(std:: chrono :: system_clock ::now ());}CString Format () const{

    std:: time_t tim = std:: chrono :: system_clock :: to_time_t(m_dt);return CString(ctime(&tim));

    }};

    class COleDateTimeSpan{public:

    enum DateTimeSpanStatus { valid , invalid , null };private:

    std:: chrono :: seconds timeSpan;DateTimeSpanStatus m_status;

    public:COleDateTimeSpan () { }COleDateTimeSpan(long lDays , int nHours , int nMins , int nSecs){

    timeSpan = (std:: chrono ::hours (24* lDays + nHours)+ std:: chrono :: minutes(nMins)+ std:: chrono :: seconds(nSecs ));

    }

    DateTimeSpanStatus GetStatus () const{

    return m_status;}void SetStatus(DateTimeSpanStatus status){

    m_status = status;}

    };

    Codefragment 7.6: COleDateTime en COleDateTimeSpan implementatie

  • IO 31

    Hoofdstuk 8

    IO

    8.1 Sockets

    8.1.1 TCP keepalive

    In IPv4 netwerken wordt gewoonlijk gebruik gemaakt van NAT om meerdere apparaten tekunnen aansluiten op één netwerkverbinding. De router houdt hiertoe een lijst bij van alleclient-server verbindingen voor elk apparaat. Door de fysieke beperkingen van vele routers ishet aantal verbindingen dat ze zo in het geheugen kunnen houden gelimiteerd. Vele imple-mentaties “vergeten” hierdoor oudere inactieve (maar open) verbindingen ten voordele vannieuwe actieve verbindingen. [Mueller 2011] Het vervelende aan deze implementatie is dat deverbinding niet “officieel” verbroken wordt, en client noch server hier dus van op de hoogtegebracht worden. Wanneer de verbinding uit onwetendheid toch gebruikt wordt, zullen allepakketten verloren gaan. Het programma kan dan pas na een (of meerdere) time-out periodesbeseffen dat de verbinding verbroken is.

    Sommige routers laten inactieve verbindingen al na 5 minuten vallen, een probleem dat ookMyForce ondervonden heeft. Om dit op te lossen gebruiken ze TCP keep-alive pakketten.Deze periodiek verstuurde berichten bevatten geen data en vragen enkel om een bevestigingvan ontvangen. [Guha 2008] Verbroken verbindingen kunnen zo snel opgespoord worden, enhet houdt de verbinding bovenaan de actieve lijst in de router.

    struct tcp_keepalive sKeepAliveSettings = {0};DWORD dwResultSize = 0L;sKeepAliveSettings.onoff = 1;sKeepAliveSettings.keepalivetime = 60000; //in mssKeepAliveSettings.keepaliveinterval = 1000; //in msWSAIoctl(m_hSocket , SIO_KEEPALIVE_VALS , &sKeepAliveSettings ,

    sizeof(sKeepAliveSettings), NULL , 0, &dwResultSize , NULL , NULL);

    Codefragment 8.1: TCP keep-alive activeren met WSA voor m hSocket

    TCP keep-alive wordt door de grote pakketvloed niet universeel aanvaard en staat daaromstandaard uit. [Internet Engineering Task Force 1989] Met de Windows Sockets API (WSA)

  • 8.1 Sockets 32

    activeer en configureer je het zoals in codefragment 8.1.

    TCP keep-alive kan op Linux zoals elke andere socket optie geactiveerd worden met hetcommando setsockopt(2) (level SOL SOCKET):

    int keepalive = 1; // 1 -> enabledsetsockopt(s, SOL_SOCKET , SO_KEEPALIVE , &( keepalive), sizeof(int ));

    Codefragment 8.2: TCP keep-alive activeren op Linux voor socket s

    Voor verdere configuratie zoals in het WSA voorbeeld moeten we naar de man page vantcp(7) kijken, we vinden daar deze globale sysctl1 variabelen:

    tcp_keepalive_intvl (integer; default: 75; since Linux 2.4)The number of seconds between TCP keep-alive probes.

    tcp_keepalive_probes (integer; default: 9; since Linux 2.2)The maximum number of TCP keep-alive probes to send before giv-ing up and killing the connection if no response is obtainedfrom the other end.

    tcp_keepalive_time (integer; default: 7200; since Linux 2.2)The number of seconds a connection needs to be idle before TCPbegins sending out keep-alive probes. Keep-alives are only sentwhen the SO_KEEPALIVE socket option is enabled. The defaultvalue is 7200 seconds (2 hours). An idle connection is termi-nated after approximately an additional 11 minutes (9 probes aninterval of 75 seconds apart) when keep-alive is enabled.

    Note that underlying connection tracking mechanisms and applica-tion timeouts may be much shorter.

    Deze variabelen aanpassen zou de configuratie globaal maken, wat duidelijk een stap te veris. Wat verder in de man page vinden we dat deze configuratie ook per socket kan wordentoegepast door opnieuw setsockopt(2) te gebruiken, maar dit keer met level SOL TCP2 inplaats van SOL SOCKET. [Busatto 2007]

    TCP KEEPCNT configureert tcp keepalive probesTCP KEEPIDLE configureert tcp keepalive timeTCP KEEPINTVL configureert tcp keepalive intvl

    In de Windowscode kwamen maar twee configuratie opties voor: tcp keepalive::keepalive-time en tcp keepalive::keepaliveinterval. Deze komen respectievelijk overeen met tcpkeepalive time (TCP KEEPIDLE) en tcp keepalive intvl (TCP KEEPINTVL), op één verschil na:op Windows worden deze uitgedrukt in milliseconden, op Linux in seconden. De ontbrekendeoptie voor tcp keepalive probes (TCP KEEPCNT) is op Windows niet configureerbaar en heeftals waarde (sinds Windows Vista) altijd 10. [Microsoft 2014f]

    1Het sysctl commando dient om at runtime kernel parameters aan te passen.2Hier is een #include voor nodig.

  • 8.1 Sockets 33

    #include #include

    #define SIO_KEEPALIVE_VALS 4struct tcp_keepalive {

    int onoff;int keepalivetime;int keepaliveinterval;

    };

    int WSAIoctl(SOCKET s, DWORD dwIoControlCode , LPVOID lpvInBuffer ,DWORD cbInBuffer , LPVOID lpvOutBuffer , DWORD cbOutBuffer ,LPDWORD lpcbBytesReturned , void *lpOverlapped ,void *lpCompletionRoutine)

    {assert(dwIoControlCode == SIO_KEEPALIVE_VALS ); //only implemented option

    tcp_keepalive *keepalive = static_cast ( lpvInBuffer );

    if (0 != setsockopt(s, SOL_SOCKET , SO_KEEPALIVE , &(keepalive ->onoff),sizeof(keepalive ->onoff )))

    {fprintf(stderr , "Could not enable TCP keep -alive\n");return 1;

    }if (0 != setsockopt(s, SOL_TCP , TCP_KEEPIDLE ,

    &(keepalive ->keepalivetime),sizeof(keepalive ->keepalivetime )))

    {fprintf(stderr , "Could not set tcp_keepalive_time\n");return 2;

    }if (0 != setsockopt(s, SOL_TCP , TCP_KEEPINTVL ,

    &(keepalive ->keepaliveinterval),sizeof(keepalive ->keepaliveinterval )))

    {fprintf(stderr , "Could not set tcp_keepalive_time\n");return 3;

    }

    // tcp_keepalive_probes = TCP_KEEPCNT (default :9)int probes = 10; // default on Windowsif (0 != setsockopt(s, SOL_TCP , TCP_KEEPCNT , &( probes), sizeof(probes ))){

    fprintf(stderr , "Could not set tcp_keepalive_time\n");return 4;

    }

    return 0;}

    Codefragment 8.3: De resulterende TCP keep-alive wrapper

  • 8.1 Sockets 34

    Tabel 8.1: Een overzicht van hoe de verschillende TCP keep-alive parameters overeenkomen

    sysctl setsockopt Windows WSA

    tcp keepalive time TCP KEEPIDLE tcp keepalive::keepalivetime/1000tcp keepalive intvl TCP KEEPINTVL tcp keepalive::keepaliveinterval/1000tcp keepalive probes TCP KEEPCNT (altijd 10)

    8.1.2 Asynchrone sockets

    MFC maakt werken met asynchrone sockets op Windows heel eenvoudig door een CAsync-Socket klasse aan te bieden met overschrijfbare callback methodes (OnConnect, OnAccept,OnReceive, ...). Het framework zorgt zelf voor de eventloop die dit mogelijk maakt.

    Op Linux zijn sockets synchroon en is er geen standaard asynchrone manier van werken, erzijn meerdere opties:

    8.1.2.1 select(2)

    Een oplossing besproken in Veerle Ongenae (2014). Computernetwerken IV (cursus) isselect(2), deze functie heeft echter enkele belangrijke nadelen. File descriptors (FD) wordenhier bijgehouden in een bitmap, waardoor bij het aanmaken van de datastructuur de maxi-mum FD moet worden opgegeven. Wanneer duizenden connecties zullen verwerkt worden, ishet onmogelijk te voorspellen hoeveel file descriptors nodig zullen zijn, laat staan welke maxi-mumwaarde deze zullen krijgen. [Silva 2009] Select(2) is verder gelimiteerd tot FD SETSIZE(gewoonlijk 1024) file descriptors per proces, en is (vooral bij inactieve verbindingen) aan-toonbaar trager dan de alternatieven. [Gammo e.a. 2004]

    8.1.2.2 poll(2)

    Poll(2) laat de restrictie op het aantal file descriptors vallen, maar wint verder niet veelin snelheid. Het verwerken van file descriptors gebeurt zoals bij select nog steeds lineair,waardoor het nagaan op activiteit bij een grote hoeveelheid verbindingen erg traag wordt eneen grote bottleneck vormt. [Stenberg 2014]

    8.1.2.3 epoll(7)

    Geen enkele van de hierboven vernoemde beperkingen komen voor in de Linux specifiekeepoll(7) interface. Hier wordt de array met file descriptors beheerd vanuit de kernel inplaats van in userspace.

    Een eigen implementatie van de MFC CAsyncSocket met dit systeem is doenbaar3, maarvereist veel tijd om grondig te kunnen testen, tijd die gezien de grote scope van het projectniet beschikbaar was.

    3Voor een duidelijk uitgewerkt voorbeeld, zie Mukund Sivaraman (2011). How to use epoll? A completeexample in C. url: https://banu.com/blog/2/how-to-use-epoll-a-complete-example-in-c/

    https://banu.com/blog/2/how-to-use-epoll-a-complete-example-in-c/

  • 8.2 Files 35

    Vele anderen hebben natuurlijk al over deze problematiek nagedacht, er zijn vele bibliothekenmet kant en klare implementaties te vinden:

    8.1.2.4 libevent, libev, libuv

    De populairste platformonafhankelijke libraries die event driven asynchrone sockets aanbiedenzijn libevent4, libev5 en het recentere libuv6, dat gebruikt wordt (en effectief gecreëerd is voor)node.js. Op Linux werden ze allemaal gëımplementeerd met epoll(7).

    8.1.2.5 (C++11 / boost) Asio

    C++11 heeft crossplatform development al veel vereenvoudigd door onder andere het gebruikvan threads in de standaard op te nemen. File IO werd al lang ondersteund, maar socketsnog altijd niet.

    Boost7 heeft wel een implementatie voor asynchrone sockets, genaamd Asio. Deze bibliotheekis intussen ook beschikbaar in een C++11 versie, zodat deze onafhankelijk van andere Boostbibliotheken kan worden gebruikt. Deze versie is al in meerdere revisies voorgesteld vooropname in de C++ standaard. [Kohlhoff 2006; Kohlhoff 2007; Kohlhoff 2012]

    Gezien het gebruik van deze library erg veel lijkt op andere delen van de STD, en de kansop inclusie in de standaard groot is, vormt deze bibliotheek een goede keuze. Vooraleerasynchrone socketwrappers gëımplementeerd kunnen worden is een event loop nodig, en datdeel van multithreading werd niet tijdig afgewerkt. De socketimplementatie is daardoor ookuitgebleven.

    8.2 Files

    Veel IO C-functies zijn gestandaardiseerd, maar hebben op Windows een variant waarvoor nogondersteuning toegevoegd moet worden. taccess, tremove en tfopen s komen bijvoorbeeldovereen met access(2), remove(3) en fopen(3).

    Sommige functies hebben een andere naam: CreateDirectory werd vervangen met mkdir(2),GetCurrentDirectory met getcwd(3), CreateFile met open(2) (en de O CREATE vlag), Write-File met write(2), enzovoort.

    4http://libevent.org5http://software.schmorp.de/pkg/libev.html6https://github.com/joyent/libuv7Boost is een collectie van open source C++ bibliotheken die de huidige standaard uitbreiden. Ze zijn

    compatibel met de STD, en hebben een gelijkaardige syntax. In het Technical Report (TR1) en de uiteindelijkeC++11 standaard zijn al 10 Boost bibliotheken opgenomen. [Schäling 2011] Voor TR2 wordt gekeken naarandere Boost bibliotheken voor onder andere Unicode en networking. [C++ Standards Committee 2005]

  • 8.2 Files 36

    8.2.1 CFile

    MFC heeft ook een eigen systeem om met bestanden te werken: CFile. Dit doet op veelpunten denken aan de gestandaardiseerde C FILE api, op een aantal verschillen na.

    class CFile{private:

    std:: string m_filename;FILE* m_file;

    public:enum OpenFlags {

    modeRead = (int) 0x00000 ,modeWrite = (int) 0x00001 ,modeReadWrite = (int) 0x00002 ,shareDenyWrite = (int) 0x00020 ,shareDenyRead = (int) 0x00030 ,shareDenyNone = (int) 0x00040 ,modeCreate = (int) 0x01000};

    CFile (): m_file(NULL) {}virtual ˜CFile ();

    enum Attribute {normal = 0x000 ,readOnly = 0x001

    };

    virtual BOOL Open(LPCTSTR lpszFileName , UINT nOpenFlags ,CFileException* pError = NULL);

    virtual ULONGLONG GetLength () const;virtual UINT Read(void* lpBuf , UINT nCount );virtual void Write(const void* lpBuf , UINT nCount );virtual void Flush ();virtual void Close ();BOOL GetStatus(CFileStatus& rStatus) const;

    };

    struct CFileStatus{

    CTime m_ctime; // creation date/time of fileCTime m_mtime; // last modification date/time of fileCTime m_atime; // last access date/time of fileULONGLONG m_size; // logical size of file in bytesBYTE m_attribute; // logical OR of CFile:: Attribute enum valuesTCHAR m_szFullName[MAX_PATH ]; // absolute path name

    };

    Codefragment 8.4: de header van CFile en CFileStatus

  • 8.2 Files 37

    In de declaratie van CFile in codefragment 8.4 zijn meteen de Read, Write, Flush en Closemethodes te herkennen. GetStatus en de bijhorende CFileStatus struct doen dan weer denkenaan de Unix stat(2) functie en gelijknamige struct. Het enige grote verschil met de C FILEAPI zit bij het opgeven van de mode bij het openen: FILE neemt een string, CFile neemteen int opgebouwd uit bitvlaggen.

    Kijken we naar de native Linux file IO API, dan zien we dat de open(2) functie net als CFileeen int vlag gebruikt. Maar deze manier van werken is dan weer niet gebufferd zoals FILE,en bestaat er geen concept van flushen. Dit betekent dat deze API veel trager in gebruik zalzijn, gezien wegschrijven de thread zal blokkeren.

    Gezien CFile verder zo goed overeenkomt met FILE, kan het best daarmee gëımplementeerdworden. Bij het openen moeten de modevlaggen vertaald worden:

    BOOL CFile::Open(LPCTSTR lpszFileName , UINT nOpenFlags ,CFileException* pError)

    {//close current file if already openif (m_file != NULL){

    fclose(m_file ); //will flush firstm_file = NULL;

    }//save filenamem_filename = lpszFileName;

    // translate nOpenFlags to fopen (3) formatstd:: string linFlags;linFlags.reserve (5);

    if (nOpenFlags & modeRead){

    linFlags += 'r';}

    if (nOpenFlags & modeWrite){

    if (nOpenFlags & modeCreate)linFlags += "w+";

    elselinFlags += 'w';

    }

    else if (nOpenFlags & modeReadWrite){

    linFlags += "w+";}

    //open filem_file = fopen(lpszFileName , linFlags.c_str ());

  • 8.2 Files 38

    //set (advisory) locksif (m_file == NULL){

    return FALSE;}else{

    if (nOpenFlags & shareDenyRead)flock(fileno(m_file), LOCK_EX ); // exclusive lock (advisory)

    else if (nOpenFlags & shareDenyWrite)flock(fileno(m_file), LOCK_SH ); // shared lock (advisory)

    //else shareDenyNone -> default}

    return TRUE;}

    Codefragment 8.5: CFile::Open methode

    Onder Windows worden op bestanden echte bestandssysteemlocks gelegd, opdat andere pro-cessen hier geen toegang toe zouden krijgen. Op Linux wordt dat niet gedaan, als er al ietsgelockt wordt is dit advisory en kan het door een proces gerust genegeerd worden.

    De CFile implementatie gebruikt een combinatie van de C FILE API en de stat(2) functie.Voor die laatste was de bestandsnaam nodig, die niet meer opgevraagd kan worden met enkelde FILE pointer. Daarom werd dit bij het openen ook in een privaat veld opgeslagen.

    CFile ::˜ CFile(){

    if (m_file != NULL){

    Flush ();Close ();

    }}

    ULONGLONG CFile:: GetLength () const{

    struct stat stbf;if (stat(m_filename.c_str(), &stbf) != 0)

    return stbf.st_size;//TODO throw CFileException bij foutreturn 0;

    }

    UINT CFile::Read(void* lpBuf , UINT nCount){

    if (m_file != NULL)return fread(lpBuf , sizeof(char), nCount , m_file );

    return 0;}

  • 8.2 Files 39

    void CFile::Write(const void* lpBuf , UINT nCount){

    if (m_file != NULL)fwrite(lpBuf , sizeof(char), nCount , m_file );

    }

    void CFile::Flush(){

    if (m_file != NULL)fflush(m_file );

    }

    void CFile::Close(){

    if (m_file != NULL){

    flock(fileno(m_file), LOCK_UN ); // release lockfclose(m_file );

    }}

    BOOL CFile:: GetStatus(CFileStatus& rStatus) const{

    struct stat stbf;if (stat(m_filename.c_str(), &stbf) != 0){

    rStatus.m_ctime = stbf.st_ctime;rStatus.m_mtime = stbf.st_mtime;rStatus.m_atime = stbf.st_atime;rStatus.m_size = stbf.st_size;rStatus.m_attribute = 0;if (access(m_filename.c_str(), W_OK) != 0)

    rStatus.m_attribute |= readOnly;realpath(m_filename.c_str(), rStatus.m_szFullName );

    return TRUE;}return FALSE;

    }

    Codefragment 8.6: Enkele CFile methodes

    8.2.2 CFileFind

    Met CFileFind kunnen alle bestanden in een opgegeven map, die voldoen aan een opgegevenpatroon (met wildcards) worden teruggevonden.

    Met de systeemfunctie fnmatch(3) kan een bestandsnaam gematcht worden tegen een patroon,rest ons nu nog een lijst van alle bestanden te bekomen. Op Linux zijn er 2 systeemfunctiesdie ons hierbij kunnen helpen: scandir(3) en ftw(3) (file tree walk). Het verschil zijnde datde laatste recursief werkt en ook in submappen zoekt. Beiden nemen een callback naar een

  • 8.2 Files 40

    filterfunctie. Geen van beiden laten echter toe van een patroon mee te geven.

    De manier waarop CFileFind paden met wildcards behandelt doet sterk denken aan shellwildcard expansion, en daar bestaat ook een systeemfunctie voor: wordexp(3). Met dezefunctie kan CFileFind volledig gëımplementeerd worden.

    class CFileFind{public:

    CFileFind ();virtual ˜CFileFind ();

    virtual BOOL FindFile(LPCTSTR pstrName = NULL , DWORD dwUnused = 0);virtual BOOL FindNextFile ();BOOL IsDirectory () const;BOOL IsDots () const;virtual CString GetFilePath () const;virtual BOOL GetCreationTime(CTime& refTime) const;void Close ();

    private:wordexp_t m_wexp;size_t m_idx;

    };

    /* ****** implementation ****** */

    CFileFind :: CFileFind () : m_idx (0){

    m_wexp.we_wordc = 0;}

    CFileFind ::˜ CFileFind (){

    wordfree (& m_wexp );}

    void CFileFind ::Close(){

    wordfree (& m_wexp );}

    BOOL CFileFind :: FindFile(LPCTSTR pstrName , DWORD dwUnused){

    //resetm_idx = 0;wordfree (& m_wexp );

    //findif (0 == wordexp(pstrName , &m_wexp , WRDE_NOCMD ))

    return TRUE;else

    return FALSE;}

  • 8.2 Files 41

    BOOL CFileFind :: FindNextFile (){

    if (++ m_idx < m_wexp.we_wordc)return TRUE;

    elsereturn FALSE;

    }

    BOOL CFileFind :: IsDirectory () const{

    if (m_wexp.we_wordv == NULL || m_idx >= m_wexp.we_wordc)return FALSE;

    struct stat st;if (0 == stat(m_wexp.we_wordv[m_idx],&st))

    if (S_ISDIR(st.st_mode ))return TRUE;

    return FALSE;}

    // return ABSOLUTE pathCString CFileFind :: GetFilePath () const{

    char buf[PATH_MAX ];CString out("");

    if (m_wexp.we_wordv != NULL && m_idx < m_wexp.we_wordc)if (NULL != realpath(m_wexp.we_wordv[m_idx], buf))

    out = buf;

    return out;}

    BOOL CFileFind :: GetCreationTime(CTime& refTime) const{

    // Ext4 FS does have this information , but I can't access it.// Linux filesystems historically never stored creation time.// Hence the target centos release doesn 't have the API (yet).//// => return modification time

    if (m_wexp.we_wordv == NULL || m_idx >= m_wexp.we_wordc)return FALSE;

    struct stat st;if (0 != stat(m_wexp.we_wordv[m_idx],&st))

    return FALSE;

    refTime = st.st_mtime;return TRUE;

    }

  • 8.3 CRC-32 en Zip 42

    // "." or ".."BOOL CFileFind :: IsDots () const{

    //will never show up in wordexp resultsreturn FALSE;

    }

    Codefragment 8.7: CFileFind implementatie

    CFileFind kan in zijn resultaten ook . en .. teruggeven, het huidige resultaat kan hierop wor-den nagekeken met de methode IsDots. Wordexp(3) zal deze nooit in zijn resultaten opnemen,en zijn in HardwareClient alvast ook nooit relevant, dus worden ze in deze implementatie ookniet toegevoegd: de IsDots methode geeft altijd false terug.

    Wanneer een bestand matcht, wordt het pad geëxpandeerd naar een absoluut pad met dehulp van de systeemfunctie realpath(3).

    GetCreationTime zorgt ook voor problemen, omdat creation time op Linux niet bestaat. Inplaats daarvan wordt in deze implementatie daarom de modification time teruggegeven.

    8.2.3 Unicode BOM

    Om aan te geven welke encodering en endianness een Unicode bestand of stream heeft, kanvooraan een specifieke reeks bytes toegevoegd worden: een Byte Order Mark (BOM). VoorUTF-16 is dit 0xFEFF. UTF-8 heeft ook een BOM, maar het gebruik daarvan wordt afgeraden.[Unicode Consortium 2006]

    Gezien we bestanden in deze Linux versie in UTF-8 zullen opslaan kunnen we de BOM codegerust uitschakelen:

    #ifndef __linux__ //UTF -16 BOM , for linux we'll use UTF -8 without BOMelse{

    if (SetFilePointer(m_fileDebug ,