Academiejaar 2013–2014 Faculteit Ingenieurswetenschappen en Architectuur Valentin Vaerwyckweg 1 – 9000 Gent

Omzetten van een Windows C++ MFC telefonie servertoepassing naar

Masterproef voorgedragen tot het behalen van het diploma van Master in de industriële wetenschappen: informatica

Tibault DAMMAN

Promotoren: Ann VAN OVERBERGE Ing. Filip HOSTE (MyForce)

Powered by TCPDF (www.tcpdf.org) Voorwoord

Vooreerst mijn dank aan iedereen bij MyForce voor een bijzonder aangename stage, ik weet niet wat ik zonder de voortdurende aanmoediging (“en, compileert het al?”) gedaan zou hebben. In het bijzonder wil ik Filip Hoste en Bert Reyntjes bedanken voor hun hulp en uitleg bij de code. Daarnaast wil ik ook mijn promotor Ann Van Overberge bedanken voor het nalezen van deze scriptie 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++ MFC telefonie servertoepassing naar Linux door

Tibault DAMMAN

Masterproef voorgedragen tot het behalen van het diploma van Master in de industri¨ele 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 Linux by

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 2 2.1 Besturingssysteem ...... 2 2.2 C++ compiler en standard library ...... 2 2.3 Buildsystem ...... 2 2.3.1 CMake ...... 3 2.3.1.1 Cross compiling ...... 4 2.3.1.2 Lijst van cpp-bestanden bekomen ...... 5 2.4 IDE ...... 5 2.5 Version Control ...... 5

3 Middleware (Driver/SDK) installatie 6 3.1 Dialogic Powermedia HMP ...... 6 3.2 Aculab ...... 7 3.2.1 libTiNG ...... 8

4 MFC 9 4.1 Wat is MFC? ...... 9 4.2 MFC alternatieven ...... 9 4.2.1 Wine ...... 10 4.2.2 Qt ...... 10 4.2.3 wxWidgets ...... 11 4.2.4 Boost ...... 11 4.2.5 C++11 STD ...... 11 4.3 Eigen MFC implementatie ...... 11

5 Het porting proces 13 5.1 Overzicht van de uiteindelijke mappenstructuur ...... 14

6 Datatypes en -structuren 15 6.1 Datatypes ...... 15 6.2 Array, List en Map ...... 15 6.3 String ...... 16 6.3.1 Unicode ...... 17 6.3.2 Implementatie CString ...... 19 INHOUDSOPGAVE ii

6.3.3 Extra stringfuncties ...... 21 6.3.4 Unicode in praktijk ...... 23 6.3.4.1 Vergelijken van tekens ...... 24

7 Time 26 7.1 GetTickCount ...... 26 7.2 CTime ...... 27 7.3 GetSystemTime en GetLocalTime ...... 28 7.4 COleDateTime en COleDateTimeSpan ...... 29

8 IO 31 8.1 Sockets ...... 31 8.1.1 TCP keepalive ...... 31 8.1.2 Asynchrone sockets ...... 34 8.1.2.1 select(2) ...... 34 8.1.2.2 poll(2) ...... 34 8.1.2.3 epoll(7) ...... 34 8.1.2.4 libevent, libev, libuv ...... 35 8.1.2.5 (C++11 / boost) Asio ...... 35 8.2 Files ...... 35 8.2.1 CFile ...... 36 8.2.2 CFileFind ...... 39 8.2.3 Unicode BOM ...... 42 8.3 CRC-32 en Zip ...... 42

9 Multithreading 44 9.1 Threads ...... 44 9.2 Locking (critical sections en mutexen) ...... 44 9.2.1 CSingleLock ...... 44 9.2.2 Named Mutex ...... 45 9.3 CEvent ...... 46 9.4 Compiler intrinsieke functies ...... 47 9.5 Thread Local Storage ...... 48

10 Daemon 49 10.1 GetModuleFileName ...... 50 10.2 Bepalen of de daemon al draait ...... 50 10.3 Het Windowsregister ...... 51

11 Link met middleware 52 11.1 Importeren van functies uit bibliotheken ...... 52 11.1.1 set invalid parameter handler ...... 53 11.2 Headerconflicten ...... 55 11.3 Ontbrekende functionaliteit ...... 55 11.3.1 Dialogic file API ...... 56 11.3.2 NCM API ...... 57 11.3.3 ALGO LOUD ...... 57 INHOUDSOPGAVE iii

11.3.4 SR MODELTYPE ...... 57 11.3.5 SWMODE CTBUS SCBUS ...... 58

12 Struikelblokken 59 12.1 64-bit ...... 59 12.1.1 M X64 ...... 59 12.1.2 Pointer als int ...... 59 12.1.3 sizeof(long) ...... 60 12.2 Het bestandssysteem ...... 60 12.3 Striktheid van de compiler ...... 61 12.4 Structured Exception Handling ...... 62 12.5 Problemen met macro’s ...... 62 12.5.1 Slechte ifdefs ...... 63 12.6 Het nut van typename bij templates ...... 64

13 Varia 66 13.1 Het aantal elementen van een array op de stack ...... 66 13.2 StrFormatByteSize ...... 66 13.3 AfxIsValidAddress ...... 67

14 Conclusie 69

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

Bibliografie 77 LIJST VAN CODEFRAGMENTEN iv

Lijst van codefragmenten

2.1 Verkorte versie van het gebruikte CMake projectbestand ...... 3 2.2 Cross compiling met CMake ...... 4 2.3 Bash instructie om codebestanden uit een Visual Studio project te filteren . .5 3.1 CMake - Dialogic headers en bibliotheken ...... 7 3.2 CMake - Aculab headers en bibliotheken ...... 8 3.3 CMake - libTiNG preprocessor symbool ...... 8 5.1 STUBBED macro [Gordon 2014] ...... 13 6.1 Enkele voorbeelden van MFC typedefs ...... 15 6.2 Voorbeeld van het itereren over een CList met POSITION ...... 16 6.3 Gespecialiseerde datastructuren ...... 16 6.4 Typedefs voor C-strings ...... 16 6.5 Constructors en operator overloads van CString ...... 19 6.6 CString non-member operator overload ...... 19 6.7 Een selectie van enkele CString methodes ...... 20 6.8 CString Format ...... 20 6.9 ttol parst een long naar een CString ...... 21 6.10 implementatie vsnprintf s, strncpy s en memcpy s...... 21 6.11 CWString een UTF-16 CString klasse ...... 23 6.12 Conversiefuncties tussen UTF-8 en UTF-16 ...... 23 6.13 Problemen bij multi byte strings ...... 24 6.14 Tekens vergelijken met UTF-8 aan de hand van std::mbrtowc ...... 25 7.1 Implementatie van GetTickCount ...... 26 7.2 CTime constructor ...... 27 7.3 Enkele CTime methodes ...... 27 7.4 de SYSTEMTIME struct ...... 28 7.5 Implementatie van GetSystemTime ...... 29 7.6 COleDateTime en COleDateTimeSpan implementatie ...... 29 8.1 TCP keep-alive activeren met WSA voor m hSocket ...... 31 8.2 TCP keep-alive activeren op Linux voor socket s ...... 32 8.3 De resulterende TCP keep-alive wrapper ...... 32 8.4 de header van CFile en CFileStatus ...... 36 8.5 CFile::Open methode ...... 37 8.6 Enkele CFile methodes ...... 38 8.7 CFileFind implementatie ...... 40 8.8 (UTF-16) BOM is op Linux niet nodig ...... 42 9.1 Voorbeeld van de scope van een CCriticalSection ...... 45 LIJST VAN CODEFRAGMENTEN v

9.2 Implementatie CCriticalSection::Lock met pthread mutex t ...... 45 9.3 Voorbeeld van het gebruik van CEvent ...... 46 9.4 Voorbeeld van het gebruik van pthread conditions ...... 46 9.5 GCC compatibele implementatie van InterlockedIncrement en -Decrement . . 47 9.6 Thread Specific Storage wrapper ...... 48 10.1 Opvragen van het executable pad op Windows ...... 50 10.2 Opvragen van het executable pad op Linux ...... 50 10.3 Controlleren of een applicatie al draait ...... 50 11.1 Dynamische bibliotheek oproepen op Windows ...... 52 11.2 Dynamische bibliotheek oproepen op Linux ...... 52 11.3 Wrapper voor dynamisch linken ...... 53 11.4 Instellen van een invalid parameter handler op Windows ...... 53 11.5 Dialogic file API ...... 56 11.6 Ontbreekt: ALGO LOUD ...... 57 11.7 Ontbreekt: SR MODELTYPE ...... 57 12.1 M X64 defini¨eren ...... 59 12.2 Code die enkel in Visual Studio compileert ...... 61 12.3 Code die overal compileert ...... 61 12.4 Een macro aanroep ...... 62 12.5 De macrodefinitie ...... 62 12.6 De macroaanroep na de preprocessor ...... 63 12.7 Een lege define ...... 63 12.8 Defines kunnen ook voor problemen zorgen ...... 63 12.9 Problematische implementatie CMap::Lookup ...... 64 12.10’typename’ voor een gekwalificeerd afhankelijk type ...... 65 13.1 Een macro voor de grootte van een array op de stack ...... 66 13.2 FormatBytes maakt gebruik van StrFormatByteSize ...... 66 13.3 Implementatie CStaticTools::FormatBytes (StrFormatByteSize) ...... 67 13.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. Hiermee kunnen gewone servers omgetoverd worden tot complete telefonieoplossingen voor bedrijven waar automatisatie van telefonie centraal staat (zoals callcenters, bureaus voor telefonisch marktonderzoek, helpdesks, etc). Dit beperkt zich niet tot traditionele ISDN telefoonlijnen: ook het flexibelere en puur softwarematige VoIP wordt ondersteund. VoIP heeft als voordeel dat het – naast een USB headset voor elke agent – geen kostelijke extra hardware vereist, die dan ook niet vervangen moet worden bij falen. Bijkomend voordeel is dat het erg schaalbaar is en naar de cloud kan worden verhuisd. Zo’n volledig telefonienetwerk wordt beheerd door ´e´enCTArchitect server, die op zijn beurt communiceert met enkele HardwareClient servers. Op deze servers staat een (software) VoIP server ge¨ınstalleerd, en/of een (hardware) ISDN kaart die met het vaste telefonienetwerk ver- bonden is. De HardwareClient software stelt zijn beschikbare VoIP/ISDN telefoonlijnen dan beschikbaar op een gestandaardiseerde manier op het netwerk (via sockets). E´enHardware- 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 ge¨ınteresseerd in een Linux versie om van de bijkomende functionaliteit te kunnen genieten. Deze thesis behandelt de vele stappen die nodig zijn om een project als HardwareClient naar Linux te brengen. We starten met het opzetten van een ontwikkelingsomgeving met alle benodigde onderdelen en bekijken vervolgens hoe we het omzetten van de code best aanpakken. We onderzoeken hoe Windows en Linux zich verschillen op gebied van sockets, multithreading en meer, en hoe zich dat vertaalt in code. Ook verborgen complexiteiten en verschillen in de compilers worden besproken. TOOLCHAIN 2

Hoofdstuk 2

Toolchain

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

2.1 Besturingssysteem

“Linux” op zich is geen besturingssyteem, maar een kernel. De keuze uit Linuxgebaseerde besturingssystemen is groot: , 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, met respectievelijk libstdc++ en libc++ als verkozen standard library. Rekening houdend met het doelplatform 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 niet evident om elke keer handmatig de compiler aan te geven in welke volgorde de bestanden gecompileerd 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 weinig populair. De klassieke Makefiles genieten nog altijd de voorkeur. Makefiles zijn eenvoudige tekstbestanden waarin aan de hand van een declaratieve syntax regels worden opgesteld voor de compiler. Wanneer de gebruiker in de commandolijn make typt, zal de Makefile automatisch gevonden en uitgevoerd worden. Makefiles zijn erg populair in de Open Source wereld, omdat het geen verplichtingen oplegt wat 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 te vereenvoudigen en te automatiseren kan gebruik gemaakt worden van CMake.

2.3.1 CMake

CMake is een populair systeem om platform- en compileronafhankelijke projectbestanden te schrijven. Aan de hand van een eenvoudige syntax wordt opgegeven waar de code staat, waar extra headers te vinden zijn, met welke bibliotheken gelinkt moet worden, enzovoort. CMake kan aan de hand van deze gegevens automatisch een projectbestand genereren, zoals onder andere 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.cpp HWClient.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 voor het toevoegen van debugsymbolen, en -Wall activeert alle compiler warnings. add definitions wordt gebruikt om preprocessor symbolen in te stellen, CLIENTMANAGER en MANAGERDIALOGIC moeten bijvoorbeeld verplicht gedefinieerd zijn voor de compilatie van HardwareClient. 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 moeten worden. 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 wat de 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 hetzelfde CMake 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 van cross 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 64b if(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 SIZEOF VOID P) 8 byte groot is, of 32 indien hij 4 byte groot is. In de CMake debugoutput wordt via message geprint welke architectuur gekozen is, zodat we bij het genereren van een buildfile of projectbestand in de debugoutput kunnen nagaan of de juiste architectuur geselecteerd werd. Indien je wil cross compilen naar 32-bit op een 64-bit systeem kan je de waarde van arch overschrijven 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 cross compilen 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/<[ˆ ]* Include=\"\(.*\)\" *\/\?>.*/\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 ingebouwde debugger om snel te vinden welke code niet werkt en aangepast moeten worden. Op Windows werd 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 niet direct in de hoofdrepository opgenomen, daarom werd voor de Linux versie een afzonderlijke repository gebruikt. Uit persoonlijke voorkeur werd hier voor gekozen. MIDDLEWARE (DRIVER/SDK) INSTALLATIE 6

Hoofdstuk 3

Middleware (Driver/SDK) installatie

MyForce ondersteunt de telefonietechnologie (ISDN en VoIP) van meerdere leveranciers zoals Dialogic, Aculab en Amtelco. Deze bedrijven leveren naast hardware ook software, zoals de drivers nodig voor de hardware, VoIP servers ter vervangen van hardware, en bibliotheken die het ontwikkelen van telefonietoepassingen in het algemeen vereenvoudigen. Dit soort software wordt ook middleware genoemd, omdat het zich tussen het besturingssysteem en de gebruikersapplicatie 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 van gespecialiseerde hardware. Dit is een zeer belangrijk onderdeel van HardwareClient. Dialogic® PowerMedia— HMP for Linux (HMP Linux) is scalable, feature-rich multimedia processing software for building innovative and cost-effective voice and video solutions suitable for enterprise or service provider deployment. HMP Linux can enable basic SIP or hybrid connectivity, audio and video play/record, multimedia streaming, transcoding, fax, automated interactive audio and video solutions (IVR and IVVR), and complex live interactions, such as contact centers and audio and video conferencing or video portals. [Dialogic 2014b] Om HardwareClient te kunnen compileren is linken met de Dialogic HMP SDK vereist. Deze is 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: $ -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-bit compatibiliteitsbibliotheek: 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 toegevoegd worden:

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 our customers’ success, our technology is used to deliver multimodal voice, data and fax solutions for use within IP, PSTN and mobile networks – with performance levels 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 and self-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 installer vereist opnieuw een 32-bit omgeving, en de installatie van twee extra bibliotheken die op een standaard 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 HardwareClient gebruikt wordt. Om deze header te kunnen gebruiken is de declaratie van een preprocessor symbool 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 ge¨ı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 ontwikkeling van commerci¨ele applicaties: van een gestandaardiseerde bibliotheek met datastructuren en algoritmes 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¨enteerde 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 geen uitzondering 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 aan te passen door elke MFC datastructuur te vervangen met de moderne gestandaardiseerde variant (std::string, std::map, . . . ). Naief, omdat dit in een codeproject van letterlijk meer dan 100 000 lijnen ongelofelijk veel vervangwerk cre¨eert. Het gebruik van de alternatieven kan bovendien erg veel verschillen, waardoor het meer herschrijven dan vervangen wordt. Daarenboven hergebruikt MyForce vaak code, het gedrag in ´e´enbestand aanpassen kan dus een effect hebben op meerdere projecten. De verschillende socketimplementaties gebruikt in HardwareClient komen bijvoorbeeld uit twee andere projecten. Enkel de code aanpassen in de bestanden nodig voor HardwareClient zou alle andere projecten kunnen breken. Het kan niet de bedoeling zijn om alle code van MyForce te herschijven, dus is een andere aanpak aan de 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. Indien je toegang hebt tot de broncode van een Windowsapplicatie, dan kan je deze rechtstreeks compileren naar Linuxcompatibele code door te linken met Winelib. Gezien de broncode van MFC meegeleverd wordt met Visual Studio, zouden we deze kunnen proberen compileren met Wine, om zo een bibliotheek te krijgen die we op Linux via een wrapper zouden kunnen gebruiken. 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 eerste publieke 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¨ele licentie. Om een closed source applicatie te maken met Qt moet een licentie aangekocht worden.

1http://winehq.org 2Uitgesproken als /hkjuqt/ (”cute”) – http://qt-project.com 3http://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 bestaande commerci¨ele oplossingen te duur waren. [wxWidgets 2014c] Dit framework is in gebruik erg gelijkaardig aan MFC. Beide gebruiken een eventgebaseerde structuur geconfigureerd met macro’s, hebben klassen voor datastructuren, sockets, threads, GUIs, . . . maar in tegenstelling tot MFC compileert code geschreven met wxWidgets op zowel Windows, 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 binaries toelaat. [wxWidgets 2014d] Concreet betekent dit dat commerci¨ele closed source applicaties die met wxWidgets zijn geschreven zonder extra licentiekosten kunnen worden verkocht (in tegenstelling 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 die gebruik in propri¨etaire software toelaat. Veel van deze Boost libraries vinden uiteindelijk ook hun weg tot inclusie in de offici¨ele C++ standaard. [Sch¨aling 2011] Boost maakt onder andere multithreading, asynchrone sockets, unicode en werken met het bestandssysteem 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 de STD in de nieuwe C++11 standaard ook ondersteuning voor onder andere threads, mutexen en conditions.

4.3 Eigen MFC implementatie wxWidgets lijkt het meeste op MFC, en was daarmee de eerste keuze voor dit project. Hoewel veel klassen bijna 1:1 konden vervangen worden, werd na een aantal weken al snel duidelijk dat dit verre van altijd het geval was. Desondanks de gelijkenissen moesten er vaak uitgebreide wrappers geschreven worden om de verschillen in de API te kunnen overbruggen. Bepaalde onderdelen waren zelfs helemaal onbruikbaar: datastructuren zoals de list en map werkten volledig 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 druk van Microsoft is het in 2004 van naam moeten veranderen. [wxWidgets 2014a] 5http://boost.org 6De 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 de opdracht. Deze vereiste is in overleg met MyForce vrij snel weggevallen eens de precieze grootte van de opdracht duidelijk werd. De GUI kon al los van de HardwareClient server op een 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. Halverwege de stage werd beslist om deze dependency te laten vallen, en werd alles herschreven met native Linux 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 veel mogelijk 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, en zoeken 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 een platformonafhankelijk alternatief onbegonnen werk. Om te weten wat een ontbrekende functie hoort te doen kunnen we de Microsoft documentatie op de MSDN site raadplegen1. Een alternatief kan dan via Google gevonden worden, of door gericht te zoeken in de Linux manual pages. Wanneer in dit document Linuxfuncties vermeld worden zal dit op de gebruikelijke manier uit de vakliteratuur gebeuren: met tussen haakjes de manual sectie waarin het teruggevonden kan worden. Bij printf(3) zijn we bijvoorbeeld ge¨ı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 in te 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, gebruik daarom 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 indeling van 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¨ele structuur of naamgeving, en zijn puur voor de overzichtelijkheid toegevoegd.

2Alle MFC headers worden geprefixt met “afx”, een restant van het Application Framework X, een oud project 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 als alles 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 staat voor Long Pointer to WORD, waarmee hier een 32-bit pointer naar een 16-bit processorwoord bedoeld wordt: deze naamgeving dateert duidelijk uit een tijd waar processorarchitecturen nog 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 met STD datastructuren, deze wrappers konden met een minimum aantal aanpassingen op Linux

1Een naamgevingconventie uitgevonden door Dr. Charles Simonyi, Chief Architect bij Microsoft, en geboren in 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, deze werden 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 pointer naar 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 plaats van 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 UNICODE typedef WCHARTCHAR; typedef LPWSTRLPTSTR; typedef LPCWSTRLPCTSTR; # else typedef CHARTCHAR; typedef LPSTRLPTSTR; typedef LPCSTRLPCTSTR; # 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 aantal typedefs 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 standaard 1 byte grote characters. De prefix t of _t zal in nog veel functie- en methodenamen terugkeren met dezelfde betekenis. Dat “Unicode” volgens Microsoft betekent dat wide characters gebruikt moeten worden is een misvatting, en cre¨eert bovendien problemen op andere besturingssystemen.

6.3.1 Unicode

Om tekst op te slaan op computers worden karakters gemapt op getallen. De klassieke manier waarop dit gebeurde was de 7-bit ASCII tabel. In deze tabel worden alle tien cijfers, enkele speciale tekens en de Engelse (accentloze) letters gemapt op de getallen 32-127. De eerste 32 getallen komen overeen met onprintbare tekens zoals ‘\0’ en ‘\n’. Gezien een byte 8 bits groot is liet dat OEMs toe om met de overblijvende bit naar eigen keuze nog 128 extra tekens toe te voegen. Hier werden initieel nooit afspraken rond gemaakt, waardoor het delen van bestanden met speciale tekens voor veel problemen zorgde. De ANSI standaard loste dit probleem deels op door de verschillende manieren om de laatste 128 tekens in te delen vast te leggen in code pages (zoals latin-1). Bij het openen van een tekst wordt de bijhorende codepage geselecteerd om de correcte vreemde tekens te kunnen weergeven. 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 van het idee dat een verdubbeling van de char-grootte naar 16 bits voldoende zou zijn om alle mogelijke karakters voor te stellen. [Radzivilovsky, Galka en Novgorodov 2014] In Unicode worden karakters gemapt op “theoretische” code points, die losstaan van hoe ze op de schijf worden opgeslagen. Dit wordt genoteerd als “U+” (voor Unicode), gevolgd door hexadecimale 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+006C U+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 chars worden 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 code points) 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 code points, 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. Dit is wat op Unix verstaan wordt onder wchar t. Merk op dat wchar t dus niet zomaar kan uitgewisseld 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 een extra 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 ontwikkelden Ken 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 in chars in plaats van wchars. UTF-8 is hierdoor byte ge¨ori¨enteerd en verschilt in tegenstelling tot 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 0 zijn en deze kunnen worden weggelaten; ze nemen hierdoor maar ´e´enbyte in. Een Engelse tekst ge¨encodeerd in ASCII is hierdoor identiek aan diezelfde tekst ge¨encodeerd in UTF-8! Hierdoor konden Unixsystemen op een vrij eenvoudige manier Unicode adopteren, zonder compatibiliteit met oudere ASCII scripts of configuratiebestanden te verliezen. UTF-8 is wat op 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 niet gebruikt kan worden op Linux: zowel de grootte van de datastructuren als de encodering van de data verschillen. In Windows worden native system calls voorzien in een variant voor 8-bit ASCII chars en een variant voor UTF-16 wchar_ts. Op Linux vind je voornamelijk system calls 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 datatypes gebruikt worden. In de MFC code wordt gelukkig voornamelijk gebruik gemaakt van de LPCTSTR typedef. Op Linux laten we deze naar const char* wijzen in plaats van naar const wchar t*.

6.3.2 Implementatie CString

CString kan worden ge¨ımplementeerd door std::string over te erven en extra functies toe te voegen. Overerving negeert constructors en operator overloads, die moeten dus allemaal opnieuw 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 eenvoudige wrapper 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 CString zelf op. We kunnen hiervoor de vsnprintf(3) variant gebruiken, die zijn resultaat naar een buffer 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 buffer char * buf = ( char *)malloc(bufsize* sizeof ( char ));

//reset arglist va_end(args); va_start(args, fmt);

// format vsnprintf(buf, bufsize, fmt, args);

//copy buffer to string this -> operator =( buf ); 6.3 String 21

//free buffer and arglist free (buf ); va_end(args); }

Codefragment 6.8: CString Format

6.3.3 Extra stringfuncties

Naast CString methodes moeten ook een aantal stringgerelateerde functies ge¨ımplementeerd worden. Gezien we op Linux mogen veronderstellen dat alle tekst UTF-8 is, moeten we voor prefix-“ t” functies (besproken in de inleiding van dit hoofdstuk) zoals ttol geen wide character 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 nog een aantal extra veiligheidscontroles ingebouwd. Naast de buffergrootte wordt bijvoorbeeld nog een extra argument voor het aantal te kopi¨eren karakters meegegeven. Eenzelfde vergelijking kan gemaakt worden voor strncpy s en strncpy(3), en memcpy s en memcpy(3).

int _vsnprintf_s(char *buffer , size_t sizeOfBuffer, size_t count , const char *format, va_list argptr) { if (buffer == 0 || format == 0 || count <= 0) { return -1; } if (count != _TRUNCATE) { if (count+1 > 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 <= numberOfElements) { strncpy(strDest, strSource, count); strDest[count-1] = 0; return 0; } else if (count == _TRUNCATE) { strncpy(strDest, strSource, numberOfElements); strDest[numberOfElements-1] = 0; return 0; } else { strDest[0] = 0; return ERANGE; } }

errno_t memcpy_s(void * dest , size_t destBufSize, void* src , size_t count ) { if (dest == NULL) { return EINVAL; } if (src == NULL) { memset(dest, 0, destBufSize); return EINVAL; } if (destBufSize < count) { memset(dest, 0, destBufSize); return ERANGE; }

memcpy(dest, src, 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¨ele MSDN documentatie.

6.3.4 Unicode in praktijk

Gezien de implementatie van CString nu intern UTF-8 gebruikt, is een andere klasse nodig voor het werken met UTF-16 strings. Omdat een wchar op Linux 4 bytes groot is in plaats van 2 bytes, is een std::wstring of een std::vector niet correct. In C++11 is de ambigu¨ıteit rondom de grootte van wchar opgelost door de introductie van twee nieuwe types: char16 t en char32 t. De conversie tussen UTF-8 en UTF-16 maakt ook deel uit van de C++11 standaard (in de vorm van std::codecvt utf8 utf16), maar was op moment van schrijven nog niet beschikbaar in GCC. De compacte UTF-CPP bibliotheek3 werd als alternatief gekozen. Deze gebruikt unsigned short – 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 to ANSI) en CA2W (Convert ANSI to Wide). In de Linux implementatie bevat de CHAR string UTF-8, en kunnen de functies CW2A en CA2W dus gebruikt worden om de conversie tussen UTF-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 nothing return 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("cafe");´ assert (s.GetLength() == 4); assert (s.at (3) == 'e´');

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 ‘´e’vormen. De code zal op Linux zelfs niet compileren, omdat ‘´e’niet in een const char past. In codefragment 6.14 is te zien hoe we dit probleem aanpakken. Om in de UTF-8 versie een teken te kunnen vergelijken wordt het eerst omgezet naar een wide character. De volledige string omzetten (met bijvoorbeeld std::mbstowcs) en pas daarna de tekens ´e´envoor ´e´en vergelijken 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 is te 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 _WIN32 CString strCopy = strString; const TCHAR* ch = strCopy.GetBuffer(); while ((*ch) != _T('\0 ')) { //we only accept space and printable characters if (!_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-8 const char* mbStr = strString.GetBuffer();

std::locale l(""); //defaults to system locale std::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 2 oproepen). Het aantal milliseconden sinds het opstarten kunnen we op Linux niet opvragen, maar gezien dit enkel gebruikt wordt om het verschil te bepalen, zouden we hier ook de systeemklok voor kunnen gebruiken. De standaard gettimeofday(2) gebruiken we hier beter niet, gezien die klok be¨ınvloed wordt door het Network Time Protocol (NTP). Als de klok tussen 2 aanroepen zou 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 // fallback struct 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, zonder sprongen 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 Linux 2.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, volstaat de 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 timeval struct 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 belangrijke verschillen 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 Saving Time (DST) in effect is. Positief voor zomertijd, 0 voor wintertijd. Bij een negatieve waarde wordt dit automatisch bepaald aam de hand van de opgegeven datum. De klasse bevat verder enkele voordehandliggende getters en operator overloads. Te vermelden waard 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 30 seconden), mktime(3) zal dit zelf doen. We mogen wel niet vergeten van isdst terug op auto te 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 van de documentatie gelukkig volledig overeen.

7.3 GetSystemTime en GetLocalTime

GetSystemTime en GetLocalTime zijn twee native win32 functies die de tijd teruggeven in een SYSTEMTIME 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 tijd in 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) in plaats 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 nieuwe gestandaardiseerde manier om in C++ met tijd te werken: std::chrono. COleDateTime en COleDateTimeSpan werden hiermee ge¨ı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 te kunnen aansluiten op ´e´ennetwerkverbinding. De router houdt hiertoe een lijst bij van alle client-server verbindingen voor elk apparaat. Door de fysieke beperkingen van vele routers is het aantal verbindingen dat ze zo in het geheugen kunnen houden gelimiteerd. Vele imple- mentaties “vergeten” hierdoor oudere inactieve (maar open) verbindingen ten voordele van nieuwe actieve verbindingen. [Mueller 2011] Het vervelende aan deze implementatie is dat de verbinding niet “officieel” verbroken wordt, en client noch server hier dus van op de hoogte gebracht worden. Wanneer de verbinding uit onwetendheid toch gebruikt wordt, zullen alle pakketten verloren gaan. Het programma kan dan pas na een (of meerdere) time-out periodes beseffen dat de verbinding verbroken is. Sommige routers laten inactieve verbindingen al na 5 minuten vallen, een probleem dat ook MyForce 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 bevestiging van ontvangen. [Guha 2008] Verbroken verbindingen kunnen zo snel opgespoord worden, en het 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 ms sKeepAliveSettings.keepaliveinterval = 1000; //in ms WSAIoctl(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 daarom standaard 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 het commando setsockopt(2) (level SOL SOCKET):

int keepalive = 1; // 1 -> enabled setsockopt(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 van tcp(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 obtained from the other end.

tcp_keepalive_time (integer; default: 7200; since Linux 2.2) The number of seconds a connection needs to be idle before TCP begins sending out keep-alive probes. Keep-alives are only sent when the SO_KEEPALIVE socket option is enabled. The default value is 7200 seconds (2 hours). An idle connection is termi- nated after approximately an additional 11 minutes (9 probes an interval 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 ver is. Wat verder in de man page vinden we dat deze configuratie ook per socket kan worden toegepast door opnieuw setsockopt(2) te gebruiken, maar dit keer met level SOL TCP2 in plaats van SOL SOCKET. [Busatto 2007] TCP KEEPCNT configureert tcp keepalive probes TCP KEEPIDLE configureert tcp keepalive time TCP 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 tcp keepalive time (TCP KEEPIDLE) en tcp keepalive intvl (TCP KEEPINTVL), op ´e´enverschil na: op Windows worden deze uitgedrukt in milliseconden, op Linux in seconden. De ontbrekende optie voor tcp keepalive probes (TCP KEEPCNT) is op Windows niet configureerbaar en heeft als 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 4 struct 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 Windows if (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/1000 tcp keepalive intvl TCP KEEPINTVL tcp keepalive::keepaliveinterval/1000 tcp 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, er zijn meerdere opties:

8.1.2.1 select(2)

Een oplossing besproken in Veerle Ongenae (2014). Computernetwerken IV (cursus) is select(2), deze functie heeft echter enkele belangrijke nadelen. File descriptors (FD) worden hier bijgehouden in een bitmap, waardoor bij het aanmaken van de datastructuur de maxi- mum FD moet worden opgegeven. Wanneer duizenden connecties zullen verwerkt worden, is het 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 veel in 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 en een grote bottleneck vormt. [Stenberg 2014]

8.1.2.3 epoll(7)

Geen enkele van de hierboven vernoemde beperkingen komen voor in de Linux specifieke epoll(7) interface. Hier wordt de array met file descriptors beheerd vanuit de kernel in plaats van in userspace. Een eigen implementatie van de MFC CAsyncSocket met dit systeem is doenbaar3, maar vereist veel tijd om grondig te kunnen testen, tijd die gezien de grote scope van het project niet beschikbaar was. 3Voor een duidelijk uitgewerkt voorbeeld, zie Mukund Sivaraman (2011). How to use epoll? A complete example in C. url: 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 bibliotheken met kant en klare implementaties te vinden:

8.1.2.4 libevent, libev, libuv

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

8.1.2.5 (C++11 / boost) Asio

C++11 heeft crossplatform development al veel vereenvoudigd door onder andere het gebruik van threads in de standaard op te nemen. File IO werd al lang ondersteund, maar sockets nog altijd niet. Boost7 heeft wel een implementatie voor asynchrone sockets, genaamd Asio. Deze bibliotheek is intussen ook beschikbaar in een C++11 versie, zodat deze onafhankelijk van andere Boost bibliotheken kan worden gebruikt. Deze versie is al in meerdere revisies voorgesteld voor opname 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 kans op inclusie in de standaard groot is, vormt deze bibliotheek een goede keuze. Vooraleer asynchrone socketwrappers ge¨ımplementeerd kunnen worden is een event loop nodig, en dat deel van multithreading werd niet tijdig afgewerkt. De socketimplementatie is daardoor ook uitgebleven.

8.2 Files

Veel IO C-functies zijn gestandaardiseerd, maar hebben op Windows een variant waarvoor nog ondersteuning toegevoegd moet worden. taccess, tremove en tfopen s komen bijvoorbeeld overeen 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.org 5http://software.schmorp.de/pkg/libev.html 6https://github.com/joyent/libuv 7Boost 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 uiteindelijke C++11 standaard zijn al 10 Boost bibliotheken opgenomen. [Sch¨aling 2011] Voor TR2 wordt gekeken naar andere 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 veel punten 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) 0 x01000 }; 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 file CTime m_mtime; // last modification date/time of file CTime m_atime; // last access date/time of file ULONGLONG m_size; // logical size of file in bytes BYTE m_attribute; // logical OR of CFile::Attribute enum values TCHAR 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 Close methodes te herkennen. GetStatus en de bijhorende CFileStatus struct doen dan weer denken aan de Unix stat(2) functie en gelijknamige struct. Het enige grote verschil met de C FILE API zit bij het opgeven van de mode bij het openen: FILE neemt een string, CFile neemt een int opgebouwd uit bitvlaggen. Kijken we naar de native Linux file IO API, dan zien we dat de open(2) functie net als CFile een 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 zal zijn, gezien wegschrijven de thread zal blokkeren. Gezien CFile verder zo goed overeenkomt met FILE, kan het best daarmee ge¨ımplementeerd worden. Bij het openen moeten de modevlaggen vertaald worden:

BOOL CFile::Open(LPCTSTR lpszFileName, UINT nOpenFlags, CFileException* pError) { //close current file if already open if (m_file != NULL) { fclose(m_file); //will flush first m_file = NULL; } //save filename m_filename = lpszFileName;

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

if (nOpenFlags & modeRead) { linFlags += 'r'; }

if (nOpenFlags & modeWrite) { if (nOpenFlags & modeCreate) linFlags += "w+"; else linFlags += 'w'; }

else if (nOpenFlags & modeReadWrite) { linFlags += "w+"; }

// open file m_file = fopen(lpszFileName, linFlags.c_str()); 8.2 Files 38

//set (advisory) locks if (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 iets gelockt 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 enkel de 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 fout return 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 lock fclose(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 opgegeven patroon (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 systeemfuncties die ons hierbij kunnen helpen: scandir(3) en ftw(3) (file tree walk). Het verschil zijnde dat de 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 shell wildcard expansion, en daar bestaat ook een systeemfunctie voor: wordexp(3). Met deze functie kan CFileFind volledig ge¨ı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) { // reset m_idx = 0; wordfree(&m_wexp);

// find if (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; else return 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 path CString 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 results return 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 ook niet toegevoegd: de IsDots methode geeft altijd false terug. Wanneer een bestand matcht, wordt het pad ge¨expandeerd naar een absoluut pad met de hulp van de systeemfunctie realpath(3). GetCreationTime zorgt ook voor problemen, omdat creation time op Linux niet bestaat. In plaats 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, kan vooraan een specifieke reeks bytes toegevoegd worden: een Byte Order Mark (BOM). Voor UTF-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 code gerust uitschakelen:

# ifndef __linux__ //UTF-16 BOM, for linux we 'll use UTF-8 without BOM else { if (SetFilePointer(m_fileDebug, 0, NULL, FILE_END)==0) { TCHAR bom = (TCHAR)0xFEFF; DWORD dwWritten = 0; WriteFile(m_fileDebug, &bom, sizeof (TCHAR), &dwWritten, NULL); } } # endif

Codefragment 8.8: (UTF-16) BOM is op Linux niet nodig

8.3 CRC-32 en Zip

CRC-32 hashing gebeurt in HardwareClient met behulp van een third party header. Deze bevat zowel een implementatie met native win32 functies, als ´e´enin pure assembly code. 8.3 CRC-32 en Zip 43

Omdat er meerdere CRC-32 varianten bestaan en compatibiliteit een must is, werd deze third party header geherimplementeerd met native Linux functies. Deze implementatie werd ook gebruikt voor de assembly versie. Ondersteuning voor datacompressie werd door MyForce ge¨ımplementeerd met zlib, een bi- bliotheek die op Linux standaard ge¨ınstalleerd staat. Dankzij de identieke (platformonafhan- kelijke) API werkte dit meteen, zonder enige aanpassing. MULTITHREADING 44

Hoofdstuk 9

Multithreading

9.1 Threads

Windows threads (en de MFC CWinThread wrapper) kunnen ge¨ımplementeerd worden met pthreads(7), of std::thread. In dit geval wordt best gekozen voor de native pthreads, want C++11 voorziet geen manier om threads gedwongen af te sluiten. CreateThread komt overeen met pthread create(3), TerminateThread met pthread kill(3), GetCurrentThreadId met pthread self(3), en WaitForSingleObject met pthread join(3).

9.2 Locking (critical sections en mutexen)

Wanneer met meerdere threads gewerkt wordt is het belangrijk om op een veilige manier met gedeelde variabelen om te gaan. Gebeurt dit niet, dan zouden de verschillende threads elkaars aanpassingen kunnen overschrijven en zo belangrijke informatie doen verloren gaan. Het is dus belangrijk om tijdelijk exclusieve toegang tot een variabele af te dwingen, een proces dat we “locking” noemen. Op Windows kan dit op twee manieren gedaan worden: aan de hand van een mutex (CMutex in MFC), of een Critical Section (CCriticalSection in MFC) [Microsoft 2014e]. Mutexen zijn native Windowsobjecten, beheerd door de kernel. Critical Sections niet, ze kunnen hierdoor niet gedeeld worden door meerdere processen, maar moeten dan ook op geen enkel moment de kernel contacteren. Critical Sections zijn hierdoor veel sneller en meteen ook de aangeraden manier om te locken. [Osterman 2005a] Een standaard Unix pthread_mutex_t is op Linux ge¨ımplementeerd met een gelijkaardig snel- ler systeem, hier futex (fast userspace mutex) genaamd. [Preshing 2011]

9.2.1 CSingleLock

Om het verkrijgen en losgeven van een lock te vereenvoudigen wordt in MFC gebruik gemaakt van een CSingleLock object waaraan een CMutex of CCriticalSection object is gelinkt. Op 9.2 Locking (critical sections en mutexen) 45

CSingleLock kunnen dan operaties als Lock en Unlock worden toegepast. Een groot voordeel van CSingleLock is dat zijn lock afhangt van de scope: wanneer een gelockt CSingleLock object out of scope gaat, wordt zijn CMutex of CCriticalSection automatisch vrijgegeven.

CCriticalSection ccs; ... { CSingleLock slot(&ccs); slot.Lock(); //ccs is hier locked } //ccs is hier unlocked

Codefragment 9.1: Voorbeeld van de scope van een CCriticalSection

Een interessante functionaliteit van een CSingleLock met CCriticalSection is de mogelijkheid om een timeout op te geven bij het aanvragen van een lock. Wanneer een thread een lock heeft op een resource en die niet vrijgeeft, kan een andere thread oneindig lang blokkeren wanneer deze ook een lock wil. Een timeout voorkomt deze deadlock, zodat dit probleem op een andere manier kan worden afgehandeld. Op Linux kan deze functionaliteit bekomen worden via de pthread_mutex_timedlock(3) functie.

BOOL CCriticalSection::Lock(DWORD dwTimeout) { if (dwTimeout == INFINITE) return CCriticalSection::Lock();

struct timespec ts_delay; clock_gettime(CLOCK_REALTIME, &ts_delay); ts_delay.tv_nsec += dwTimeout*1000; //millisec -> nanosec

if (pthread_mutex_timedlock(&cc_mutex, &ts_delay) > 0) return FALSE; else return TRUE; }

Codefragment 9.2: Implementatie CCriticalSection::Lock met pthread mutex t

9.2.2 Named Mutex

Zoals hierboven vermeld werd, kan een mutex op Windows door verschillende processen ge- deeld worden. Bij de constructie van een mutex kan een unieke naam meegegeven worden. Wanneer een ander proces een mutex met dezelfde naam probeert te construeren, zal het systeem deze mutex naar hetzelfde kernelobject doen verwijzen. In HardwareClient wordt een Named Mutex enkel gebruikt om bij het opstarten te detecteren of er al een andere instantie van HardwareClient draait. Bij het opstarten wordt geprobeerd om een lock te krijgen op een mutex genaamd “HWClient”. Indien dit lukt gaat het pro- gramma verder en wordt de mutex pas vrijgegeven bij het afsluiten, indien het faalt wordt 9.3 CEvent 46 het programma onmiddellijk terug afgesloten met de melding dat een andere instantie al draait. Op Linux krijgen mutexen geen naam, het detecteren van reeds draaiende instanties van applicaties gebeurt aan de hand van pid bestanden. Meer hierover in hoofdstuk 10.2. Een implementatie van dit soort mutexen is hierdoor overbodig.

9.3 CEvent

Wanneer we een thread willen pauzeren tot een bepaalde voorwaarde voldaan is, zouden we deze periodisch kunnen wakker maken om na te gaan of de voorwaarde al is voldaan. Deze polling techniek is, voor dezelfde reden als bij sockets, de minst effici¨ente manier om dit probleem op te lossen. Beter is om bij het aanpassen van de inhoud meteen de voorwaarde te controleren, en de thread dan op een of andere manier te signaleren wanneer de voorwaarde voldaan is. In MFC worden dit events genoemd, ge¨ımplementeerd in CEvent.

/**** init ****/ CEvent* pEvent = new CEvent(FALSE, FALSE);

/**** in de thread die wacht ****/ ::WaitForSingleObject(pEvent->m_hObject, INFINITE);

/**** in de thread waar de inhoud aangepast wordt ****/ while (...) { ... pas inhoud aan ... if (voorwaarde_voldaan()) pEvent->SetEvent(); //signaleer wachtende thread ... }

Codefragment 9.3: Voorbeeld van het gebruik van CEvent

CEvent erft over van hetzelfde object als CMutex en CCriticalSection en heeft hierdoor de locking functionaliteit ingebouwd die nodig is om dit alles thread safe te kunnen doen.

Op Linux is het systeem gelijkaardig, maar spreekt men van condition variables, ge¨ımplementeerd met pthread conditions. Hierbij moet wel nog handmatig een mutex worden bijgehouden.

/**** init ****/ pthread_mutex_t mtx; pthread_cond_t evt;

pthread_mutex_init(&mtx, NULL); pthread_cond_init(&evt, NULL); 9.4 Compiler intrinsieke functies 47

/**** in de thread die wacht ****/ pthread_mutex_lock(&mtx); pthread_cond_wait(&evt, &mtx); // thread blokkeert hier pthread_mutex_unlock(&mtx);

/**** in de thread waar de inhoud aangepast wordt ****/ while (...) { ... pthread_mutex_lock(&mtx); ... pas inhoud aan ... if (voorwaarde_voldaan()) pthread_cond_signal(&evt); //signaleer wachtende thread pthread_mutex_unlock(&mtx); ... } /***** cleanup *****/ pthread_mutex_destroy(&mtx); pthread_cond_destroy(&evt);

Codefragment 9.4: Voorbeeld van het gebruik van pthread conditions

Dankzij de overeenkomst kan dit opnieuw eenvoudig gewrapt worden in de MFC API.

9.4 Compiler intrinsieke functies

Om op een eenvoudige manier bepaalde atomische operaties te kunnen uitvoeren zijn in de Visual Studio compiler enkele functies ingebouwd. Twee zulke intrinsieke functies die in de HardwareClient meerdere keren voorkomen zijn InterlockedIncrement en Interlocked- Decrement. Deze zullen bij de opgegeven long in een atomische operatie 1 optellen of aftrek- ken. GCC heeft hier gelukkig ook gelijkaardige intrinsieke functies voor. [GCC 2014]

inline long InterlockedIncrement(long * l_ptr ) { return __sync_add_and_fetch(l_ptr, 1); }

inline long InterlockedDecrement(long * l_ptr ) { return __sync_sub_and_fetch(l_ptr, 1); }

Codefragment 9.5: GCC compatibele implementatie van InterlockedIncrement en -Decrement 9.5 Thread Local Storage 48

9.5 Thread Local Storage

Alle threads binnen een proces delen dezelfde virtuele adresruimte. In andere woorden: glo- bale en statische variabelen worden gedeeld, enkel lokale variabelen binnen een functie zijn eigen aan een thread. [Microsoft 2014g] Wanneer je binnen een thread toch globale variabelen wil gebruiken die niet gedeeld worden met andere threads kan je gebruik maken van Thread Local Storage (TLS). Hierbij wordt in het proces eenmalig een “index” (of “sleutel”) toegewezen (met TlsAlloc), waarna elke thread een eigen geheugenlocatie zal alloceren die op te roepen is met die index (TlsGetValue/ TlsSetValue). Deze manier van werken komt grotendeels overeen met de pthread thread-specific data func- tionaliteit [Kerens en Ruttan 2014], waardoor opnieuw een eenvoudige wrapper gemaakt kan worden. De enige aanpassing die gemaakt moet worden in de originele code is het datatype van de index. Bij de Windows TLS is dit een DWORD, bij pthreads is dit een pthread_key_t. We werken dit verschil weg door het type van de index te vervangen met een eigen platfor- mafhankelijke typedef TLS_ID.

# ifdef _WIN32 typedef DWORDTLS_ID; # else typedef pthread_key_t TLS_ID;

inline TLS_ID TlsAlloc() { TLS_ID tmp; pthread_key_create(&tmp,0); return tmp; }

inline void TlsFree(TLS_ID dwTlsIndex) { pthread_key_delete(dwTlsIndex); }

inline void * TlsGetValue(TLS_ID dwTlsIndex) { return pthread_getspecific(dwTlsIndex); }

inline int TlsSetValue(TLS_ID dwTlsIndex, const void* value ) { return pthread_setspecific(dwTlsIndex, value) == 0 ? 1 : 0; } # endif

Codefragment 9.6: Thread Specific Storage wrapper DAEMON 49

Hoofdstuk 10

Daemon

Een daemon is een proces dat in de achtergrond draait, losstaand van de gebruikerssessie, en geen enkele tussenkomst van de gebruiker vereist. Op Windows wordt gesproken van een service. Servertoepassingen worden gewoonlijk op deze manier uitgevoerd, HardwareClient is hier geen uitzondering op. Gezien het gebruik van een NT Service en Unix Daemon grondig verschilt, werd dit keer niet met een wrapper gewerkt. De bestanden met de implementatie (HWClientService.cpp en .h) werden genegeerd en volledig vervangen door de nieuwe implementatie in HWClientDaemon.cpp. Dit bestand bevat nu ook de main functie. Om een proces los te koppelen van de gebruikerssessie kan een aantal keer geforkt worden. Wanneer het ouderproces van een geforkt proces sterft, wordt het “geadopteerd” door het init proces (pid 1). Een proces “daemoniseren” houdt naast forken nog een aantal andere zaken in: ˆ de working directory wordt best veranderd (bijvoorbeeld naar /), zodat geen medium of gebruikersmap onnodig geblokkeerd wordt (“chmod("/");”) ˆ de umask van het proces is na het forken nog altijd gelijk aan die van de gebruiker, deze moet gereset worden (“umask(0);”) ˆ file descriptors (zoals stdin, stdout en stderr) worden beter gesloten en vervangen met een logboek, gezien het proces niet interactief zal zijn ˆ signalen zoals SIG HUP negeren ˆ ... Voor meer informatie zie LINFO 2014 en Lennert 2014. Linux heeft een systeemfunctie daemon(3) die het forken, sluiten van filedescriptors en ver- anderen van de working directory in 1 aanroep op zich neemt. De systeemfunctie syslog(3) kan gebruikt worden om naar het systeemlogboek te schrijven. Nog eenvoudiger is het gebruik van een gespecialiseerde bibliotheek zoals libdaemon1 of een script zoals daemonize2.

1http://0pointer.de/lennart/projects/libdaemon/ 2http://software.clapper.org/daemonize/ 10.1 GetModuleFileName 50

10.1 GetModuleFileName

TCHAR buffer[MAX_PATH] = {0}; GetModuleFileName(NULL, buffer, MAX_PATH);

Codefragment 10.1: Opvragen van het executable pad op Windows

GetModuleFileName is een Windowsfunctie die in de opgegeven buffer het pad naar de op- gegeven bibliotheek plaatst, of indien NULL zoals hierboven, het pad naar de executable van het huidige proces. [Microsoft 2014c] Dit is niet hetzelfde als argv[0], gezien de waarde daarvan kan vervalst worden door bijvoor- beeld een alias. Op Linux bestaat hiervoor geen functie, maar we kunnen wel creatief gebruik maken van het “alles is een bestand” principe. Alle informatie over het huidige proces is terug te vinden in het virtuele bestandssysteem /proc. Hierin vinden we voor elke processid een map terug, en een map self die een symlink is naar het huidige proces. In /proc/self vinden we alle informatie over het huidige proces, waaronder ook een symlink exe naar de executable. We bekomen uiteindelijk dezelfde functionaliteit van GetModuleFileName door te volgen waar de symlink /proc/self/exe naar wijst:

char buffer[MAX_PATH] = {0}; readlink("/proc/self/exe", buffer, MAX_PATH);

Codefragment 10.2: Opvragen van het executable pad op Linux

10.2 Bepalen of de daemon al draait

Om op Linux te bepalen of een proces al draait wordt gewoonlijk gebruik gemaakt van pid- bestanden: kleine bestandjes (doorgaans de naam van het programma met extensie .pid) met daarin het processid (pid) van de draaiende applicatie. Bij het opstarten van het programma wordt geprobeerd van een hardwareclient.pid bestand aan te maken in de hiervoor bedoelde map /run (vroeger /var/run) [Russell, Quinlan en Yeoh 2004].

const char* pid_file_str = "/var/run/hardwareclient.pid"; int pid_file_fd = open(pid_file_str, O_CREAT | O_RDWR, 0644); //exclusive lock (LOCK_EX) + non blocking (LOCK_NB) int lock_error = flock(pid_file_fd, LOCK_EX | LOCK_NB);

if (lock_error && (errno == EWOULDBLOCK)) // instance draait al

Codefragment 10.3: Controlleren of een applicatie al draait

Op dit bestand wordt vervolgens een exclusieve file system lock genomen, of toch geprobeerd (non blocking). Indien het locken faalt weten we dat het programma reeds draait in een ander 10.3 Het Windowsregister 51 proces, en wat het bijhorende pid is (staat in het bestand). Bij succes voert het programma verder normaal uit, bij het afsluiten wordt het pid-bestand uiteindelijk verwijderd. Moest het programma niet proper afsluiten (door bijvoorbeeld een crash), dan kan het ge- beuren dat het pid-bestand niet verwijderd wordt. Dit is waarom we naast het maken van het pid-bestand ook een lock namen: wanneer de eigenaar van de lock wegvalt, dan valt ook de lock weg. Het besturingssysteem zal /run en /var/run bovendien zelf opruimen bij het heropstarten.

10.3 Het Windowsregister

Op Windows worden configuratiegegevens veelal opgeslaan in het register, een soort hi¨erar- chische database. Deze werd door Microsoft ingevoerd in Windows 95 ter vervanging van de INI-bestanden die voorheen gebruikt werden. Het register moest een eenvoudige, veilige en snelle manier worden om een grote hoeveelheid configuratiegegevens voor meerdere gebruikers op een gecentraliseerde manier bij te houden. [Esposito 2000] Op Linux worden nog steeds configuratiebestanden gebruikt, gezien dezelfde problemen daar niet voorkomen: configuratiebestanden hebben een standaardlocatie in /etc, en gebruikers- specifieke configuratiebestanden komen terecht in de eigen home map (centralisatie). Het ingebouwde rechtensysteem verzekert bovendien dat gebruikers enkel configuratiebestanden kunnen aanpassen waar zij rechten op hebben (veiligheid). Een bijkomend voordeel is dat bij corruptie van een configuratiebestand enkel dat bestand getroffen is, terwijl een corrupt register bij Windows kan leiden tot een systeem dat niet opstart. Ook in HardwareClient wordt gebruikt gemaakt van het register, via een CRegistry object. Op Linux werd dit met een wrapper vertaald naar configuratiebestanden. Om de implementatie te vereenvoudigen werd gebruik gemaakt van de LGPL libconfig bibliotheek3.

3http://www.hyperrealm.com/libconfig/ LINK MET MIDDLEWARE 52

Hoofdstuk 11

Link met middleware

11.1 Importeren van functies uit bibliotheken

Wanneer dynamisch gelinkt wordt met bibliotheken, gebeurt die verbinding at runtime in plaats van at compile time. De code wordt bij het compileren dus niet aan de binary toege- voegd, en moet bij het opstarten van het programma “dynamisch” opgespoord worden. Stel dat we een bibliotheek libfoo hebben, met daarin een functie int foo(const char* bar). We kunnen deze op Windows (codefragment 11.1) en Linux (11.2) als volgt oproepen:

typedef int (* foo_t )( const char *); foo_t foo = NULL;

HINSTANCE hDLL = LoadLibrary("libfoo.dll"); if (!hDll) { /*error*/ } foo_t foo = (foo_t) GetProcAddress(hDLL, "foo"); if (!foo) { /*error*/ }

int out = foo("bar"); FreeLibrary(hDLL);

Codefragment 11.1: Dynamische bibliotheek oproepen op Windows

typedef int (* foo_t )( const char *); foo_t foo = NULL;

void *pLib = dlopen("libfoo.so", RTLD_LAZY); if (!pLib) { /*error*/ } foo_t foo = (foo_t) dlsym(pLib, "foo"); if (!foo) { /*error*/ }

int out = foo("bar"); dlclose(pLib);

Codefragment 11.2: Dynamische bibliotheek oproepen op Linux 11.1 Importeren van functies uit bibliotheken 53

Door deze sterke gelijkenis is een eenvoudige herimplementatie van de Windows functies met de Linux varianten mogelijk:

# include

typedef void*HINSTANCE;

void * LoadLibrary( const char* filename ) { void * handle = dlopen(filename, RTLD_LAZY); if (! handle ) fprintf(stderr, "Error loading library "%s": %s\n", filename, dlerror()); return handle ; }

void * GetProcAddress( void *handle , const char * symbol ) { char * error ; dlerror(); /* Clear any existing error */ void * func = dlsym(handle, symbol); if ((error = dlerror()) != NULL) fprintf(stderr, "Error getting symbol "%s" from library: %s\n", symbol, error); return func ; }

int FreeLibrary( void * handle ) { return dlclose(handle); }

Codefragment 11.3: Wrapper voor dynamisch linken

Wanneer deze wrappercode ge¨ıncludeerd wordt v´o´orde code uit fragment 11.1, dan zal deze code bijna zonder aanpassingen werken op Linux. Het spreekt voor zich dat de bestandsnaam van de bibliotheek niet meer hardgecodeerd mag zijn en op Linux naar een bestand met extensie .so moet wijzen, en op Windows naar een bestand met extensie .dll. Opdat dit zou werken moet ook gelinkt worden met de dl bibliotheek, geef hiertoe aan g++ het argument -ldl mee, of voeg het toe aan target link libraries in het CMake project.

11.1.1 set invalid parameter handler

In HardwareClient komt deze code voor:

void myInvalidParameterHandler( const wchar_t * expression, const wchar_t * function , const wchar_t * file , unsigned int line , uintptr_t pReserved) 11.1 Importeren van functies uit bibliotheken 54

{ mprintf(0,0,_T("ERROR myInvalidParameterHandler expression %s function %s file %s line %ld"), expression, function, file, line); }

void SetInvalidParameterHandlerForAllCRT() { HANDLE hProcess = GetCurrentProcess(); HMODULE *hModules; DWORD requiredSize = 0; DWORD secondRequiredSize = 0; if (!EnumProcessModules(hProcess, NULL, 0, &requiredSize)) { return ; } hModules = (HMODULE *)malloc(requiredSize); if (!hModules) return ; if (EnumProcessModules(hProcess, hModules, requiredSize, &secondRequiredSize)) { int i; int loadedModules = min(requiredSize, secondRequiredSize) / sizeof (HMODULE); for (i = 0; i < loadedModules; i++) { void *(WINAPI *_set_invalid_parameter_handler_function)( void *) = ( void *(WINAPI*)( void *)) GetProcAddress ( hModules[i], "_set_invalid_parameter_handler" ); if (_set_invalid_parameter_handler_function != NULL) { _set_invalid_parameter_handler_function(myInvalidParameterHandler); } } } free(hModules); }

Codefragment 11.4: Instellen van een invalid parameter handler op Windows

EnumProcessModules (dl iterate phdr(3) op Linux) itereert over alle ingeladen modules (een Microsoft term voor shared objects), de invalid parameter handler wordt op elke module geinstalleerd. Deze callback wordt opgeroepen bij ongeldige parameters. Op Linux bestaat deze callback handler niet, daar is het gebruikelijk om bij het oproepen van functies de returncode te bekijken, en in geval van een fout te kijken of errno gelijk is aan EINVAL. Gezien de gebruikte handler enkel een foutboodschap print en er geen Linux alternatief be- staat, werd deze code op Linux uitgeschakeld. Bij verder onderzoek zou kunnen nagegaan worden of bij dit soort fouten op Linux geen C signalen (zoals SIGILL) gebruikt kunnen worden. 11.2 Headerconflicten 55

11.2 Headerconflicten

Gezien de Dialogic en Aculab bibliotheken erg gelijkaardige dingen doen is het misschien niet verbazend dat in de headers af en toe dezelfde namen voor constanten gebruikt worden. Zo vinden we in cl lib.h (van Aculab) #define UNKNOWN SERVICE 0xff, en in isdncmd.h (van Dialogic) #define UNKNOWN SERVICE 0x00. Dit is een onmogelijke situatie, een symbool kan niet twee verschillende waarden hebben. In de Windowsversie van HardwareClient is dit door MyForce opgelost door een eigen bewerkte kopie van de headers bij te houden, waarin ´e´envan de twee tegenstrijdige symbolen hernoemd is (het symbool in de Dialogic header heet nu bijvoorbeeld D UNKNOWN SERVICE).1 De Windows- en Linuxheaders komen jammer genoeg niet 1:1 overeen (meer/minder inhoud, andere bestandsnaam, . . . ), dus we kunnen de bewerkte headers niet gewoon overnemen voor de Linuxversie. Om de benamingsaanvaringen te vinden zit er niets anders op dan de code telkens opnieuw te proberen compileren en aan de hand van de foutmeldingen de overeenkomstige defines en hun aanpassingen te vinden in de Windowsheaders. Omdat de headers zelf ook een identieke bestandsnaam kunnen hebben, worden alle headers gekopieerd (met aanpassingen) naar een eigen aculab of dialogic map. hw_include/ |-- aculab `-- dialogic We verwijderen uiteindelijk de verwijzingen naar de standaardlocatie van deze headers uit het CMake projectbestand, en vervangen dit met een verwijzing naar de bovenliggende map van onze nieuwe headermappen (hw include): INCLUDE_DIRECTORIES(../3rdparty_linux/hw_include/) Bij het includeren van deze headers in de code moet nu ook de gewenste submap vermeld worden: #include wordt #include . Deze mappenstruc- tuur werd op Windows ook gebruikt, waardoor de bestaande “#include”-vermeldingen in de code gelijk blijven.

11.3 Ontbrekende functionaliteit

Zoals eerder al vermeld zijn de Windows en Linux versies van de middleware niet identiek. De Linux versie bevat veel nieuwe features die niet in de Windows versie zitten, en de Windows versie bevat dan weer een aantal oudere features die niet meer in de Linux versie zitten. Sommige zaken werken zelfs op een volledig andere manier. De documentatie nalezen (zie ook Dialogic Research 2008) is hierbij een belangrijke hulp.

1Dit mag omdat de middleware bibliotheken al gecompileerd zijn met juiste waarden, de naam van het symbool in de header maakt voor de binaire bibliotheken niets meer uit. 11.3 Ontbrekende functionaliteit 56

11.3.1 Dialogic file API

Voor file IO bootst Dialogic op Windows de Unix file API na. Deze functies worden in HardwareClient aan de hand van de PDLL DECLARE FUNCTION en MyPDLL INITIALIZE FUNCTION macro’s uit een Dialogic DLL ingeladen. Op Linux moeten deze uitgeschakeld en vervangen worden met de native system calls:

# ifdef _WIN32 PDLL_DECLARE_FUNCTION3(int , dx_fileopen, const char*, int , int) PDLL_DECLARE_FUNCTION1(int , dx_fileclose, int) PDLL_DECLARE_FUNCTION3(int , dx_fileseek, int , long , int) PDLL_DECLARE_FUNCTION3(int , dx_fileread, int , void *,UINT) PDLL_DECLARE_FUNCTION3(int , dx_filewrite, int , void*,UINT) PDLL_DECLARE_FUNCTION0(int , dx_fileerrno) ... MyPDLL_INITIALIZE_FUNCTION(dx_fileopen) MyPDLL_INITIALIZE_FUNCTION(dx_fileseek) MyPDLL_INITIALIZE_FUNCTION(dx_fileclose) MyPDLL_INITIALIZE_FUNCTION(dx_fileread) MyPDLL_INITIALIZE_FUNCTION(dx_filewrite) MyPDLL_INITIALIZE_FUNCTION(dx_fileerrno) # else #include #include #include #include

inline int dx_fileopen( const char * pathname , int flags , int mode ) { return open(pathname, flags, mode); }

inline int dx_fileclose(int fd) { return close (fd ); }

inline int dx_fileseek(int fd , long offset , int whence ) { return lseek(fd, offset, whence); }

inline int dx_fileread(int fd , void *buf, UINT count) { return read(fd, buf, count); }

inline int dx_filewrite(int fd , const void *buf, UINT count) { return write(fd, buf, count); } 11.3 Ontbrekende functionaliteit 57

inline int dx_fileerrno() { return errno ; } # endif // win32

Codefragment 11.5: Dialogic file API

11.3.2 NCM API

Op Windows kunnen Dialogic PCI kaarten geconfigureerd worden met de Native Configuration (NCM) API. Deze API is niet beschikbaar op Linux. De objectgeori¨enteerde OA&M API kan voor de meeste gevallen een alternatief bieden. [Dialogic Research 2008] Gezien ondersteuning voor hardwarekaarten niet tot de masterproefopdracht behoorde, wer- den aanroepen van deze API uitgeschakeld met #ifdefs.

11.3.3 ALGO LOUD

int dcbvaluep = ALGO_LOUD; // The new loudest talker algorithm, // default is ALGO_LONG if (pHardware->dcb_setbrdparm(dcbboarddevh, MSG_ALGORITHM, &dcbvaluep) == -1) { mprintf(0,0,_T("Error in dcb_setbrdparm!")); }

Codefragment 11.6: Ontbreekt: ALGO LOUD

Het symbool ALGO LOUD wordt op Linux niet gedefinieerd, MSG ALGORITHM wel, maar wordt in de documentatie niet meer vermeld als geldige parameter. Gezien het niet meer relevant is (en het luidste algoritme toch standaard geselecteerd wordt) werd deze code ook uitgeschakeld. [Dialogic 2007; Intel 2006]

11.3.4 SR MODELTYPE

int mode = SR_POLLMODE; # ifndef __linux__ int iRC = m_pHardware->sr_setparm(0, SRL_DEVICE, SR_MODELTYPE, &mode); # else int iRC = m_pHardware->sr_setparm(0, SRL_DEVICE, SR_MODEID, &mode); # endif

Codefragment 11.7: Ontbreekt: SR MODELTYPE

SR MODELTYPE bestaat enkel op Windows, SR MODEID enkel op Linux. Vermoedelijk doen ze in het geval van sr setparm hetzelfde, maar dit zou moeten getest worden. 11.3 Ontbrekende functionaliteit 58

Vreemd genoeg wordt SR POLLMODE in de API documentatie niet als geldige optie van SR MODELTYPE vermeld, maar wel van SR MODEID, wat doet vermoeden dat de Win- dowscode ongeldig is. SR MODEID zo instellen kan alvast geen kwaad, gezien het de stan- daardwaarde is. [Dialogic 2008]

11.3.5 SWMODE CTBUS SCBUS

De enumwaarde SWMODE CTBUS SCBUS, gedefinieerd in sw lib.h komt niet voor op Linux. Ook hier werd de code die dit gebruikte uitgeschakeld, en vervangen met een waarschuwingsbood- schap op de console.

Figuur 11.1: Windows (Visual Studio) links, Linux (Qt Creator) rechts STRUIKELBLOKKEN 59

Hoofdstuk 12

Struikelblokken

12.1 64-bit

De masterproef werd ontwikkeld op een 64-bit editie van CentOS, waardoor ook een 64-bit versie van HardwareClient onderzocht werd. Hoewel een groot deel van de code base al door MyForce 64-bit compatibel gemaakt werd, was dit voor HardwareClient zelf nog niet het geval. Na het ontdekken van meerdere problemen met compileren naar 64-bit (zie verder), werd de port toch beperkt tot enkel een 32-bit versie.

12.1.1 M X64

In de code wordt onderscheid gemaakt tussen 64- en 32-bit Windows aan de hand van het M X64 symbool, dat enkel gedefinieerd is voor 64-bit. Dit symbool wordt enkel door Visual Studio gebruikt, op Linux wordt amd64 gebruikt. Wanneer we deze code ook op Linux onder 64-bit willen activeren moeten we M X64 zelf defini¨eren. Dit kan in CMake, of in de code op een plaats v´o´orde andere code gelezen wordt:

#if defined(__amd64) && !defined(_M_X64) #define _M_X64 # endif

Codefragment 12.1: M X64 defini¨eren

12.1.2 Pointer als int

Op 32-bit systemen past een pointer in een int (beide 32 bits groot), maar op 64-bit systemen is een pointer dubbel zo groot. Wanneer je toch een pointer adres in een integer wil opslaan kan je het type intptr t gebrui- ken, deze is gegarandeerd altijd groot genoeg voor een pointer. 12.2 Het bestandssysteem 60

12.1.3 sizeof(long)

Het grootste (onverwachte) probleem kwam met long. Wie een achtergrond in Java heeft, denkt misschien dat de standaardtypes ook in C++ als volgt gedefinieerd zijn: char = 1B short = 2B int = 4B long = 8B Dit blijkt in C/C++ allesbehalve zo te zijn! Het enige dat in verband met deze types in de standaard vastligt is hun onderlinge relatie: char <= short <= int <= long Op 32-bit systemen (zowel Linux als Windows) is een int in C++ bijvoorbeeld even groot als een long: beide zijn 4 bytes, of 32 bits groot. Het probleem doet zich nu voor op 64-bit systemen, want daar verschillen Windows en Linux. Windows volgt de LLP64 standaard (long is 4 bytes) [Microsoft 2014a], Linux (net als de andere Unixen) volgt de LP64 standaard (long is 8 bytes) [Adiga 2006].

Tabel 12.1: De grootte van een long

architectuur 32 bit 64 bit sizeof(long) op Windows 4 byte 4 byte sizeof(long) op Linux 4 byte 8 byte

In plaats van deze onduidelijk gedefinieerde types te gebruiken is het beter (zeker wanneer het gaat om gegevens die over het netwerk uitgewisseld moeten worden) om types als int16 t, int32 t en int64 t te gebruiken, gezien daar geen ambigu¨ıteit meer bestaat rondom de grootte.

12.2 Het bestandssysteem

Paden op het web en op Unix gebruiken een slash (/) als padseparator, terwijl Windows door historische omstandigheden een backslash (\) gebruikt.1 Al sinds de DOS jaren wordt gelukkig intern stiekem ook de slash ondersteund voor paden, om compatibiliteit met Unix te behou- den. #include werkt enkel op Windows, maar als je het herschrijft als: #include , dan werkt het zonder problemen op zowel Windows als Linux. Een ander probleem waarmee rekening gehouden moet worden is dat Windows een hoofd- letterongevoelig bestandssysteem gebruikt, in tegenstelling tot Linux. Wanneer in de code #include "Socks.H" staat, maar de header eigenlijk SOCKS.H heet, dan zal dat op Linux niet werken. Bij elke include (of ander pad) zal moeten nagegaan worden of de schrijfwijze over- eenkomt met de effectieve bestandsnaam. 1De ontwikkelaars van DOS2.0 (de eerste versie die mappen ondersteunde) wouden een slash (/) gebruiken zoals in Unix, maar konden dat niet omdat ze de slash al gebruikten voor commandolijnvlaggen. Daarom werd dan uiteindelijk de visueel gelijkaardige backslash (\) gekozen. [Osterman 2005b] 12.3 Striktheid van de compiler 61

12.3 Striktheid van de compiler

De Microsoft Visual Studio compiler is een stuk minder strikt dan GCC, waardoor we bij het compileren op Linux nog een groot aantal syntaxfouten kunnen vinden.

class voorbeeld { private : int index ; public : voorbeeld (int _index) : index(_index) {}; voorbeeld(voorbeeld &vb) { index = vb.index; }; ˜voorbeeld() {};

void voorbeeld::doe_iets() { index ++; }; voorbeeld & operator = (voorbeeld& rval) { index = rval.index; }; };

Codefragment 12.2: Code die enkel in Visual Studio compileert

class voorbeeld { private : int index ; public : voorbeeld (int _index) : index(_index) {} voorbeeld ( const voorbeeld &vb) { index = vb.index; } ˜voorbeeld(){}

void doe_iets () { index ++; } voorbeeld & operator = ( const voorbeeld& rval) { index = rval.index; return *this ; } };

Codefragment 12.3: Code die overal compileert

In codefragment 12.2 staan vier fouten (verbeterd in 12.3) die in Visual Studio succesvol compileren, maar een warning of fout opleveren in GCC: ˆ de definitie van constructors en methodes mag niet eindigen op een puntkomma 12.4 Structured Exception Handling 62

ˆ bij de declaratie/definitie van methoden binnen de declaratie van een klasse mag je de naam van die klasse niet als extra qualifier gebruiken ˆ het argument van de operator= overload moet const zijn ˆ als je een returntype hebt moet je ook iets teruggeven!

12.4 Structured Exception Handling

Om crashes op te vangen worden op Linux signals zoals SIGSEGV gebruikt, op Windows gaat dit nog iets verder. Structured Exception Handling (SEH) is een implementatie van exception handling binnenin de Windowskernel, en kan zo fouten opvangen (met try, except en finally) van zowel user land als kernel land. Dit systeem wordt beschermd door een patent en is onder andere hierdoor niet ge¨ımplementeerd op Linux. [Miller 2006]

12.5 Problemen met macro’s

Foutmeldingen van C++ compilers durven niet altijd even hulpvol te zijn, zeker bij het gebruik van macro’s. Neem nu bijvoorbeeld volgende lijn code:

PDLL_DECLARE_FUNCTION1(long , sr_waitevt, long )

Codefragment 12.4: Een macro aanroep

De compiler faalt hierop met volgende fout: error: expected ')' before '*' token error: 'TYPE_sr_waitevt' does not name a type Verwarrend, gezien op die lijn helemaal geen '*' of 'TYPE_sr_waitevt' staan. Deze zijn natuurlijk afkomstig van PDLL_DECLARE_FUNCTION1, een macro die als volgt gedefinieerd is:

# define PDLL_DECLARE_FUNCTION1(retVal, FuncName, Param1) \ typedef retVal (CALLBACK*TYPE_##FuncName)(Param1); \ TYPE_##FuncName m_##FuncName; \ retVal FuncName(Param1 p1) const \ {\ if (m_dllHandle && (NULL != m_##FuncName)) \ return m_##FuncName(p1); \ else \ return (retVal)NULL; \ }

Codefragment 12.5: De macrodefinitie 12.5 Problemen met macro’s 63

“##” is macro syntax voor een concatenatie. Het enige opvallende aan deze code is misschien de CALLBACK. Volgens Visual Studio is dit op Windows gedefinieerd als __stdcall, iets dat op Linux niet betekenisvol is en gewoon mag worden weggelaten. Volgens QtCreator is CALLBACK leeg gedefinieerd. Alles ziet er dus ok uit. Waarom faalt het dan? Macro’s maken het moeilijk om te zien wat er precies gecompileerd wordt. We weten met geen enkele zekerheid op welk deel van de code de compiler faalt, gezien de volledige inhoud van de macro hetzelfde lijnnummer krijgt. Het zou handiger zijn moesten we de code hebben met de macro’s reeds ge¨expandeerd, of in andere woorden, het resultaat van de preprocessor. Gelukkig kunnen we de C++ preprocessor op Linux afzonderlijk (zonder compiler) uitvoeren met het commando cpp. We krijgen volgend resultaat:

typedef long (CALLBACK*TYPE_sr_waitevt)( long ); TYPE_sr_waitevt m_sr_waitevt; long sr_waitevt( long p1) const { if (m_dllHandle && (__null != m_sr_waitevt)) return m_sr_waitevt(p1); else return (long ) __null ; }

Codefragment 12.6: De macroaanroep na de preprocessor

Het valt direct op dat CALLBACK niet vervangen is. De code compileren bevestigt dat dit ook weldegelijk de regel is die de fout veroorzaakt. Het woord verwijderen lost het probleem op. Het is vreemd dat de QtCreator IDE beweerde dat dit toch al gedefinieerd was, maar de propere oplossing komt alvast neer op het toevoegen van volgende lege definitie:

# ifdef __linux__ #define CALLBACK # endif

Codefragment 12.7: Een lege define

12.5.1 Slechte ifdefs

Slecht gekozen #ifdef voorwaarden kunnen soms tot heel moeilijk te vinden fouten leiden. Ergens verborgen in een header stond bijvoorbeeld het volgende stukje code:

# ifdef _WIN32 ... # else # define short int # endif

Codefragment 12.8: Defines kunnen ook voor problemen zorgen 12.6 Het nut van typename bij templates 64

Blijkbaar is er ooit ondersteuning voor een platform toegevoegd dat geen short datatype had. Deze #define werd ook voor Linux actief, omdat deze uitzondering voor alles dat niet Windows is werd gedefinieerd, met honderden onbegrijpbare fouten tot gevolg. Het is belangrijk om goed na te denken over de voorwaarde die gebruikt moet worden. Dit was een goede les om duidelijk te maken dat bij het toevoegen van uitzonderingen voor Linux beter geschreven wordt wat bedoeld wordt, dus #ifdef linux in plaats van #ifndef WIN32. Ook het vermelden waard: een #endif mag bij GCC door niets (buiten commentaar) gevolgd worden. Dit is dus fout: #endif _WIN32 Maar dit is ok: #endif //_WIN32

12.6 Het nut van typename bij templates

We kennen typename als zijnde een duidelijker sleutelwoord dan class om te gebruiken bij het defini¨eren van templates (template ). Dit is echter niet het enige nut van dit woord. Neem als voorbeeld deze implementatie van de Lookup methode van CMap:

299 BOOL Lookup( const K& key, V& rValue) const 300 { 301 std::map::const_iterator it = this ->find(key); 302 if (it == this ->end ()) 303 return FALSE; 304 rValue = *(it->second); 305 return TRUE; 306 }

Codefragment 12.9: Problematische implementatie CMap::Lookup

Dit lijkt juist, maar het compileert niet: afx.h: In member function 'BOOL CMap::Lookup(K&, V&) const': afx.h:301: error: expected ';' before 'it' afx.h:302: error: 'it' was not declared in this scope afx.h:304: error: 'it' was not declared in this scope De compiler heeft problemen met het type std::map::const_iterator omdat dit een gekwalificeerde afhankelijke naam is (het geeft de namespace aan, en is afhankelijk van nog in te vullen templates). De C++ parser kan de const_iterator zowel interpreteren als een type, als een variabele met die naam, en weet hierdoor niet wat het moet doen. Bij gekwalificeerde afhankelijke namen die verwijzen naar een type moet verplicht typename gebruikt worden om deze ambigu¨ıteit te voorkomen: [Driscoll 2014] 12.6 Het nut van typename bij templates 65

301 typename std::map::const_iterator it = this ->find(key);

Codefragment 12.10: ’typename’ voor een gekwalificeerd afhankelijk type

Voor een uitgebreidere uitleg over de precieze regels rondom typename verwijs ik graag naar de uitstekende uitleg in Evan Driscoll (2014). A Description of the C++ typename keyword. url: http://pages.cs.wisc.edu/∼driscoll/typename.html (bezocht op 23-04-2014). VARIA 66

Hoofdstuk 13

Varia

13.1 Het aantal elementen van een array op de stack

Microsoft voorziet een macro countof(array) waarmee het aantal elementen van een array die op de stack staat kan worden opgevraagd. Bij een array op de heap is deze informatie natuurlijk niet beschikbaar. Klassiek wordt dit ge¨ımplementeerd door de grootte van de array te delen door de grootte van een element: sizeof(array) / sizeof(*(array)). Dit voorziet wel geen enkele beveiliging tegen het meegeven van een pointer naar de heap. C++11 introduceert een veiligere manier van werken:

# define _countof(array) std::extent::value

Codefragment 13.1: Een macro voor de grootte van een array op de stack

13.2 StrFormatByteSize

In het project vinden we volgende helperfunctie die een gegeven aantal bytes omzet naar een leesbare string (2356 wordt “2.30 kB”):

CString CStaticTools::FormatBytes(DWORD dwBytes) { TCHAR szSize[100]; if (StrFormatByteSize(dwBytes, szSize, 100)) return CString(szSize); else return CString (); }

Codefragment 13.2: FormatBytes maakt gebruik van StrFormatByteSize 13.3 AfxIsValidAddress 67

De gebruikte functie StrFormatByteSize bestaat niet op Linux, noch is er een gelijkaardig alternatief. Verder moet ook rekening gehouden worden met het feit dat de gebruikte eenheden een andere betekenis kunnen hebben afhankelijk van de doelgroep. Op Windows wordt met kilo nog steeds 210 bedoeld, terwijl op Linux en Mac OS tegenwoordig de ISO betekenis 103 gebruikelijker is. In de uiteindelijke implementatie werden beide opties voorzien:

# define USE_ISO_UNITS false

const char* old_form[] = {"%.0f B", "%.2f kiB", "%.2f MiB", "%.2f GiB", "%.2f TiB", "%.2f EiB"}; const char* iso_form[] = {"%.0f B", "%.2f kB", "%.2f MB", "%.2f GB", "%.2f TB", "%.2f EB"};

CString FormatBytes(DWORD dwBytes, bool use_iso = USE_ISO_UNITS) { char buff [100];

const char ** form ; short groep ; short base ; double nullen ;

if ( use_iso ){ nullen = log10(dwBytes); base = 10; groep = 3; form = iso_form; } else { nullen = log2(dwBytes); base = 2; groep = 10; form = old_form; } int orde = nullen / groep; if (orde > 5) orde = 5;

sprintf(buff, form[orde], dwBytes/pow(base,orde*groep));

return CString(buff); }

Codefragment 13.3: Implementatie CStaticTools::FormatBytes (StrFormatByteSize)

13.3 AfxIsValidAddress

MFC heeft een functie die volgens de documentatie hetvolgende doet: (AfxIsValidAddress) tests any memory address to ensure that it is contained entirely within the program’s memory space. [Microsoft 2014b] 13.3 AfxIsValidAddress 68

Dit klinkt als een geweldige feature om te hebben, en wordt in HardwareClient ook meer- maals gebruikt. Wie echter in de broncode van MFC gaat kijken hoe deze complexe functie ge¨ımplementeerd is, vindt het volgende:

BOOL AfxIsValidAddress( const void* p, size_t nBytes, BOOL bReadWrite) { (bReadWrite); ( nBytes ); return (p != NULL ); }

Codefragment 13.4: Microsofts implementatie van AfxIsValidAddress

Een nullpointer controle, meer niet. Ontgoochelend, maar eenvoudig om over te nemen. Het moet niet altijd moeilijk zijn. CONCLUSIE 69

Hoofdstuk 14

Conclusie

C++ is in principe een platformonafhankelijke “write once, compile everywhere” taal, zolang je enkel gebruik maakt van de STD bibliotheek en eventuele extra platformonafhankelijke bibliotheken. De STD is echter vrij jong, en zelfs vandaag nog altijd vrij beperkt in functi- onaliteit in vergelijking met gelijkaardige bibliotheken in andere talen. Dat MyForce MFC gebruikt, een ouder en veel uitgebreider alternatief voor de STD, is dus niet onbegrijpelijk te noemen. Het is simpelweg teleurstellend dat het zo lang heeft moeten duren om belangrijke zaken zoals datastructuren, multithreading, timing en Unicode in de C++ standaard op te nemen. De toekomst lijkt gelukkig rooskleuriger nu ook sockets in de standaard zouden worden opgeno- men. Een groot project in pure C++ STD is veel haalbaarder dan ooit tevoren. Niet dat het gebruik van de STD vereist is: er zijn ook goede platformonafhankelijke MFC alternatieven beschikbaar, zoals wxWidgets en Qt. Een bestaand project van in de honderduizend lijnen code – zoals HardwareClient – wordt echter niet zomaar herschreven. De keuze voor het schrijven van wrappers die de bestaande code zonder (of met een minimum aan) aanpassingen op Linux doen werken, bleek een uit- stekende oplossing die in praktijk goed werkte. Naast het vervangen van platformafhankelijke bibliotheken moest ook opgelet worden met datatypes. Integers en longs hebben niet altijd dezelfde grootte, het is dus aan te raden om goed gedefinieerde types als int32 t en int64 t te gebruiken in plaats van int en long. Ook tekst was gevaarlijk, meer bepaald het gebruik van Unicode. Er is geen encodering die zowel op Windows als Linux gebruikt wordt, waardoor er altijd conversies nodig zijn. De omvang van de opdracht bleek al snel veel groter dan verwacht en werd waar mogelijk wat vereenvoudigd (zoals het weglaten van GUI). Het meeste werk bleef de onoverkomelijke MFC herimplementatie. Het beoogde doel (een werkende Linuxversie van HardwareClient) werd niet bereikt, maar het overgrote deel hiervan werd alsnog behandeld. De opdracht was groot en de vele lange zoektochten naar zowel de oorzaak als de oplossing van probleem na probleem kon met momenten bijzonder frustrerend zijn, maar het was een ongelooflijk interessant proces en een bijzonder leerrijke ervaring. INSTALLATIE DRIVERS/SDK 70

Bijlage A

Installatie drivers/sdk

A.1 Dialogic HMP

In een superuser sessie na het uitpakken van lnxHMP_4.1_161.tgz: # ./install.sh The 32-bit libstdc++ package, required by HMP, was not found. Please correct the issue before attempting HMP installation again.

# yum install libstdc++.i686 # ./install.sh

Installing Dialogic(R) HMP Software Release 4.1LIN Redistributable Runtime Please wait, installation is in progress...

Initializing install, please wait ......

======Dialogic(R) HMP Software Release 4.1LIN Redistributable Runtime INSTALLATION

You will now have the opportunity to install software packages. After the menu is displayed, enter the package number(s) of the desired packages, separated by a space. Enter A for all packages, Q to quit.

Package dependencies will be automatically resolved during installation. For example, selecting a single package will automatically install all packages required for that selection.

Press ENTER to display the menu of packages: A.1 Dialogic HMP 71

Item Package Description ------1 Dialogic(r) Host Media Processing Software (261 MB) 2 Dialogic(r) DNI Boards & HMP Software (328 MB)

A Install All (328 MB) Q Quit Installation

Enter the packages you want to install, separated by a space, or [A,a,Q,q]: A

Package installation order (including dependencies): lsb-dialogic-hmp41-com lsb-dialogic-hmp41-x64com lsb-dialogic-hmp41-dmdev lsb-dialogic-hmp41-hmp lsb-dialogic-hmp41-x64hmp lsb-dialogic-hmp41-t1e1 lsb-dialogic-hmp41-lic lsb-dialogic-hmp41-docs

Checking for previously installed packages: None found.

Checking for sufficient disk space (need 328 MB): OK After installation, the filesystem containing the Dialogic(R) Software will be 32% full.

Installing or upgrading 8 needed packages:

Preparing... ########################################### [100%] 1:lsb-dialogic-hmp41-lic ########################################### [ 13%] 2:lsb-dialogic-hmp41-docs########################################### [ 25%] 3:lsb-dialogic-hmp41-com ########################################### [ 38%] Building the drivers... 4:lsb-dialogic-hmp41-hmp ########################################### [ 50%] 5:lsb-dialogic-hmp41-x64h########################################### [ 63%] 6:lsb-dialogic-hmp41-x64c########################################### [ 75%] 7:lsb-dialogic-hmp41-t1e1########################################### [ 88%] Building the ctimod and mercd drivers... 8:lsb-dialogic-hmp41-dmde########################################### [100%] Installation successful.

Press ENTER to continue: A.1 Dialogic HMP 72

Item Package Description ------1 Dialogic(r) Host Media Processing Software (261 MB) 2 Dialogic(r) DNI Boards & HMP Software (328 MB)

A Install All (328 MB) Q Quit Installation

Enter the packages you want to install, separated by a space, or [A,a,Q,q]: q

Installation of Dialogic(R) HMP Software Release 4.1LIN Redistributable Runtime was successful.

Installing media server prompt package Preparing... ########################################### [100%] 1:lsb-dialogic-hmp41-msp ########################################### [100%]

Do you wish to install the Software Development Kit [y/n] ? y

Installing Dialogic(R) HMP Software Release 4.1LIN Software Development Kit

Preparing... ########################################### [100%] 1:lsb-dialogic-hmp41-sdk ########################################### [100%] Installation successful.

Installation of Dialogic(R) HMP Software Release 4.1LIN Software Development Kit was successful.

Do you wish to install the Software Demo Kit [y/n] ? y

Installing Dialogic(R) HMP Software Release 4.1LIN Software Demo Kit

Preparing... ########################################### [100%] 1:lsb-dialogic-hmp41-demo########################################### [100%] Installation successful.

Installation of Dialogic(R) HMP Software Release 4.1LIN Software Demo Kit was successful.

Do you wish to configure the SNMP Agent Listening Port (default is 161) [y/n] ? y Please Enter SNMP Agent Listening Port? 161 SNMP port 161 accepted. Do you wish to configure the CLI Agent Telnet Port (default is 23) [y/n] ? n A.1 Dialogic HMP 73

Installation of Dialogic(R) HMP Software Release 4.1LIN successfully completed.

------NOTE: ------(1) The default Verification License has been automatically installed. -- -- (2) The Dialogic(R) system services will automatically start every -- -- time the system is rebooted. To start and stop system services -- -- manually, use the dlstop and dlstart scripts found in -- -- /usr/dialogic/bin. -- -- (3) For administration via SNMP, use any SNMP browser, library or tools-- -- (4) For administration via CLI, use the telnet command -- -- (5) For details, refer to documentation ------NEXT STEPS: -- -- (1) Before using the software, you MUST do one of the following: -- -- a) Reboot the system. -- OR ------b) Do the following: -- -- (i) Ensure that the environment variables are set by performing -- -- the following action: -- -- - Logout and login -- -- (ii) Start the Dialogic(R) system services manually using the -- -- dlstart script found in /usr/dialogic/bin. -- -- (2) Configure an appropriate license (higher density) via SNMP or CLI -- -- (3) If you are using PSTN, refer documentation on how to configure -- -- PSTN via SNMP or CLI ------A.2 Aculab 74

A.2 Aculab

In een superuser sessie na het uitpakken van AIT_2.0.21.tgz: # yum install libXext.i686 libz.i686 # ./AIT_GUI

We klikken de boodschap weg, en kiezen in het menu File → New Package . . .

In het verschenen venster vullen we bij Package Name (A) een naam in naar eigen keuze, en selecteren we bij Base Distributions de Linux versie (B). A.2 Aculab 75

We openen nu de aangemaakte package via File → Open Package . . .

Het openen kan even duren, maar daarna worden we gepresenteerd met een lijst van compo- nenten. Rechtsklik, kies select all, en klik op install. A.2 Aculab 76

Accepteer de EULA, en wacht tot de installatie eindigt.

De installatie is nu compleet en terug te vinden in /usr/local/aculab/v6. BIBLIOGRAFIE 77

Bibliografie

Aculab (2012). About. url: http://www.aculab.com/about/ (bezocht op 23-04-2012). – (2014). Prosody Guide - how to build applications to use Prosody. url: http://www.aculab. com/support/pdf documents/v6 linux/TiNG/pubdoc/howto buildapp.html (bezocht op 23-04-2014). Adiga, Harsha S. (2006). Porting Linux applications to 64-bit systems. IBM. url: http : //www.ibm.com/developerworks/library/l-port64/. Blaszczak, Mike (1997). Professional MFC with Visual C++ 5.0. Wrox Press Ltd. Hfdstk. Ap- pendix E: A History of MFC. url: http://cygnus.redirectme.net/ProMFC 5/ch24 1.htm (bezocht op 30-04-2014). Busatto, Fabio (2007). TCP keep-alive HOWTO. url: http : / / tldp . org / HOWTO / TCP - Keepalive-HOWTO/programming.html. C++ Standards Committee, The (2005). Library Extension TR2 Call for Proposals. url: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1810.html. Dialogic (2014a). About Us. url: http://www.dialogic.com/en/company/about-us.aspx (bezocht op 23-04-2014). – (2014b). PowerMedia HMP for Linux. url: http://www.dialogic.com/products/media- server-software/hmp-software/hmp-linux.aspx (bezocht op 23-04-2014). – (2007). Dialogic® Audio Conferencing API - Library Reference. url: https : / / www . dialogic.com/∼/media/manuals/docs/audio conf api v5.pdf. – (2008). Dialogic® Standard Runtime Library API - Library Reference. url: http://www. dialogic.com/∼/media/manuals/docs/srl api v6.pdf. Dialogic Research, Inc. (2008). Creating Telephony Applications for Both Windows® and Linux: Principles and Practice. url: http://www.dialogic.com/∼/media/products/docs/ media-server-software/9754 Create Tel Apps for Win Linux an.pdf. Driscoll, Evan (2014). A Description of the C++ typename keyword. url: http://pages.cs. wisc.edu/∼driscoll/typename.html (bezocht op 23-04-2014). Esposito, Dino (2000). Windows 2000 Registry: Latest Features and APIs Provide the Power to Customize and Extend Your Apps. url: http://msdn.microsoft.com/en-gb/magazine/ bb985037.aspx (bezocht op 28-04-2014). Gammo, Louay e.a. (2004). Comparing and Evaluating epoll, select, and poll Event Mecha- nisms. url: https://www.kernel.org/doc/ols/2004/ols2004v1-pages-215-226.pdf. GCC (2014). Built-in functions for atomic memory access. url: http : / / gcc . gnu . org / onlinedocs / gcc - 4 . 4 . 7 / gcc / Atomic - Builtins . html # Atomic - Builtins (bezocht op 28-02-2014). Gordon, Ryan (2014). Getting Started with Linux Game Development. When in doubt, stub it out. url: https://icculus.org/SteamDevDays/SteamDevDays2014-LinuxPorting.pdf. BIBLIOGRAFIE 78

Guha, et al. (2008). RFC 5382 - NAT Behavioral Requirements for TCP. url: http://tools. ietf.org/html/rfc5382. Habets, Thomas (2010). gettimeofday() should never be used to measure time. url: http: //blog.habets.pp.se/2010/09/gettimeofday-should-never-be-used-to-measure-time (bezocht op 05-03-2014). Intel (2006). Intel NetStructure® Host Media Processing Software Release 1.2 for Linux. url: http://www.dialogic.com/∼/media/manuals/hmp12lin/release update.pdf. Internet Engineering Task Force (1989). RFC 1122 - Requirements for Internet Hosts – Com- munication Layers. url: http://tools.ietf.org/html/rfc1122. Kerens, Guy en Arden Ruttan (2014). “Private” thread data - Thread-Specific Data. url: http://www.cs.kent.edu/∼ruttan/sysprog/lectures/multi- thread/multi- thread. html#thread tss (bezocht op 27-04-2014). Kohlhoff, Christopher (2006). Networking Library Proposal for TR2. url: http://www.open- std.org/jtc1/sc22/wg21/docs/papers/2006/n2054.pdf. – (2007). Networking Library Proposal for TR2 (Revision 1). url: http://www.open-std. org/jtc1/sc22/wg21/docs/papers/2007/n2175.pdf. – (2012). Networking Library Status Report. url: http://www.open-std.org/jtc1/sc22/ wg21/docs/papers/2012/n3360.pdf. Lennert, Dave (2014). How To Write a UNIX Daemon. Hewlett-Packard Company. url: http://cjh.polyplex.org/software/daemon.pdf (bezocht op 16-04-2014). LINFO (2014). Daemon Definition. url: http://www.linfo.org/daemon.html (bezocht op 17-04-2014). Microsoft (2014a). Abstract Data Models. url: http://msdn.microsoft.com/en-us/library/ windows/desktop/aa384083(v=vs.85).aspx (bezocht op 23-06-2014). – (2014b). AfxIsValidAddress. url: http://msdn.microsoft.com/en-us/library/s240k6fz. aspx (bezocht op 31-03-2014). – (2014c). GetModuleFileName function. url: http://msdn.microsoft.com/en-us/library/ windows/desktop/ms683197(v=vs.85).aspx (bezocht op 09-05-2014). – (2014d). MFC Desktop Applications. url: http://msdn.microsoft.com/en-us/library/ d06h2x6e.aspx (bezocht op 30-04-2014). – (2014e). MVC CCriticalSection. url: http : / / msdn . microsoft . com / en - us / library / h5zew56b(v=vs.80).aspx (bezocht op 03-2014). – (2014f). SIO KEEPALIVE VALS control code. url: http://msdn.microsoft.com/en- us/library/windows/desktop/dd877220(v=vs.85).aspx (bezocht op 25-06-2014). – (2014g). Thread Local Storage. url: http : / / msdn . microsoft . com / en - us / library / windows/desktop/ms686749(v=vs.85).aspx (bezocht op 27-04-2014). Miller, Matt (2006). Structured Exception Handling. url: http://uninformed.org/index. cgi?v=5&a=2&p=4. Mueller, Rob (2011). TCP keepalive, iOS 5 and NAT routers. url: http://blog.fastmail. fm/2011/10/27/tcp-keepalive-ios-5-and-nat-routers/. Ongenae, Veerle (2014). Computernetwerken IV. Osterman, Larry (2005a). Why don’t critical sections work cross process? url: http://blogs. msdn.com/b/larryosterman/archive/2005/08/24/455741.aspx. – (2005b). Why is the DOS path character “\”? url: http://blogs.msdn.com/b/larryosterman/ archive/2005/06/24/432386.aspx (bezocht op 31-03-2014). Pike, Rob (2003). UTF-8 history. url: http://www.cl.cam.ac.uk/∼mgk25/ucs/utf- 8- history.txt. BIBLIOGRAFIE 79

Preshing, Jeff (2011). Always Use a Lightweight Mutex. url: http://preshing.com/20111124/ always-use-a-lightweight-mutex/. Qt (2014). C++ GUI Programming with Qt 4 > A Brief History of Qt. url: http://my. safaribooksonline.com/0131872494/pref04?portal=oreilly (bezocht op 02-2014). Radzivilovsky, Pavel, Yakov Galka en Slava Novgorodov (2014). UTF-8 Everywhere Mani- festo. url: http://www.utf8everywhere.org (bezocht op 30-04-2014). RedHat (2014). Using clock getres() to compare clock resolution. url: https : / / access . redhat . com / site / documentation / en - US / Red Hat Enterprise MRG / 2 / html / Realtime Reference Guide/Realtime Reference Guide-Timestamping-Clock Resolution.html (be- zocht op 31-03-2014). Russell, Rusty, Daniel Quinlan en Christopher Yeoh (2004). /run : Run-time variable data. url: http://www.linuxbase.org/betaspecs/fhs/fhs/ch03s15.html. Sch¨aling, Boris (2011). The Boost C++ Libraries. XML Press. url: http://en.highscore. de/cpp/boost/. Silva, Mois´es(2009). select system call limitation in Linux. url: http://www.moythreads. com/wordpress/2009/12/22/select-system-call-limitation/. Simonyi, Charles (1999). Hungarian Notation. url: http://msdn.microsoft.com/en-us/ library/aa260976(v=vs.60).aspx (bezocht op 31-03-2014). Sivaraman, Mukund (2011). How to use epoll? A complete example in C. url: https://banu. com/blog/2/how-to-use-epoll-a-complete-example-in-c/. Spolsky, Joel (2003). The Absolute Minimum Every Software Developer Absolutely, Positi- vely Must Know About Unicode and Character Sets (No Excuses!) url: http : / / www . joelonsoftware.com/articles/Unicode.html. Stenberg, Daniel (2014). poll vs select vs event-based. url: http://daniel.haxx.se/docs/ poll-vs-select.html. Unicode Consortium (2006). The Unicode Standard, Version 5.0. Hfdstk. General Structure. url: http://www.unicode.org/versions/Unicode5.0.0/ch02.pdf. West, Andrew (2014). How many Unicode characters are there? url: http://babelstone. blogspot.be/2005/11/how- many- unicode- characters- are- there.html (bezocht op 06-06-2014). Wine (2014a). Winlib User’s Guide - Dealing with the MFC. url: http://www.winehq.org/ docs/winelib-guide/mfc (bezocht op 02-2014). – (2014b). Winlib User’s Guide - Legal issues. url: http://www.winehq.org/docs/winelib- guide/mfc-legal-issues (bezocht op 02-2014). Wingo, Scot (1996). Frequently Asked Questions (aka the MFC FAQ). url: http : / / cs . sookmyung.ac.kr/class/00891/C++/mfc-faq/. wxWidgets (2014a). Name Change. url: http://www.wxwidgets.org/about/name-change/ (bezocht op 03-2014). – (2014b). Unicode Support in wxWidgets. url: http : / / docs . wxwidgets . org / trunk / overview unicode.html (bezocht op 28-04-2014). – (2014c). wxWidgets - History. url: http://www.wxwidgets.org/about/history (bezocht op 20-03-2014). – (2014d). wxWidgets - Licence. url: http://www.wxwidgets.org/about/newlicen.htm (bezocht op 31-03-2014).