-
Academiejaar 2013–2014 Faculteit Ingenieurswetenschappen en
Architectuur
Valentin Vaerwyckweg 1 – 9000 Gent
Omzetten van een Windows C++ MFC
telefonie servertoepassing naar Linux
Promotoren: Ann VAN OVERBERGE
Ing. Filip HOSTE (MyForce)
Tibault DAMMAN
Masterproef voorgedragen tot het behalen van het diploma van
Master in de industriële wetenschappen: informatica
Powered by TCPDF (www.tcpdf.org)
-
Voorwoord
Vooreerst mijn dank aan iedereen bij MyForce voor een bijzonder
aangename stage, ik weetniet wat ik zonder de voortdurende
aanmoediging (“en, compileert het al?”) gedaan zouhebben. In het
bijzonder wil ik Filip Hoste en Bert Reyntjes bedanken voor hun
hulp enuitleg bij de code.
Daarnaast wil ik ook mijn promotor Ann Van Overberge bedanken
voor het nalezen van dezescriptie en geven van bruikbare feedback.
Ook aan Rudy Stoop, tweede lezer en docent C++,een woord van
dank.
Ten slotte dank ik ook Hannes, Arne en Bas voor hun tips en het
verdragen van mijn onge-twijfeld eindeloos geklaag.
Tibault Damman
De Pinte, 28 augustus 2014
-
Omzetten van een Windows C++ MFCtelefonie servertoepassing naar
Linux
door
Tibault DAMMAN
Masterproef voorgedragen tot het behalen van het diploma van
Master in de industriële wetenschappen: informatica
Academiejaar 2013–2014
Interne promotor: Ann VAN OVERBERGE
Externe promotor: Ing. Filip HOSTE (MyForce)
Faculteit Ingenieurswetenschappen en Architectuur
Universiteit Gent
Abstract
MyForce is een bedrijf dat zich specialiseert in het ontwikkelen
van oplossingen voor bedrijven
waar automatisatie van telefonie centraal staat, zoals bij
callcenters en bureaus voor markt-
onderzoek. HardwareClient is het component dat instaat voor de
communicatie tussen het
publieke telefonienetwerk (via ISDN en VoIP) en het interne
bedrijfsnetwerk.
Momenteel werkt deze software enkel op Microsoft Windows, maar
de gebruikte middleware
en drivers voor dit platform worden minder actief ontwikkeld dan
hun Linux variant.
Deze thesis heeft als doel de HarwareClient software beschikbaar
te maken op Linux, om op
deze manier gebruik te kunnen maken van alle nieuwe
mogelijkheden die dit platform met
zich meebrengt.
Om dit resultaat te verkrijgen moesten de verschillen tussen
Windows en Linux onderzocht
worden wat betreft de drivers, de sockets, de multithreading,
het aanspreken van het bestands-
systeem, tot zelfs de verschillen tussen de compilers. Een groot
deel van de tijd werd besteed
aan het vervangen van het MFC framework, een Microsoft
alternatief voor de STD library,
dat bovendien een schil rond een groot deel van de Win32
functies biedt. Voorzichtigheid was
ook geboden om de compatibilteit met bestaande Windowssystemen
niet te breken.
Trefwoorden
Linux – porting – MFC – C++ – telefonie
-
Porting a Windows C++ MFC
telephony server application to Linuxby
Tibault DAMMAN
Master thesis submitted to obtain the degree of
Master of Science in Information Engineering Technology
Academic year 2013–2014
Internal promotor: Ann VAN OVERBERGE
External promotor: Eng. Filip HOSTE (MyForce)
Faculty of Engineering and Architecture
University Gent
Abstract
MyForce is a company specialising in the development of
solutions for companies with a focus
on telecommunication, such as call centres and market
researchers. HardwareClient is their
software component responsible for linking the public telephony
network (through ISDN or
VoIP) with the internal company network.
Currently this software only runs on Microsoft Windows, but the
used middleware and drivers
for this operating system are less actively developed than their
Linux counterpart.
The goal of this thesis is to enable HardwareClient to run on
Linux, so that it can make use
of the new possibilities and increased third party support this
platform offers.
Various differences between Windows and Linux were researched,
varying from drivers, to
sockets, multithreading, file systems, and even the intricate
differences between compilers.
The majority of the work consisted of replacing the MFC
framework, a Microsoft alternative
to the C++ STD, that also offers an easy wrapper around many
Win32 functions. Great care
was also taken not to break compatibility with the existing
Windows clients and servers.
Keywords
Linux – porting – MFC – C++ – telephony
-
INHOUDSOPGAVE i
Inhoudsopgave
1 Inleiding 1
2 Toolchain 22.1 Besturingssysteem . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . 22.2 C++ compiler en standard
library . . . . . . . . . . . . . . . . . . . . . . . . . 22.3
Buildsystem . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . 2
2.3.1 CMake . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . 32.3.1.1 Cross compiling . . . . . . . . . . .
. . . . . . . . . . . . . . 42.3.1.2 Lijst van cpp-bestanden
bekomen . . . . . . . . . . . . . . . 5
2.4 IDE . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . 52.5 Version Control . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . 5
3 Middleware (Driver/SDK) installatie 63.1 Dialogic Powermedia
HMP . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63.2
Aculab . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . 7
3.2.1 libTiNG . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . 8
4 MFC 94.1 Wat is MFC? . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . 94.2 MFC alternatieven . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . 9
4.2.1 Wine . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . 104.2.2 Qt . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . 104.2.3 wxWidgets . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . 114.2.4
Boost . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . 114.2.5 C++11 STD . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . 11
4.3 Eigen MFC implementatie . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . 11
5 Het porting proces 135.1 Overzicht van de uiteindelijke
mappenstructuur . . . . . . . . . . . . . . . . . 14
6 Datatypes en -structuren 156.1 Datatypes . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . 156.2 Array,
List en Map . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . 156.3 String . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . 16
6.3.1 Unicode . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . 176.3.2 Implementatie CString . . . . . . . . .
. . . . . . . . . . . . . . . . . . 19
-
INHOUDSOPGAVE ii
6.3.3 Extra stringfuncties . . . . . . . . . . . . . . . . . . .
. . . . . . . . . 216.3.4 Unicode in praktijk . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . 23
6.3.4.1 Vergelijken van tekens . . . . . . . . . . . . . . . . .
. . . . . 24
7 Time 267.1 GetTickCount . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . 267.2 CTime . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 277.3
GetSystemTime en GetLocalTime . . . . . . . . . . . . . . . . . . .
. . . . . 287.4 COleDateTime en COleDateTimeSpan . . . . . . . . .
. . . . . . . . . . . . . 29
8 IO 318.1 Sockets . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . 31
8.1.1 TCP keepalive . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . 318.1.2 Asynchrone sockets . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . 34
8.1.2.1 select(2) . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . 348.1.2.2 poll(2) . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . 348.1.2.3 epoll(7) . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . 348.1.2.4 libevent, libev,
libuv . . . . . . . . . . . . . . . . . . . . . . . 358.1.2.5
(C++11 / boost) Asio . . . . . . . . . . . . . . . . . . . . . .
35
8.2 Files . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . 358.2.1 CFile . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . 368.2.2 CFileFind . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
398.2.3 Unicode BOM . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . 42
8.3 CRC-32 en Zip . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . 42
9 Multithreading 449.1 Threads . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . 449.2 Locking (critical
sections en mutexen) . . . . . . . . . . . . . . . . . . . . . .
44
9.2.1 CSingleLock . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . 449.2.2 Named Mutex . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . 45
9.3 CEvent . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . 469.4 Compiler intrinsieke functies . . . .
. . . . . . . . . . . . . . . . . . . . . . . . 479.5 Thread Local
Storage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. 48
10 Daemon 4910.1 GetModuleFileName . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . 5010.2 Bepalen of de daemon al
draait . . . . . . . . . . . . . . . . . . . . . . . . . . 5010.3
Het Windowsregister . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . 51
11 Link met middleware 5211.1 Importeren van functies uit
bibliotheken . . . . . . . . . . . . . . . . . . . . . 52
11.1.1 set invalid parameter handler . . . . . . . . . . . . . .
. . . . . . . . 5311.2 Headerconflicten . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . 5511.3 Ontbrekende
functionaliteit . . . . . . . . . . . . . . . . . . . . . . . . . .
. . 55
11.3.1 Dialogic file API . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . 5611.3.2 NCM API . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . 5711.3.3 ALGO LOUD . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . 57
-
INHOUDSOPGAVE iii
11.3.4 SR MODELTYPE . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . 5711.3.5 SWMODE CTBUS SCBUS . . . . . . . . . . . . .
. . . . . . . . . . . 58
12 Struikelblokken 5912.1 64-bit . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . 59
12.1.1 M X64 . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . 5912.1.2 Pointer als int . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . 5912.1.3 sizeof(long) . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
12.2 Het bestandssysteem . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . 6012.3 Striktheid van de compiler . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . 6112.4 Structured
Exception Handling . . . . . . . . . . . . . . . . . . . . . . . .
. . 6212.5 Problemen met macro’s . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . 62
12.5.1 Slechte ifdefs . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . 6312.6 Het nut van typename bij templates . .
. . . . . . . . . . . . . . . . . . . . . 64
13 Varia 6613.1 Het aantal elementen van een array op de stack .
. . . . . . . . . . . . . . . . 6613.2 StrFormatByteSize . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . 6613.3
AfxIsValidAddress . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . 67
14 Conclusie 69
Bijlage A Installatie drivers/sdk 70A.1 Dialogic HMP . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70A.2
Aculab . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . 74
Bibliografie 77
-
LIJST VAN CODEFRAGMENTEN iv
Lijst van codefragmenten
2.1 Verkorte versie van het gebruikte CMake projectbestand . . .
. . . . . . . . . 32.2 Cross compiling met CMake . . . . . . . . .
. . . . . . . . . . . . . . . . . . . 42.3 Bash instructie om
codebestanden uit een Visual Studio project te filteren . . 53.1
CMake - Dialogic headers en bibliotheken . . . . . . . . . . . . .
. . . . . . . 73.2 CMake - Aculab headers en bibliotheken . . . . .
. . . . . . . . . . . . . . . . 83.3 CMake - libTiNG preprocessor
symbool . . . . . . . . . . . . . . . . . . . . . 85.1 STUBBED
macro [Gordon 2014] . . . . . . . . . . . . . . . . . . . . . . . .
. 136.1 Enkele voorbeelden van MFC typedefs . . . . . . . . . . . .
. . . . . . . . . . 156.2 Voorbeeld van het itereren over een CList
met POSITION . . . . . . . . . . . 166.3 Gespecialiseerde
datastructuren . . . . . . . . . . . . . . . . . . . . . . . . . .
166.4 Typedefs voor C-strings . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . 166.5 Constructors en operator overloads van
CString . . . . . . . . . . . . . . . . . 196.6 CString non-member
operator overload . . . . . . . . . . . . . . . . . . . . . . 196.7
Een selectie van enkele CString methodes . . . . . . . . . . . . .
. . . . . . . 206.8 CString Format . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . 206.9 ttol parst een long
naar een CString . . . . . . . . . . . . . . . . . . . . . . .
216.10 implementatie vsnprintf s, strncpy s en memcpy s . . . . . .
. . . . . . . . . 216.11 CWString een UTF-16 CString klasse . . . .
. . . . . . . . . . . . . . . . . . 236.12 Conversiefuncties tussen
UTF-8 en UTF-16 . . . . . . . . . . . . . . . . . . . 236.13
Problemen bij multi byte strings . . . . . . . . . . . . . . . . .
. . . . . . . . 246.14 Tekens vergelijken met UTF-8 aan de hand van
std::mbrtowc . . . . . . . . . 257.1 Implementatie van GetTickCount
. . . . . . . . . . . . . . . . . . . . . . . . . 267.2 CTime
constructor . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . 277.3 Enkele CTime methodes . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . 277.4 de SYSTEMTIME struct . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . 287.5 Implementatie
van GetSystemTime . . . . . . . . . . . . . . . . . . . . . . . .
297.6 COleDateTime en COleDateTimeSpan implementatie . . . . . . .
. . . . . . 298.1 TCP keep-alive activeren met WSA voor m hSocket .
. . . . . . . . . . . . . 318.2 TCP keep-alive activeren op Linux
voor socket s . . . . . . . . . . . . . . . . 328.3 De resulterende
TCP keep-alive wrapper . . . . . . . . . . . . . . . . . . . . .
328.4 de header van CFile en CFileStatus . . . . . . . . . . . . .
. . . . . . . . . . . 368.5 CFile::Open methode . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . 378.6 Enkele CFile
methodes . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . 388.7 CFileFind implementatie . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . 408.8 (UTF-16) BOM is op Linux niet nodig
. . . . . . . . . . . . . . . . . . . . . . 429.1 Voorbeeld van de
scope van een CCriticalSection . . . . . . . . . . . . . . . .
45
-
LIJST VAN CODEFRAGMENTEN v
9.2 Implementatie CCriticalSection::Lock met pthread mutex t . .
. . . . . . . . 459.3 Voorbeeld van het gebruik van CEvent . . . .
. . . . . . . . . . . . . . . . . . 469.4 Voorbeeld van het gebruik
van pthread conditions . . . . . . . . . . . . . . . 469.5 GCC
compatibele implementatie van InterlockedIncrement en -Decrement .
. 479.6 Thread Specific Storage wrapper . . . . . . . . . . . . . .
. . . . . . . . . . . 4810.1 Opvragen van het executable pad op
Windows . . . . . . . . . . . . . . . . . 5010.2 Opvragen van het
executable pad op Linux . . . . . . . . . . . . . . . . . . .
5010.3 Controlleren of een applicatie al draait . . . . . . . . . .
. . . . . . . . . . . . 5011.1 Dynamische bibliotheek oproepen op
Windows . . . . . . . . . . . . . . . . . 5211.2 Dynamische
bibliotheek oproepen op Linux . . . . . . . . . . . . . . . . . . .
5211.3 Wrapper voor dynamisch linken . . . . . . . . . . . . . . .
. . . . . . . . . . . 5311.4 Instellen van een invalid parameter
handler op Windows . . . . . . . . . . . . 5311.5 Dialogic file API
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5611.6 Ontbreekt: ALGO LOUD . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . 5711.7 Ontbreekt: SR MODELTYPE . . . . . . . . .
. . . . . . . . . . . . . . . . . 5712.1 M X64 definiëren . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5912.2
Code die enkel in Visual Studio compileert . . . . . . . . . . . .
. . . . . . . . 6112.3 Code die overal compileert . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . 6112.4 Een macro aanroep .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6212.5 De macrodefinitie . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . 6212.6 De macroaanroep na de preprocessor
. . . . . . . . . . . . . . . . . . . . . . . 6312.7 Een lege
define . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . 6312.8 Defines kunnen ook voor problemen zorgen . . . . .
. . . . . . . . . . . . . . 6312.9 Problematische implementatie
CMap::Lookup . . . . . . . . . . . . . . . . . . 6412.10’typename’
voor een gekwalificeerd afhankelijk type . . . . . . . . . . . . .
. . 6513.1 Een macro voor de grootte van een array op de stack . .
. . . . . . . . . . . . 6613.2 FormatBytes maakt gebruik van
StrFormatByteSize . . . . . . . . . . . . . . 6613.3 Implementatie
CStaticTools::FormatBytes (StrFormatByteSize) . . . . . . . .
6713.4 Microsofts implementatie van AfxIsValidAddress . . . . . . .
. . . . . . . . . 68
-
INLEIDING 1
Hoofdstuk 1
Inleiding
MyForce (vroeger MI4C) specialiseert zich in innovatieve
oplossingen op gebied van Com-puter Telephony Integration (CTI),
waaronder het pakket genaamd CTArchitect. Hiermeekunnen gewone
servers omgetoverd worden tot complete telefonieoplossingen voor
bedrijvenwaar automatisatie van telefonie centraal staat (zoals
callcenters, bureaus voor telefonischmarktonderzoek, helpdesks,
etc). Dit beperkt zich niet tot traditionele ISDN
telefoonlijnen:ook het flexibelere en puur softwarematige VoIP
wordt ondersteund. VoIP heeft als voordeeldat het – naast een USB
headset voor elke agent – geen kostelijke extra hardware vereist,
diedan ook niet vervangen moet worden bij falen. Bijkomend voordeel
is dat het erg schaalbaaris en naar de cloud kan worden
verhuisd.
Zo’n volledig telefonienetwerk wordt beheerd door één
CTArchitect server, die op zijn beurtcommuniceert met enkele
HardwareClient servers. Op deze servers staat een (software)
VoIPserver gëınstalleerd, en/of een (hardware) ISDN kaart die met
het vaste telefonienetwerk ver-bonden is. De HardwareClient
software stelt zijn beschikbare VoIP/ISDN telefoonlijnen
danbeschikbaar op een gestandaardiseerde manier op het netwerk (via
sockets). Eén Hardware-Client server kan zo verantwoordelijk zijn
voor enkele honderden telefoonlijnen.
CTArchitect kan dan aan de hand van enkele speciaal ontwikkelde
algoritmen op een intelli-gente manier de meest optimale
verbindingen maken tussen persoon en lijn.
De onderliggende ISDN/VoIP hardware/software (van onder andere
netwerkcommunicatie-bedrijf Dialogic) die in deze oplossingen
gebruikt wordt, bestaat reeds in een Linux- en Win-dowsversie,
waarvan de Linuxversie het best onderhouden is en de meeste
features biedt.MyForce ondersteunt tot nu toe enkel Windows, maar
is erg gëınteresseerd in een Linuxversie om van de bijkomende
functionaliteit te kunnen genieten.
Deze thesis behandelt de vele stappen die nodig zijn om een
project als HardwareClient naarLinux te brengen.
We starten met het opzetten van een ontwikkelingsomgeving met
alle benodigde onderdelenen bekijken vervolgens hoe we het omzetten
van de code best aanpakken. We onderzoekenhoe Windows en Linux zich
verschillen op gebied van sockets, multithreading en meer, enhoe
zich dat vertaalt in code. Ook verborgen complexiteiten en
verschillen in de compilersworden besproken.
-
TOOLCHAIN 2
Hoofdstuk 2
Toolchain
Vooraleer aan de code van de Linuxversie kan worden begonnen,
moeten er eerst een aantalkeuzes worden gemaakt omtrent de
programmeeromgeving.
2.1 Besturingssysteem
“Linux” op zich is geen besturingssyteem, maar een kernel. De
keuze uit Linuxgebaseerdebesturingssystemen is groot: Ubuntu,
Debian, Fedora, RHEL, CentOS, SuSe, ...
Voor HarwareClient werd (in overleg met MyForce) gekozen voor
CentOS 6.5 als doelplatform,om drie redenen:
ervaring met het platform
de stabiliteit
het wordt door alle gebruikte middleware ondersteund
2.2 C++ compiler en standard library
De twee meest populaire compilers op Linux zijn momenteel GCC en
LLVM/CLang, metrespectievelijk libstdc++ en libc++ als verkozen
standard library. Rekening houdend met hetdoelplatform en de
middleware, was de combinatie GCC met libstdc++ de beste keuze.
2.3 Buildsystem
In een project met heel veel codebestanden en verschillende
externe bibliotheken, is het nietevident om elke keer handmatig de
compiler aan te geven in welke volgorde de bestandengecompileerd
moeten worden, en welke onderling gelinkt moeten worden.
-
2.3 Buildsystem 3
Dit kan geautomatiseerd worden door deze informatie vast te
leggen inMakefiles, of project-bestanden. Op Windows worden hier
vaak solution files (en bijhorende filters) voor gebruikt,dit zijn
de projectbestanden van de Microsoft Visual Studio IDE.
Elke IDE op Linux heeft zo ook een eigen gelijkaardig
projectbestand, maar die zijn weinigpopulair. De klassieke
Makefiles genieten nog altijd de voorkeur.
Makefiles zijn eenvoudige tekstbestanden waarin aan de hand van
een declaratieve syntaxregels worden opgesteld voor de compiler.
Wanneer de gebruiker in de commandolijn maketypt, zal de Makefile
automatisch gevonden en uitgevoerd worden.
Makefiles zijn erg populair in de Open Source wereld, omdat het
geen verplichtingen oplegtwat betreft de te gebruiken IDE. Gezien
iedereen de code kan compileren door make te typen,is het gebruik
van een IDE niet eens verplicht.
Makefiles schrijven is een complexe platform- en
compilerafhankelijke opdracht, om dit tevereenvoudigen en te
automatiseren kan gebruik gemaakt worden van CMake.
2.3.1 CMake
CMake is een populair systeem om platform- en
compileronafhankelijke projectbestanden teschrijven. Aan de hand
van een eenvoudige syntax wordt opgegeven waar de code staat,
waarextra headers te vinden zijn, met welke bibliotheken gelinkt
moet worden, enzovoort. CMakekan aan de hand van deze gegevens
automatisch een projectbestand genereren, zoals onderandere een
Microsoft Visual Studio solution of Unix Makefile.
cmake_minimum_required (VERSION 2.6)project (HWClient) #set
project name
SET(EXECUTABLE_OUTPUT_PATH ${CMAKE_SOURCE_DIR
}/bin)SET(LIBRARY_OUTPUT_PATH ${CMAKE_SOURCE_DIR }/lib)
SET(CMAKE_CXX_FLAGS "${xcomp} -std=c++0x -pthread -g -Wall")
# Necessary symbol to comile hardware client (cfr
HWClientManager.cpp)add_definitions("-DCLIENTMANAGER
-DMANAGERDIALOGIC -D__NCM_DISABLED")
INCLUDE_DIRECTORIES (. ../ CTArchitect ../ CTDesign ../
ProjectCopy)
SET(SRC StdAfx.cppHWClient.cpp...)
add_executable(${PROJECT_NAME}
${SRC})target_link_libraries(${PROJECT_NAME} dl rt m z)
Codefragment 2.1: Verkorte versie van het gebruikte CMake
projectbestand
Codefragment 2.1 toont een minimale versie van het CMake
configuratiebestand voor Hardware-Client. We zien hoe de naam van
het project ingesteld wordt, hoe de mappen gekozen worden
-
2.3 Buildsystem 4
waarin de binaries terecht zullen komen, en hoe we extra vlaggen
aan de compiler (GCC) mee-geven.
-std=c++0x activeert C++11, -pthread voegt ondersteuning voor
pthreads toe, -g zorgt voorhet toevoegen van debugsymbolen, en
-Wall activeert alle compiler warnings.
add definitions wordt gebruikt om preprocessor symbolen in te
stellen, CLIENTMANAGERen MANAGERDIALOGIC moeten bijvoorbeeld
verplicht gedefinieerd zijn voor de compilatie
vanHardwareClient.
Met INCLUDE DIRECTORIES worden alle mappen opgegeven waar zich
headers in kunnen bevin-den. De SRC variabele vullen we met SET op
met alle .cpp bestanden die gecompileerd moetenworden. Dit wordt
vervolgens met de gewenste bestandsnaam voor de binary (de
project-naam ingesteld in het begin) doorgegeven aan add
executable. CMake zal zelf bepalen watde afhankelijkheden zijn en
in welke volgorde het compileren en linken moet gebeuren.
Het resultaat wordt met target link libraries dan nog gelinkt
met externe bibliotheken:dl (dynamic linking), rt (real time), m
(math), en z (zlib).
Om meerdere besturingssystemen, architecturen en compilers te
ondersteunen met hetzelfdeCMake bestand kan gebruikt gemaakt worden
van conditionele logica.
2.3.1.1 Cross compiling
Om een onderscheid te kunnen maken tussen 32-bit en 64-bit (zie
later ook in hoofdstuk 3),en om op een 64-bit systeem een 32-bit
binary te kunnen produceren maken we gebruik vancross
compiling.
Om in CMake onderscheid te kunnen maken tussen deze twee
architecturen voegen we on-derstaande code toe:
if(DEFINED arch AND (arch EQUAL 32 OR arch EQUAL
64))message("Selecting ${arch} bit architecture ...")
else()if(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(arch 64)else()
set(arch 32)endif()message("Defaulted to native ${arch} bit
architecture ...")
endif()
# crosscompile if 32b target on 64bif(arch EQUAL 32 AND
CMAKE_SIZEOF_VOID_P EQUAL 8)
set(xcomp "-m32")else()
set(xcomp "")endif()
Codefragment 2.2: Cross compiling met CMake
-
2.4 IDE 5
Dit introduceert een arch variabele die we instellen op 64
wanneer een pointer (CMAKE SIZEOFVOID P) 8 byte groot is, of 32
indien hij 4 byte groot is. In de CMake debugoutput wordt
viamessage geprint welke architectuur gekozen is, zodat we bij het
genereren van een buildfileof projectbestand in de debugoutput
kunnen nagaan of de juiste architectuur geselecteerdwerd.
Indien je wil cross compilen naar 32-bit op een 64-bit systeem
kan je de waarde van archoverschrijven door op voorhand de
variabele zelf als volgt in te stellen: set(arch 32).
In dit geval wordt de xcomp variabele ingevuld met de GCC
compilervlag “-m32”, die crosscompilen activeert. In codefragment
2.1 was al zichtbaar hoe deze variabele gebruikt wordt.
2.3.1.2 Lijst van cpp-bestanden bekomen
Om snel een lijst van alle nodige cpp-bestanden te bekomen kan
een eenvoudig scriptje geschre-ven worden om deze uit de moeilijk
leesbare Visual Studio solution bestanden te parsen:
cat HWClient.v11.vcxproj.filters | sed "s/Filter
Include/workaround/" \| grep " Include=" | sed "s/.*/\1/"
Codefragment 2.3: Bash instructie om codebestanden uit een
Visual Studio project te filteren
2.4 IDE
Een goede IDE helpt aan de hand van onder andere syntax
highlighting en een ingebouwdedebugger om snel te vinden welke code
niet werkt en aangepast moeten worden. Op Windowswerd Microsoft
Visual Studio gebruikt, op Linux werd gekozen voor QtCreator.
Naast een uitgebreide IDE werd ook VIM gebruikt om snelle
aanpassingen te maken.
2.5 Version Control
Bij MyForce wordt SVN gebruikt. Experimentele Linux aanpassingen
worden beter nietdirect in de hoofdrepository opgenomen, daarom
werd voor de Linux versie een afzonderlijkerepository gebruikt. Uit
persoonlijke voorkeur werd hier voor Git gekozen.
-
MIDDLEWARE (DRIVER/SDK) INSTALLATIE 6
Hoofdstuk 3
Middleware (Driver/SDK)installatie
MyForce ondersteunt de telefonietechnologie (ISDN en VoIP) van
meerdere leveranciers zoalsDialogic, Aculab en Amtelco. Deze
bedrijven leveren naast hardware ook software, zoals dedrivers
nodig voor de hardware, VoIP servers ter vervangen van hardware, en
bibliothekendie het ontwikkelen van telefonietoepassingen in het
algemeen vereenvoudigen. Dit soortsoftware wordt ook middleware
genoemd, omdat het zich tussen het besturingssysteem en
degebruikersapplicatie bevindt.
Voor de Linuxversie werd de ondersteuning beperkt tot de
technologie van Dialogic en Aculab.
3.1 Dialogic Powermedia HMP
Over het bedrijf:
Dialogic, the Network Fuel® company, inspires the world’s
leading service provi-ders and application developers to elevate
the performance of media-rich commu-nications across the most
advanced networks. [Dialogic 2014a]
Dialogic PowerMedia Host Media Processing (HMP) laat toe om
grootschaalse telefonie-applicaties te bouwen voor gewone servers,
zonder verplicht gebruik te moeten maken vangespecialiseerde
hardware. Dit is een zeer belangrijk onderdeel van
HardwareClient.
Dialogic® PowerMedia HMP for Linux (HMP Linux) is scalable,
feature-richmultimedia processing software for building innovative
and cost-effective voiceand video solutions suitable for enterprise
or service provider deployment. HMPLinux can enable basic SIP or
hybrid connectivity, audio and video play/record,multimedia
streaming, transcoding, fax, automated interactive audio and
videosolutions (IVR and IVVR), and complex live interactions, such
as contact centersand audio and video conferencing or video
portals. [Dialogic 2014b]
Om HardwareClient te kunnen compileren is linken met de Dialogic
HMP SDK vereist. Dezeis inbegrepen in de installatie van de HMP
server, en is vrij te downloaden van de Dialogic
-
3.2 Aculab 7
website.
Bij het starten van de installatie merken we een paar problemen
op:
$ tar -xzvf lnxHMP_4.1_161.tgz$ cd lnxHMP_4.1_161$ su -c
"./install.sh"
The 32-bit libstdc++ package, required by HMP, was not
found.Please correct the issue before attempting HMP installation
again.
De software ondersteunt officieel 64-bit besturingssytemen, maar
de installer zelf vereist blijk-baar wel nog steeds een 32-bit
omgeving. Om verder te gaan installeren we de gevraagde
32-bitcompatibiliteitsbibliotheek:
su -c "yum install libstdc++.i686"
De installatie kan nu zoals hiervoor gestart worden. Een
volledig verslag van het installatie-proces, met alle in- en output
is terug te vinden in bijlage A.1.
Na het herstarten van de computer kan de Dialogic server
aangesproken worden via telnet,op de “CLI1 Agent” poort gekozen in
de installer (standaard 23).
De headers zijn te vinden in /usr/dialogic/inc, de bibliotheken
in /usr/dialogic/lib en/usr/dialogic/lib64. In het CMake
projectbestand kunnen deze dan als volgt toegevoegdworden:
INCLUDE_DIRECTORIES (/usr/dialogic/inc)
if(arch EQUAL 64)LINK_DIRECTORIES (/usr/dialogic/lib64)
else()LINK_DIRECTORIES (/usr/dialogic/lib)
endif()
Codefragment 3.1: CMake - Dialogic headers en bibliotheken
3.2 Aculab
Over het bedrijf:
Aculab provides deployment proven telephony products to the
global communi-cations market. [...] With over 35 years of
experience in helping to drive ourcustomers’ success, our
technology is used to deliver multimodal voice, data andfax
solutions for use within IP, PSTN and mobile networks – with
performancelevels that are second to none.
Companies worldwide have adopted our technology for a wide
variety of busi-ness critical services and solutions. These can
include, for example, high perfor-
1Command Line Interface
-
3.2 Aculab 8
mance inbound/outbound contact centre applications, speech
enabled IVR andself-service systems, fax and voice broadcast,
conferencing, unified communicati-ons, messaging, and hosted or
cloud-based services. [Aculab 2012]
Net als bij Dialogic hebben we ook van Aculab de headers en
bibliotheken nodig om Hardware-Client te kunnen compileren.
De installatie gebeurt dit keer grafisch, via de Aculab
Installation Tool (AIT). Deze installervereist opnieuw een 32-bit
omgeving, en de installatie van twee extra bibliotheken die op
eenstandaard CentOS 6.5 systeem ontbreken (libXext.so.6 en
libz.so.1):
su -c "yum install libXext.i686 libz.i686"
Een compleet verslag van het installatieproces is terug te
vinden in bijlage A.2.
In CMake worden de headers en bibliotheken als volgt
toegevoegd:
INCLUDE_DIRECTORIES (/usr/local/aculab/v6/include)
if(arch EQUAL 64)LINK_DIRECTORIES
(/usr/local/aculab/v6/lib64)
else()LINK_DIRECTORIES (/usr/local/aculab/v6/lib)
endif()
Codefragment 3.2: CMake - Aculab headers en bibliotheken
De Dialogic en Aculab headers beide op deze manier includeren
zal voor problemen zorgen.De reden waarom (en de oplossing) wordt
besproken in hoofdstuk 11.2.
3.2.1 libTiNG
Een onderdeel van de Aculab installatie is de TiNG bibliotheek,
die ook in HardwareClientgebruikt wordt. Om deze header te kunnen
gebruiken is de declaratie van een preprocessorsymbool nodig.
[Aculab 2014]
In CMake kunnen we hier volgend lijntje voor toevoegen:
add_definitions(-DTiNGTYPE_LINUX =1)
Codefragment 3.3: CMake - libTiNG preprocessor symbool
-
MFC 9
Hoofdstuk 4
MFC
4.1 Wat is MFC?
De Microsoft Foundation Class Library (MFC) werd gëıntroduceerd
in de Microsoft C/C++
7.0 compiler in 1992 (voor de duidelijkheid: deze predateert
Microsoft Visual C++ 1.0).[Blaszczak 1997] C++ was in die tijd nog
relatief jong als voorkeurstaal voor de ontwikkelingvan
commerciële applicaties: van een gestandaardiseerde bibliotheek
met datastructuren enalgoritmes was toen zelfs nog geen sprake. MFC
vormde hiervoor een oplossing, en encap-suleert bovendien een groot
deel van de Windows API in object georiënteerde C++
klassen.[Microsoft 2014d]
In MFC dus klassen te vinden voor strings, lijsten, mappen, maar
ook voor sockets, threads,files, mutexen, ... en GUIs.
Microsoft staat niet gekend om zijn ondersteuning voor andere
platformen, MFC is hier geenuitzondering op en is bijgevolg enkel
beschikbaar op Windows.
4.2 MFC alternatieven
Een naieve oplossing om de code Linuxcompatibel te maken is de
code rechtstreeks aante passen door elke MFC datastructuur te
vervangen met de moderne gestandaardiseerdevariant (std::string,
std::map, . . . ). Naief, omdat dit in een codeproject van
letterlijk meerdan 100 000 lijnen ongelofelijk veel vervangwerk
creëert. Het gebruik van de alternatievenkan bovendien erg veel
verschillen, waardoor het meer herschrijven dan vervangen
wordt.Daarenboven hergebruikt MyForce vaak code, het gedrag in
één bestand aanpassen kan duseen effect hebben op meerdere
projecten. De verschillende socketimplementaties gebruikt
inHardwareClient komen bijvoorbeeld uit twee andere projecten.
Enkel de code aanpassen inde bestanden nodig voor HardwareClient
zou alle andere projecten kunnen breken. Het kanniet de bedoeling
zijn om alle code van MyForce te herschijven, dus is een andere
aanpak aande orde.
Een API-compatibele herimplementatie van de gebruikte MFC
klassen en methodes, aan de
-
4.2 MFC alternatieven 10
hand van wrappers rond bestaande alternatieven zou betekenen dat
de bestaande Hardware-Client code zonder aanpassingen (op een paar
defines na) behouden kan worden.
Een bespreking van de beschikbare alternatieven die hiervoor
gebruikt kunnen worden:
4.2.1 Wine
Wine1 is een open source herimplementatie van de Win32 API op
Unix. Het laat toe om.exe programma’s zonder aanpassingen uit te
voeren op Linux, BSD en Mac OS X. Indienje toegang hebt tot de
broncode van een Windowsapplicatie, dan kan je deze
rechtstreekscompileren naar Linuxcompatibele code door te linken
met Winelib. Gezien de broncode vanMFC meegeleverd wordt met Visual
Studio, zouden we deze kunnen proberen compilerenmet Wine, om zo
een bibliotheek te krijgen die we op Linux via een wrapper zouden
kunnengebruiken.
Er zijn echter meerdere goede redenen om dit niet te doen:
de legaliteit van het hercompileren en distribueren van deze
Microsoft code is onduidelijk[Wine 2014b]
Wine rendert vensters zoals in Windows 98, wat in een Linux
omgeving niet goed past
alle code zou zo steunen op een automatische vertalingslaag
(Wine) waar we geen con-trole over hebben, zonder enige garantie
van correctheid of stabiliteit
het maken van de wrapper en het corrigeren van de headers is een
complexe opdracht[Wine 2014a]
een groot deel van MFC is terug te vinden in de vorm van macro’s
in de eigen code,ook deze moeten werkend gemaakt worden [Wine
2014a]
Het lijkt veiliger om de code zonder deze emulatielaag te doen
werken.
4.2.2 Qt
Qt2 is een populair platformonafhankelijk applicatie- en
GUI-framework voor C++. De eerstepublieke versie kwam uit in 1995.
Het won snel aan populariteit als het de facto GUI frame-work voor
C++ applicaties op Linux nadat het gebruikt werd om de populaire
desktopomge-ving KDE3 mee te bouwen. [Qt 2014] Qt wordt gebruikt
door onder andere Nokia, Canonical(Ubuntu), Skype en VideoLan
(VLC).
Qt is beschikbaar onder de GPL (General Public License) en een
commerciële licentie. Omeen closed source applicatie te maken met
Qt moet een licentie aangekocht worden.
1http://winehq.org2Uitgesproken als /hkjuqt/ (”cute”) –
http://qt-project.com3http://kde.org
-
4.3 Eigen MFC implementatie 11
4.2.3 wxWidgets
wxWidgets4 is een ander gratis en vrij platformonafhankelijk C++
GUI- en applicatieframe-work. Het project werd gestart aan de
universiteit van Edinburgh in 1992 omdat de bestaandecommerciële
oplossingen te duur waren. [wxWidgets 2014c]
Dit framework is in gebruik erg gelijkaardig aan MFC. Beide
gebruiken een eventgebaseerdestructuur geconfigureerd met macro’s,
hebben klassen voor datastructuren, sockets, threads,GUIs, . . .
maar in tegenstelling tot MFC compileert code geschreven met
wxWidgets op zowelWindows, Mac OS als Linux.
wxWindows is beschikbaar onder de “wxWindows Library Licence”,
in essentie de LGPL(Library General Public License) met een
uitzondering die de distributie van afgeleide binariestoelaat.
[wxWidgets 2014d] Concreet betekent dit dat commerciële closed
source applicatiesdie met wxWidgets zijn geschreven zonder extra
licentiekosten kunnen worden verkocht (integenstelling tot Qt).
4.2.4 Boost
Boost5 is een populaire verzameling van platformonafhankelijke
C++ libraries van hoge kwa-liteit. De code is beschikbaar onder de
Boost Software License, een open source licentie diegebruik in
propriëtaire software toelaat. Veel van deze Boost libraries
vinden uiteindelijk ookhun weg tot inclusie in de officiële C++
standaard. [Schäling 2011]
Boost maakt onder andere multithreading, asynchrone sockets,
unicode en werken met hetbestandssysteem een stuk eenvoudiger en
platformonafhankelijk.
4.2.5 C++11 STD
De STD (STanDard library) – vaak ook verkeerdelijk STL (Standard
Template Library)genoemd6 – biedt een alternatief voor MFC strings
en datastructuren. Sinds 2011 bevat deSTD in de nieuwe C++11
standaard ook ondersteuning voor onder andere threads, mutexenen
conditions.
4.3 Eigen MFC implementatie
wxWidgets lijkt het meeste op MFC, en was daarmee de eerste
keuze voor dit project. Hoewelveel klassen bijna 1:1 konden
vervangen worden, werd na een aantal weken al snel duidelijk datdit
verre van altijd het geval was. Desondanks de gelijkenissen moesten
er vaak uitgebreidewrappers geschreven worden om de verschillen in
de API te kunnen overbruggen. Bepaaldeonderdelen waren zelfs
helemaal onbruikbaar: datastructuren zoals de list en map
werktenvolledig anders dan de MFC variant, waardoor ze niet gewrapt
konden worden.
4http://wxwidgets.org; Vroeger stond het project bekend onder de
naam wxWindows, maar onder drukvan Microsoft is het in 2004 van
naam moeten veranderen. [wxWidgets 2014a]
5http://boost.org6De STD is gebaseerd op de STL, maar is veel
uitgebreider
-
4.3 Eigen MFC implementatie 12
In de oorspronkelijke masterproefbeschrijving behoorde het
porten van de GUI ook tot deopdracht. Deze vereiste is in overleg
met MyForce vrij snel weggevallen eens de preciezegrootte van de
opdracht duidelijk werd. De GUI kon al los van de HardwareClient
server opeen andere (Windows)computer gestart worden om via het
netwerk met de server te verbinden.Het is voor de Linuxversie dus
voldoende om dit communicatieprotocol te ondersteunen.
Veel van de voordelen en redenen om wxWidgets te gebruiken
vielen hierdoor weg. Halverwegede stage werd beslist om deze
dependency te laten vallen, en werd alles herschreven met
nativeLinux functies en C++11 STD.
-
HET PORTING PROCES 13
Hoofdstuk 5
Het porting proces
We hebben voor de code een buildfile geschreven met CMake, en
beslist van MFC zo veelmogelijk te vervangen met C++11. Hoe gaan we
nu te werk?
In essentie is het eenvoudig: we starten het compileren door het
commando make uit te voeren,of door op de “build”-knop in de IDE te
klikken. Vervolgens wachten we tot dit faalt, enzoeken we in de
compileerfouten en -waarschuwingen naar de oorzaak.
Hierin vinden we informatie over welke headers, klassen,
functies en macro’s ontbreken. Zo-als reeds vermeld in hoofdstuk
4.2 is deze ontbrekende functionaliteit vervangen met
eenplatformonafhankelijk alternatief onbegonnen werk.
Om te weten wat een ontbrekende functie hoort te doen kunnen we
de Microsoft documentatieop de MSDN site raadplegen1. Een
alternatief kan dan via Google gevonden worden, of doorgericht te
zoeken in de Linux manual pages.
Wanneer in dit document Linuxfuncties vermeld worden zal dit op
de gebruikelijke manier uitde vakliteratuur gebeuren: met tussen
haakjes de manual sectie waarin het teruggevonden kanworden. Bij
printf(3) zijn we bijvoorbeeld gëınteresseerd in de versie uit de
C bibliotheek(man 3 ), niet die uit Bash (man 1 ).
Het is eenvoudiger om eerst lege wrappers toe te voegen zonder
implementatie, en deze pas inte vullen eens het project compileert.
Een macro zoals in codefragment 5.1 helpt hierbij.
#define STUBBED(x) do { \static bool seen_this = false; \if (!
seen_this) { \
seen_this = true; \fprintf(stderr , "STUBBED: %s at %s
(%s:%d)\n", \
x, __FUNCTION__ , __FILE__ , __LINE__ ); \} \
} while (0)
Codefragment 5.1: STUBBED macro [Gordon 2014]
1Tip: de zoekfunctie op de MSDN site lijkt zo goed als nooit de
juiste pagina terug te vinden, gebruikdaarom Google met de zoekterm
voorafgegaan door “msdn”.
-
5.1 Overzicht van de uiteindelijke mappenstructuur 14
5.1 Overzicht van de uiteindelijke mappenstructuur
Opdat de #include lijnen in de code zouden blijven werken,
werden de namen en indelingvan de MFC headers2 gelijk
overgenomen.
afx_linux/|-- afx.cpp|-- afx.h|-- afxio.cpp|-- afxio.h|--
afxmt.cpp|-- afxmt.h|-- afxsock.cpp|-- afxsock.h|-- afxtime.cpp|--
afxtime.h|-- algo/| |-- crc32.cpp| `-- crc32.h|-- datastruct/| |--
StdArray.h| |-- StdList.h| |-- StdMap.h| |-- StdString.cpp| `--
StdString.h`-- win32/
|-- win32_dll.cpp|-- win32_dll.h|-- win32_io.cpp|--
win32_io.h|-- win32_mt.cpp|-- win32_mt.h|-- win32_str.cpp|--
win32_str.h|-- win32_time.cpp|-- win32_time.h`-- win32_types.h
De algo, datastruct en win32 mappen volgen geen officiële
structuur of naamgeving, en zijnpuur voor de overzichtelijkheid
toegevoegd.
2Alle MFC headers worden geprefixt met “afx”, een restant van
het Application Framework X, een oudproject dat uiteindelijk is
uitgegroeid tot MFC [Wingo 1996].
-
DATATYPES EN -STRUCTUREN 15
Hoofdstuk 6
Datatypes en -structuren
6.1 Datatypes
In MFC-code wordt zelden rechtstreeks gebruik gemaakt van native
datatypes, zo goed alsalles heeft een eigen typedef die op Linux
zal moeten worden toegevoegd.
Enkele voorbeelden:
typedef int INT;typedef int BOOL;typedef long LONG;typedef
unsigned char BYTE;typedef void* HANDLE;typedef unsigned short WORD
,* LPWORD;
Codefragment 6.1: Enkele voorbeelden van MFC typedefs
In het laatste voorbeeld zie je hoe Microsoft Hongaarse notatie1
gebruikt. LPWORD staatvoor Long Pointer to WORD, waarmee hier een
32-bit pointer naar een 16-bit processorwoordbedoeld wordt: deze
naamgeving dateert duidelijk uit een tijd waar
processorarchitecturennog 16-bit waren.
6.2 Array, List en Map
CArray, CList en CMap uit MFC komen grotendeels overeen met de
C++11 std::array,std::list en std::unordered map. Het grootste
verschil zit in het itereren over elementen,wat hier niet met een
klassieke iterator gebeurt, maar (zoals in codefragment 6.2 te zien
is)met een POSITION pointer.
Deze drie datastructuren waren eerder al door een MyForce
programmeur herschreven metSTD datastructuren, deze wrappers konden
met een minimum aantal aanpassingen op Linux
1Een naamgevingconventie uitgevonden door Dr. Charles Simonyi,
Chief Architect bij Microsoft, en geborenin Hongarije. [Simonyi
1999] Bij deze naamgeving worden prefixen gebruikt die aangeven wat
het type is.
-
6.3 String 16
hergebruikt worden.
CList list;...POSITION pos = list.GetHeadPosition ();while
(pos){
int item = list.GetNext(pos);...
}
Codefragment 6.2: Voorbeeld van het itereren over een CList met
POSITION
In MFC komen ook een aantal gespecialiseerde array-, list- en
mapimplementaties voor, dezewerden met een eenvoudige typedef
ondersteund:
typedef CArray CStringArray;typedef CArray CDWordArray;typedef
CList CPtrList;typedef CMap CMapStringToPtr;typedef CMap
CMapStringToString;
Codefragment 6.3: Gespecialiseerde datastructuren
6.3 String
In hedendaagse standaard C++ vinden we 2 soorten strings: de
klassieke C-string (een pointernaar een char of wchar_t array,
afgesloten met ‘\0’), en de std::string (of std::wstring),een
wrapper die onder andere het geheugenbeheer op zich neemt. In MFC
vinden we in plaatsvan die laatste de gelijkaardige (maar erg
verwarrend genaamde) CString.
typedef char CHAR;typedef char* LPSTR;typedef const char*
LPCSTR;
typedef wchar_t WCHAR;typedef wchar_t* LPWSTR;typedef const
wchar_t* LPCWSTR;
#ifdef UNICODEtypedef WCHAR TCHAR;typedef LPWSTR LPTSTR;typedef
LPCWSTR LPCTSTR;
#elsetypedef CHAR TCHAR;typedef LPSTR LPTSTR;typedef LPCSTR
LPCTSTR;
#endif // unicode
Codefragment 6.4: Typedefs voor C-strings
-
6.3 String 17
De klassieke C-string wordt natuurlijk ook in MFC gebruikt,
hiervoor zijn een groot aantaltypedefs voorzien, zoals te zien in
codefragment 6.4.
Interessant is dat MFC naast de voorspelbare CHAR en WCHAR
typedefs ook een TCHAR definieert.Wanneer UNICODE gedefinieerd is,
worden wide characters gebruikt in plaats van de standaard1 byte
grote characters. De prefix t of _t zal in nog veel functie- en
methodenamen terugkerenmet dezelfde betekenis.
Dat “Unicode” volgens Microsoft betekent dat wide characters
gebruikt moeten worden iseen misvatting, en creëert bovendien
problemen op andere besturingssystemen.
6.3.1 Unicode
Om tekst op te slaan op computers worden karakters gemapt op
getallen. De klassieke manierwaarop dit gebeurde was de 7-bit ASCII
tabel. In deze tabel worden alle tien cijfers, enkelespeciale
tekens en de Engelse (accentloze) letters gemapt op de getallen
32-127. De eerste32 getallen komen overeen met onprintbare tekens
zoals ‘\0’ en ‘\n’. Gezien een byte 8 bitsgroot is liet dat OEMs
toe om met de overblijvende bit naar eigen keuze nog 128 extra
tekenstoe te voegen. Hier werden initieel nooit afspraken rond
gemaakt, waardoor het delen vanbestanden met speciale tekens voor
veel problemen zorgde.
De ANSI standaard loste dit probleem deels op door de
verschillende manieren om de laatste128 tekens in te delen vast te
leggen in code pages (zoals latin-1). Bij het openen van eentekst
wordt de bijhorende codepage geselecteerd om de correcte vreemde
tekens te kunnenweergeven. Dit was natuurlijk erg onhandig, en
werkte nog steeds niet voor talen als Chinees,waar er duizenden
tekens zijn. [Spolsky 2003]
In 1988 publiceerde Joseph D. Becker het eerste Unicode
voorstel. Hierin werd uitgegaan vanhet idee dat een verdubbeling
van de char-grootte naar 16 bits voldoende zou zijn om
allemogelijke karakters voor te stellen. [Radzivilovsky, Galka en
Novgorodov 2014]
In Unicode worden karakters gemapt op “theoretische” code
points, die losstaan van hoe zeop de schijf worden opgeslagen. Dit
wordt genoteerd als “U+” (voor Unicode), gevolgd doorhexadecimale
cijfers. De letter A wordt bijvoorbeeld voorgesteld door
U+0041.
De string “Hallo” komt overeen met vijf code points: U+0048
U+0061 U+006C U+006CU+006F.
Om dit op te slaan legt Unicode geen specifieke encodering op:
er zijn meerdere mogelijkheden,de meest gekende/gebruikte zijn
UCS-2, UTF-16, UTF-32 en UTF-8.
In de eerste versie van de standaard, gepubliceerd in 1991, werd
verondersteld dat een verdub-beling van de 8 bits char-grootte naar
16 bits voldoende zou zijn. Deze 2 bytes grote charsworden wide
characters genoemd, en worden onder Windows opgeslagen in het wchar
t type.De 2 bytes die in deze UCS-2 encodering gebruikt werden (16
bits, of 65536 mogelijke codepoints) bleken echter al snel niet
genoeg om alle mogelijke code points in op te slaan2.
2Op het moment van schrijven bevat de huidige versie van Unicode
(7.0) al zo’n 252603 toegewezen codepoints, waarvan 112804
grafische tekens zijn. [West 2014]
-
6.3 String 18
UTF-32 verdubbelt om deze reden de wide character grootte nog
eens naar 4 bytes. Ditis wat op Unix verstaan wordt onder wchar t.
Merk op dat wchar t dus niet zomaar kanuitgewisseld worden tussen
Windows (2 bytes) en Unix (4 bytes)!
4 bytes per code point is bijzonder geheugenintensief en in de
meeste gevallen onnodig groot.In 1996 ontstond hierdoor UTF-16,
waarbij de code points een variabele grootte kregen.Standaard is
dit nog steeds een wide character van 2 bytes groot, maar indien
nodig kan eenextra wide character gebruikt worden, zodat een code
point uiteindelijk 4 bytes kan innemen.UTF-16 wordt vooral op
Windows veel gebruikt.
Op Unix koos men voor een andere oplossing: aan het einde van
het jaar 1992 ontwikkeldenKen Thompson en Rob Pike uit ongenoegen
met de bestaande Unicode encoderingen UTF-8.UTF-8 gebruikt zoals
UTF-16 ook code points van variabele grootte, maar slaat die op
inchars in plaats van wchars. UTF-8 is hierdoor byte geöriënteerd
en verschilt in tegenstellingtot UTF-16 en UTF-32 niet in gebruik
op big of little endian systemen. [Pike 2003]
Een groot voordeel van deze encodering is dat bij Engelse tekens
de eerste bits altijd 0zijn en deze kunnen worden weggelaten; ze
nemen hierdoor maar één byte in. Een Engelsetekst geëncodeerd in
ASCII is hierdoor identiek aan diezelfde tekst geëncodeerd in
UTF-8!Hierdoor konden Unixsystemen op een vrij eenvoudige manier
Unicode adopteren, zondercompatibiliteit met oudere ASCII scripts
of configuratiebestanden te verliezen. UTF-8 is watop het web en in
de Unixwereld gebruikt wordt.
In sommige gevallen kan de UTF-8 encodering ook iets groter dan
UTF-16 uitvallen.
Figuur 6.1: Het verschil tussen de UTF-8 en UTF-16 representatie
van ‘AC’ [wxWidgets 2014b]
Wat we hieruit leren is dat Windowscode die gebruik maakt van
UTF-16 wide characters nietgebruikt kan worden op Linux: zowel de
grootte van de datastructuren als de encodering vande data
verschillen. In Windows worden native system calls voorzien in een
variant voor 8-bitASCII chars en een variant voor UTF-16 wchar_ts.
Op Linux vind je voornamelijk systemcalls voor UTF-8 chars, plus
enkele voor UTF-32 wchar_ts.
-
6.3 String 19
Bij het porten moet bijgevolg gezorgd worden dat op Linux en
Windows andere datatypesgebruikt worden. In de MFC code wordt
gelukkig voornamelijk gebruik gemaakt van deLPCTSTR typedef. Op
Linux laten we deze naar const char* wijzen in plaats van naarconst
wchar t*.
6.3.2 Implementatie CString
CString kan worden gëımplementeerd door std::string over te
erven en extra functies toete voegen. Overerving negeert
constructors en operator overloads, die moeten dus allemaalopnieuw
gedefinieerd worden.
CString () : std:: string () {}CString(const CString &str) :
std:: string(str) {}CString(const std:: string &str) : std::
string(str) {}CString(const char* cstr) : std:: string(cstr) {}
using std:: string :: operator +=;
CString& operator =( CString& str){
return this ->operator =( static_cast (str));}CString&
operator =(const char* str){
return this ->operator =(str);}CString& operator =(const
std:: string& str){
return this ->operator =(str);}
Codefragment 6.5: Constructors en operator overloads van
CString
Ook non-member function overloads zoals operator== moeten
opnieuw buiten de klasse ge-definieerd worden:
bool operator == (const CString& a, const CString&
b){
return (static_cast (a) == static_cast (b));}bool operator ==
(const char* a, const CString& b){
return (a == static_cast (b));}bool operator == (const
CString& a, const char* b){
return (static_cast (a) == b);}
Codefragment 6.6: CString non-member operator overload
-
6.3 String 20
Dit moet natuurlijk ook voor alle andere relationele operators
(!=, =) gebeuren.Daar komt dan nog ondersteuning voor een cast naar
const char* bij en een hoop eenvoudigewrapper methodes:
operator const char *() const{
return c_str ();}
const char& GetAt(size_t n) const{
return at(n);}
size_t GetLength () const{
return length ();}
BOOL IsEmpty () const{
return empty() ? TRUE : FALSE;}
Codefragment 6.7: Een selectie van enkele CString methodes
Sommige methodes vereisen iets meer werk, zoals onderstaande
Format. Deze gebruikt de-zelfde formaatbeschrijving van printf(3),
maar slaat het resultaat rechtstreeks in de CStringzelf op. We
kunnen hiervoor de vsnprintf(3) variant gebruiken, die zijn
resultaat naar eenbuffer in plaats van stdout schrijft.
void Format(LPCTSTR fmt , ...){
va_list args;va_start(args , fmt);
// determine buffer size (use _vscprintf on Windows !)int
bufsize = vsnprintf(NULL , 0, fmt , args) + 1; //+1 for '\0'
//make bufferchar* buf = (char*) malloc(bufsize*sizeof(char
));
//reset arglistva_end(args);va_start(args , fmt);
// formatvsnprintf(buf , bufsize , fmt , args);
//copy buffer to stringthis ->operator =(buf);
-
6.3 String 21
//free buffer and arglistfree(buf);va_end(args);
}
Codefragment 6.8: CString Format
6.3.3 Extra stringfuncties
Naast CString methodes moeten ook een aantal stringgerelateerde
functies gëımplementeerdworden. Gezien we op Linux mogen
veronderstellen dat alle tekst UTF-8 is, moeten wevoor prefix-“ t”
functies (besproken in de inleiding van dit hoofdstuk) zoals ttol
geen widecharacter variant voorzien.
long _ttol(const CString& str){
return atol(str.c_str ());}
Codefragment 6.9: ttol parst een long naar een CString
vsntprintf s heeft een gelijkaardig gedrag als de standaard
vsnprintf(3), maar heeft nogeen aantal extra veiligheidscontroles
ingebouwd. Naast de buffergrootte wordt bijvoorbeeldnog een extra
argument voor het aantal te kopiëren karakters meegegeven.
Eenzelfde vergelijking kan gemaakt worden voor strncpy s en
strncpy(3), en memcpy s enmemcpy(3).
int _vsnprintf_s(char *buffer , size_t sizeOfBuffer , size_t
count ,const char *format , va_list argptr)
{if (buffer == 0 || format == 0 || count sizeOfBuffer){
buffer [0] = '\0';return -1;
}else{
sizeOfBuffer = count +1;}
}
return vsnprintf(buffer , sizeOfBuffer , format , argptr );}
-
6.3 String 22
errno_t strncpy_s(char *strDest , size_t numberOfElements ,const
char *strSource , size_t count)
{if (strDest == NULL){
return EINVAL;}if (strSource == NULL){
strDest [0] = 0;return EINVAL;
}if (count == 0 || numberOfElements == 0){
return EINVAL;}if (count
-
6.3 String 23
return 0;}
Codefragment 6.10: implementatie vsnprintf s, strncpy s en
memcpy s
Het gedrag van al deze implementaties is gebaseerd op de
officiële MSDN documentatie.
6.3.4 Unicode in praktijk
Gezien de implementatie van CString nu intern UTF-8 gebruikt, is
een andere klasse nodigvoor het werken met UTF-16 strings. Omdat
een wchar op Linux 4 bytes groot is in plaatsvan 2 bytes, is een
std::wstring of een std::vector niet correct.
In C++11 is de ambigüıteit rondom de grootte van wchar opgelost
door de introductie vantwee nieuwe types: char16 t en char32 t. De
conversie tussen UTF-8 en UTF-16 maakt ookdeel uit van de C++11
standaard (in de vorm van std::codecvt utf8 utf16), maar was
opmoment van schrijven nog niet beschikbaar in GCC.
De compacte UTF-CPP bibliotheek3 werd als alternatief gekozen.
Deze gebruikt unsignedshort – altijd 2 bytes groot – als WCHAR
type, en std::vector als UTF-16 string.Gebaseerd hierop, werd een
CWString (geen echte MFC klasse) gemaakt:
typename unsigned short WCHAR;class CWString : public
std::vector {public:
CWString () {}CWString(LPCWSTR str);
CWString& operator =(std::vector & str);
operator LPCWSTR () const;};
Codefragment 6.11: CWString een UTF-16 CString klasse
In HardwareClient werd al onderscheid gemaakt tussen WCHAR
(UTF-16) strings en CHAR(ANSI) strings. Waar nodig worden ze
omgezet met de functies CW2A (Convert Wide toANSI) en CA2W (Convert
ANSI to Wide). In de Linux implementatie bevat de CHAR stringUTF-8,
en kunnen de functies CW2A en CA2W dus gebruikt worden om de
conversie tussenUTF-8 en UTF-16 uit te voeren.
const char* CW2A(const CString& str){
//our CString isn't wide , and is already UTF -8, so do
nothingreturn str.c_str ();
}
3http://utfcpp.sourceforge.net/
-
6.3 String 24
// LPCSTR (Long Pointer Const STRing ): const char*CWString
CA2W(LPCSTR str){
std:: string s_utf8(str);CWString s_utf16;
utf8:: utf8to16(s_utf8.begin(),utf8::
find_invalid(s_utf8.begin(),
s_utf8.end()),back_inserter(s_utf16 ));
return s_utf16;}
// LPCWSTR (Long Pointer Const Wide STRing ): const
WCHAR*CString CW2A(LPCWSTR str){
LPCWSTR end = str;while (*end != 0)
end++;
CString out8;
utf8:: utf16to8(str , end+1, back_inserter(out8 ));
return out8;}
Codefragment 6.12: Conversiefuncties tussen UTF-8 en UTF-16
6.3.4.1 Vergelijken van tekens
Een extra complicatie doet zich in het volgende geval voor:
CString s = _T("caf é");assert (s.GetLength () == 4);assert
(s.at(3) == ' é');
Codefragment 6.13: Problemen bij multi byte strings
Onder Windows zullen deze asserts slagen, want CString gebruikt
intern wide characters.Onze Linux implementatie gebruikt multi byte
characters, s.GetLength() zal 5 teruggeven,en s.at(3) een
onprintbaar teken, omdat char 3 en 4 enkel samen ‘é’ vormen. De
code zalop Linux zelfs niet compileren, omdat ‘é’ niet in een
const char past.
In codefragment 6.14 is te zien hoe we dit probleem aanpakken.
Om in de UTF-8 versie eenteken te kunnen vergelijken wordt het
eerst omgezet naar een wide character. De volledigestring omzetten
(met bijvoorbeeld std::mbstowcs) en pas daarna de tekens één voor
éénvergelijken vereist het aanmaken van een buffer (van een op
voorhand ongekende grootte)en dat 2 keer door de string moet worden
gelopen. Deze verspilling van geheugen en tijd iste vermijden door
maar 1 teken (code point) per keer om te zetten naar een wide
character
-
6.3 String 25
(zoals hier met behulp van std::mbrtowc).
#ifdef _WIN32CString strCopy = strString;const TCHAR* ch =
strCopy.GetBuffer ();while ((*ch) != _T('\0')){
//we only accept space and printable charactersif (! _istspace
(*ch) && !_istprint (*ch) &&
*ch != _T('\b') && *ch != _T('\f') &&*ch !=
_T('\a') && *ch != _T('\u20ac')) //u+20ac:EUR
{return false;
}ch++;
}return true;
#else// We have to do this a bit different because we use UTF
-8const char* mbStr = strString.GetBuffer ();
std:: locale l(""); // defaults to system localestd:: mbstate_t
state = std:: mbstate_t ();const char* end = mbStr + std::
strlen(mbStr);int len;wchar_t wc;
while ((len = std:: mbrtowc (&wc, mbStr , end -mbStr ,
&state)) > 0){
if (!std:: isspace(wc,l) && !std:: isprint(wc,l)
&&wc != L'\b' && wc != L'\f' &&wc != L'\a'
&& wc != L'\u20ac') //u+20ac:EUR
{return false;
}mbStr += len;
}return len == 0; //else the parsing failed , so def.
invalid
#endif
Codefragment 6.14: Tekens vergelijken met UTF-8 aan de hand van
std::mbrtowc
-
TIME 26
Hoofdstuk 7
Time
7.1 GetTickCount
GetTickCount is een Windowsfunctie die de milliseconden sinds
het opstarten van het sys-teem teruggeeft. Het wordt voornamelijk
gebruikt voor benchmarking (het verschil tussen 2oproepen).
Het aantal milliseconden sinds het opstarten kunnen we op Linux
niet opvragen, maar geziendit enkel gebruikt wordt om het verschil
te bepalen, zouden we hier ook de systeemklok voorkunnen gebruiken.
De standaard gettimeofday(2) gebruiken we hier beter niet, gezien
dieklok bëınvloed wordt door het Network Time Protocol (NTP). Als
de klok tussen 2 aanroepenzou synchroniseren zou dit de meting (het
verschil) ongeldig maken. [Habets 2010]
long GetTickCount (){#ifdef CLOCK_MONOTONIC_COARSE //check if
available
struct timespec ts;if (! clock_gettime(CLOCK_MONOTONIC_COARSE ,
&ts)) {
return (ts.tv_sec * 1000) + (ts.tv_nsec / 1000000);}
#endif// fallbackstruct timeval tv;gettimeofday (&tv,
NULL);return (ts.tv_sec * 1000) + (tv.tv_usec / 1000);
}
Codefragment 7.1: Implementatie van GetTickCount
Een MONOTONIC klok geeft het aantal (nano)seconden sinds een
onbepaald tijdstip, zondersprongen in de tijd bij NTP
synchronisaties. De man page van de clock gettime(2) Unixfunc-tie
vermeldt twee interessante Linuxspecifieke addities: CLOCK
MONOTONIC RAW (vanaf Linux2.6. 28) en de snellere, maar minder
precieze CLOCK MONOTONIC COARSE (vanaf Linux 2.6.32).Gezien
GetTickCount als eenheid milliseconden in plaats van nanoseconden
gebruikt, volstaatde verminderde precisie van de COARSE variant
voor onze implementatie. [RedHat 2014]
-
7.2 CTime 27
Wat niet direct duidelijk is in de documentatie is dat de inhoud
van de timespec of timevalstruct samengeteld moet worden om de
correcte tijd te krijgen. tv_nsec is geen precie-zere versie van
tv_sec, maar het aantal nanoseconden die bij tv_sec moeten worden
toege-voegd.
7.2 CTime
CTime is een MFC datastructuur voor kalendertijdstippen. Er zijn
een aantal belangrijkeverschillen met de Unixfuncties, wat al
direct merkbaar is in de implementatie van de con-structor:
CTime::CTime(int year , int month , int day ,int hour , int
minute , int second)
{struct tm t;
t.tm_year = year -1900;t.tm_mon = month;t.tm_mday =
day;t.tm_hour = hour;t.tm_min = minute;t.tm_sec = second;
t.tm_isdst = -1; //auto
time_t time = mktime (&t);if (time != -1)
m_time = time;}
Codefragment 7.2: CTime constructor
Op te merken: tm year bevat het aantal jaren sinds 1900, tm
isdst bepaalt of Daylight SavingTime (DST) in effect is. Positief
voor zomertijd, 0 voor wintertijd. Bij een negatieve waardewordt
dit automatisch bepaald aam de hand van de opgegeven datum.
De klasse bevat verder enkele voordehandliggende getters en
operator overloads. Te vermeldenwaard zijn de volgende
methodes:
CTime CTime:: operator+ (const CTimeSpan& span){
struct tm t = *localtime (& m_time );t.tm_mday +=
span.m_days;t.tm_hour += span.m_hours;t.tm_min +=
span.m_mins;t.tm_sec += span.m_secs;t.tm_isdst = -1; //auto
return CTime(mktime (&t));}
-
7.3 GetSystemTime en GetLocalTime 28
int CTime:: GetYear (){
return localtime (& m_time)->tm_year + 1900;}
int CTime:: GetDayOfWeek (){
return localtime (& m_time)->tm_wday + 1;}
CString CTime:: Format(const char* fmt){
char buf [1024];
if (std:: strftime(buf , sizeof(buf), fmt , localtime (&
m_time )) == 0){
// either strlen is actually supposed to be 0, or//the buffer
was too small. I can't predict this ,//so in case of bugs: make the
buffer bigger!
buf[0] = '\0';}
return CString(buf);}
Codefragment 7.3: Enkele CTime methodes
Bij het optellen met een timespan (CTimeSpan is een klasse met
een int voor elke tijdgroot-heid) moeten we geen rekening houden
met het “overflowen” (90 seconden is 1 minuut en 30seconden),
mktime(3) zal dit zelf doen. We mogen wel niet vergeten van isdst
terug op autote zetten, want dit kan door het optellen
veranderen.
Om het jaar te krijgen moeten we natuurlijk weer 1900 bijtellen.
Op Linux is maandag-zondag 0-6, op Windows is het 1-7. De
formattering vlaggen komen na het vergelijken vande documentatie
gelukkig volledig overeen.
7.3 GetSystemTime en GetLocalTime
GetSystemTime en GetLocalTime zijn twee native win32 functies
die de tijd teruggeven in eenSYSTEMTIME struct.
typedef struct _SYSTEMTIME {WORD wYear;WORD wMonth;WORD
wDayOfWeek;WORD wDay;WORD wHour;WORD wMinute;
-
7.4 COleDateTime en COleDateTimeSpan 29
WORD wSecond;WORD wMilliseconds;
} SYSTEMTIME , *LPSYSTEMTIME;
Codefragment 7.4: de SYSTEMTIME struct
GetLocalTime geeft zoals de naam doet vermoeden de lokale tijd,
GetSystemTime geeft de tijdin UTC (zie codefragment 7.5).
void GetSystemTime(LPSYSTEMTIME lpSystemTime){
struct timeval tod;struct tm lintime;
gettimeofday (&tod , NULL);gmtime_r (&(tod.tv_sec),
&lintime ); //UTC
lpSystemTime ->wYear = static_cast ( lintime.tm_year
);lpSystemTime ->wMonth = static_cast ( lintime.tm_mon
);lpSystemTime ->wDayOfWeek = static_cast ( lintime.tm_wday
);lpSystemTime ->wDay = static_cast ( lintime.tm_mday
);lpSystemTime ->wHour = static_cast ( lintime.tm_hour
);lpSystemTime ->wMinute = static_cast ( lintime.tm_min
);lpSystemTime ->wSecond = static_cast ( lintime.tm_sec
);lpSystemTime ->wMilliseconds = static_cast (tod.tv_usec
/1000);
}
Codefragment 7.5: Implementatie van GetSystemTime
GetLocalTime wordt op dezelfde manier geimplementeerd, maar
gebruikt localtime r(3) inplaats van gmtime r(3).
7.4 COleDateTime en COleDateTimeSpan
Een andere MFC tijdklasse is COleDateTime. In HardwareClient
worden hier niet veel me-thodes van gebruikt, dus de implementatie
ging vrij snel. C++11 introduceerde een nieuwegestandaardiseerde
manier om in C++ met tijd te werken: std::chrono. COleDateTime
enCOleDateTimeSpan werden hiermee gëımplementeerd.
class COleDateTime{private:
std:: chrono :: system_clock :: time_point m_dt;public:
COleDateTime (){}
COleDateTime(const std:: chrono :: system_clock ::
time_point& tp){
m_dt = tp;}
-
7.4 COleDateTime en COleDateTimeSpan 30
COleDateTime(const COleDateTime& dt);{
m_dt = dt.m_dt;}static COleDateTime GetCurrentTime (){
return COleDateTime(std:: chrono :: system_clock ::now
());}CString Format () const{
std:: time_t tim = std:: chrono :: system_clock ::
to_time_t(m_dt);return CString(ctime(&tim));
}};
class COleDateTimeSpan{public:
enum DateTimeSpanStatus { valid , invalid , null };private:
std:: chrono :: seconds timeSpan;DateTimeSpanStatus
m_status;
public:COleDateTimeSpan () { }COleDateTimeSpan(long lDays , int
nHours , int nMins , int nSecs){
timeSpan = (std:: chrono ::hours (24* lDays + nHours)+ std::
chrono :: minutes(nMins)+ std:: chrono :: seconds(nSecs ));
}
DateTimeSpanStatus GetStatus () const{
return m_status;}void SetStatus(DateTimeSpanStatus status){
m_status = status;}
};
Codefragment 7.6: COleDateTime en COleDateTimeSpan
implementatie
-
IO 31
Hoofdstuk 8
IO
8.1 Sockets
8.1.1 TCP keepalive
In IPv4 netwerken wordt gewoonlijk gebruik gemaakt van NAT om
meerdere apparaten tekunnen aansluiten op één netwerkverbinding.
De router houdt hiertoe een lijst bij van alleclient-server
verbindingen voor elk apparaat. Door de fysieke beperkingen van
vele routers ishet aantal verbindingen dat ze zo in het geheugen
kunnen houden gelimiteerd. Vele imple-mentaties “vergeten” hierdoor
oudere inactieve (maar open) verbindingen ten voordele vannieuwe
actieve verbindingen. [Mueller 2011] Het vervelende aan deze
implementatie is dat deverbinding niet “officieel” verbroken wordt,
en client noch server hier dus van op de hoogtegebracht worden.
Wanneer de verbinding uit onwetendheid toch gebruikt wordt, zullen
allepakketten verloren gaan. Het programma kan dan pas na een (of
meerdere) time-out periodesbeseffen dat de verbinding verbroken
is.
Sommige routers laten inactieve verbindingen al na 5 minuten
vallen, een probleem dat ookMyForce ondervonden heeft. Om dit op te
lossen gebruiken ze TCP keep-alive pakketten.Deze periodiek
verstuurde berichten bevatten geen data en vragen enkel om een
bevestigingvan ontvangen. [Guha 2008] Verbroken verbindingen kunnen
zo snel opgespoord worden, enhet houdt de verbinding bovenaan de
actieve lijst in de router.
struct tcp_keepalive sKeepAliveSettings = {0};DWORD dwResultSize
= 0L;sKeepAliveSettings.onoff = 1;sKeepAliveSettings.keepalivetime
= 60000; //in mssKeepAliveSettings.keepaliveinterval = 1000; //in
msWSAIoctl(m_hSocket , SIO_KEEPALIVE_VALS , &sKeepAliveSettings
,
sizeof(sKeepAliveSettings), NULL , 0, &dwResultSize , NULL ,
NULL);
Codefragment 8.1: TCP keep-alive activeren met WSA voor m
hSocket
TCP keep-alive wordt door de grote pakketvloed niet universeel
aanvaard en staat daaromstandaard uit. [Internet Engineering Task
Force 1989] Met de Windows Sockets API (WSA)
-
8.1 Sockets 32
activeer en configureer je het zoals in codefragment 8.1.
TCP keep-alive kan op Linux zoals elke andere socket optie
geactiveerd worden met hetcommando setsockopt(2) (level SOL
SOCKET):
int keepalive = 1; // 1 -> enabledsetsockopt(s, SOL_SOCKET ,
SO_KEEPALIVE , &( keepalive), sizeof(int ));
Codefragment 8.2: TCP keep-alive activeren op Linux voor socket
s
Voor verdere configuratie zoals in het WSA voorbeeld moeten we
naar de man page vantcp(7) kijken, we vinden daar deze globale
sysctl1 variabelen:
tcp_keepalive_intvl (integer; default: 75; since Linux 2.4)The
number of seconds between TCP keep-alive probes.
tcp_keepalive_probes (integer; default: 9; since Linux 2.2)The
maximum number of TCP keep-alive probes to send before giv-ing up
and killing the connection if no response is obtainedfrom the other
end.
tcp_keepalive_time (integer; default: 7200; since Linux 2.2)The
number of seconds a connection needs to be idle before TCPbegins
sending out keep-alive probes. Keep-alives are only sentwhen the
SO_KEEPALIVE socket option is enabled. The defaultvalue is 7200
seconds (2 hours). An idle connection is termi-nated after
approximately an additional 11 minutes (9 probes aninterval of 75
seconds apart) when keep-alive is enabled.
Note that underlying connection tracking mechanisms and
applica-tion timeouts may be much shorter.
Deze variabelen aanpassen zou de configuratie globaal maken, wat
duidelijk een stap te veris. Wat verder in de man page vinden we
dat deze configuratie ook per socket kan wordentoegepast door
opnieuw setsockopt(2) te gebruiken, maar dit keer met level SOL
TCP2 inplaats van SOL SOCKET. [Busatto 2007]
TCP KEEPCNT configureert tcp keepalive probesTCP KEEPIDLE
configureert tcp keepalive timeTCP KEEPINTVL configureert tcp
keepalive intvl
In de Windowscode kwamen maar twee configuratie opties voor: tcp
keepalive::keepalive-time en tcp keepalive::keepaliveinterval. Deze
komen respectievelijk overeen met tcpkeepalive time (TCP KEEPIDLE)
en tcp keepalive intvl (TCP KEEPINTVL), op één verschil na:op
Windows worden deze uitgedrukt in milliseconden, op Linux in
seconden. De ontbrekendeoptie voor tcp keepalive probes (TCP
KEEPCNT) is op Windows niet configureerbaar en heeftals waarde
(sinds Windows Vista) altijd 10. [Microsoft 2014f]
1Het sysctl commando dient om at runtime kernel parameters aan
te passen.2Hier is een #include voor nodig.
-
8.1 Sockets 33
#include #include
#define SIO_KEEPALIVE_VALS 4struct tcp_keepalive {
int onoff;int keepalivetime;int keepaliveinterval;
};
int WSAIoctl(SOCKET s, DWORD dwIoControlCode , LPVOID
lpvInBuffer ,DWORD cbInBuffer , LPVOID lpvOutBuffer , DWORD
cbOutBuffer ,LPDWORD lpcbBytesReturned , void *lpOverlapped ,void
*lpCompletionRoutine)
{assert(dwIoControlCode == SIO_KEEPALIVE_VALS ); //only
implemented option
tcp_keepalive *keepalive = static_cast ( lpvInBuffer );
if (0 != setsockopt(s, SOL_SOCKET , SO_KEEPALIVE ,
&(keepalive ->onoff),sizeof(keepalive ->onoff )))
{fprintf(stderr , "Could not enable TCP keep -alive\n");return
1;
}if (0 != setsockopt(s, SOL_TCP , TCP_KEEPIDLE ,
&(keepalive ->keepalivetime),sizeof(keepalive
->keepalivetime )))
{fprintf(stderr , "Could not set tcp_keepalive_time\n");return
2;
}if (0 != setsockopt(s, SOL_TCP , TCP_KEEPINTVL ,
&(keepalive ->keepaliveinterval),sizeof(keepalive
->keepaliveinterval )))
{fprintf(stderr , "Could not set tcp_keepalive_time\n");return
3;
}
// tcp_keepalive_probes = TCP_KEEPCNT (default :9)int probes =
10; // default on Windowsif (0 != setsockopt(s, SOL_TCP ,
TCP_KEEPCNT , &( probes), sizeof(probes ))){
fprintf(stderr , "Could not set tcp_keepalive_time\n");return
4;
}
return 0;}
Codefragment 8.3: De resulterende TCP keep-alive wrapper
-
8.1 Sockets 34
Tabel 8.1: Een overzicht van hoe de verschillende TCP keep-alive
parameters overeenkomen
sysctl setsockopt Windows WSA
tcp keepalive time TCP KEEPIDLE tcp
keepalive::keepalivetime/1000tcp keepalive intvl TCP KEEPINTVL tcp
keepalive::keepaliveinterval/1000tcp keepalive probes TCP KEEPCNT
(altijd 10)
8.1.2 Asynchrone sockets
MFC maakt werken met asynchrone sockets op Windows heel
eenvoudig door een CAsync-Socket klasse aan te bieden met
overschrijfbare callback methodes (OnConnect, OnAccept,OnReceive,
...). Het framework zorgt zelf voor de eventloop die dit mogelijk
maakt.
Op Linux zijn sockets synchroon en is er geen standaard
asynchrone manier van werken, erzijn meerdere opties:
8.1.2.1 select(2)
Een oplossing besproken in Veerle Ongenae (2014).
Computernetwerken IV (cursus) isselect(2), deze functie heeft
echter enkele belangrijke nadelen. File descriptors (FD) wordenhier
bijgehouden in een bitmap, waardoor bij het aanmaken van de
datastructuur de maxi-mum FD moet worden opgegeven. Wanneer
duizenden connecties zullen verwerkt worden, ishet onmogelijk te
voorspellen hoeveel file descriptors nodig zullen zijn, laat staan
welke maxi-mumwaarde deze zullen krijgen. [Silva 2009] Select(2) is
verder gelimiteerd tot FD SETSIZE(gewoonlijk 1024) file descriptors
per proces, en is (vooral bij inactieve verbindingen) aan-toonbaar
trager dan de alternatieven. [Gammo e.a. 2004]
8.1.2.2 poll(2)
Poll(2) laat de restrictie op het aantal file descriptors
vallen, maar wint verder niet veelin snelheid. Het verwerken van
file descriptors gebeurt zoals bij select nog steeds
lineair,waardoor het nagaan op activiteit bij een grote hoeveelheid
verbindingen erg traag wordt eneen grote bottleneck vormt.
[Stenberg 2014]
8.1.2.3 epoll(7)
Geen enkele van de hierboven vernoemde beperkingen komen voor in
de Linux specifiekeepoll(7) interface. Hier wordt de array met file
descriptors beheerd vanuit de kernel inplaats van in userspace.
Een eigen implementatie van de MFC CAsyncSocket met dit systeem
is doenbaar3, maarvereist veel tijd om grondig te kunnen testen,
tijd die gezien de grote scope van het projectniet beschikbaar
was.
3Voor een duidelijk uitgewerkt voorbeeld, zie Mukund Sivaraman
(2011). How to use epoll? A completeexample in C. url:
https://banu.com/blog/2/how-to-use-epoll-a-complete-example-in-c/
https://banu.com/blog/2/how-to-use-epoll-a-complete-example-in-c/
-
8.2 Files 35
Vele anderen hebben natuurlijk al over deze problematiek
nagedacht, er zijn vele bibliothekenmet kant en klare
implementaties te vinden:
8.1.2.4 libevent, libev, libuv
De populairste platformonafhankelijke libraries die event driven
asynchrone sockets aanbiedenzijn libevent4, libev5 en het recentere
libuv6, dat gebruikt wordt (en effectief gecreëerd is
voor)node.js. Op Linux werden ze allemaal gëımplementeerd met
epoll(7).
8.1.2.5 (C++11 / boost) Asio
C++11 heeft crossplatform development al veel vereenvoudigd door
onder andere het gebruikvan threads in de standaard op te nemen.
File IO werd al lang ondersteund, maar socketsnog altijd niet.
Boost7 heeft wel een implementatie voor asynchrone sockets,
genaamd Asio. Deze bibliotheekis intussen ook beschikbaar in een
C++11 versie, zodat deze onafhankelijk van andere Boostbibliotheken
kan worden gebruikt. Deze versie is al in meerdere revisies
voorgesteld vooropname in de C++ standaard. [Kohlhoff 2006;
Kohlhoff 2007; Kohlhoff 2012]
Gezien het gebruik van deze library erg veel lijkt op andere
delen van de STD, en de kansop inclusie in de standaard groot is,
vormt deze bibliotheek een goede keuze. Vooraleerasynchrone
socketwrappers gëımplementeerd kunnen worden is een event loop
nodig, en datdeel van multithreading werd niet tijdig afgewerkt. De
socketimplementatie is daardoor ookuitgebleven.
8.2 Files
Veel IO C-functies zijn gestandaardiseerd, maar hebben op
Windows een variant waarvoor nogondersteuning toegevoegd moet
worden. taccess, tremove en tfopen s komen bijvoorbeeldovereen met
access(2), remove(3) en fopen(3).
Sommige functies hebben een andere naam: CreateDirectory werd
vervangen met mkdir(2),GetCurrentDirectory met getcwd(3),
CreateFile met open(2) (en de O CREATE vlag), Write-File met
write(2), enzovoort.
4http://libevent.org5http://software.schmorp.de/pkg/libev.html6https://github.com/joyent/libuv7Boost
is een collectie van open source C++ bibliotheken die de huidige
standaard uitbreiden. Ze zijn
compatibel met de STD, en hebben een gelijkaardige syntax. In
het Technical Report (TR1) en de uiteindelijkeC++11 standaard zijn
al 10 Boost bibliotheken opgenomen. [Schäling 2011] Voor TR2 wordt
gekeken naarandere Boost bibliotheken voor onder andere Unicode en
networking. [C++ Standards Committee 2005]
-
8.2 Files 36
8.2.1 CFile
MFC heeft ook een eigen systeem om met bestanden te werken:
CFile. Dit doet op veelpunten denken aan de gestandaardiseerde C
FILE api, op een aantal verschillen na.
class CFile{private:
std:: string m_filename;FILE* m_file;
public:enum OpenFlags {
modeRead = (int) 0x00000 ,modeWrite = (int) 0x00001
,modeReadWrite = (int) 0x00002 ,shareDenyWrite = (int) 0x00020
,shareDenyRead = (int) 0x00030 ,shareDenyNone = (int) 0x00040
,modeCreate = (int) 0x01000};
CFile (): m_file(NULL) {}virtual ˜CFile ();
enum Attribute {normal = 0x000 ,readOnly = 0x001
};
virtual BOOL Open(LPCTSTR lpszFileName , UINT nOpenFlags
,CFileException* pError = NULL);
virtual ULONGLONG GetLength () const;virtual UINT Read(void*
lpBuf , UINT nCount );virtual void Write(const void* lpBuf , UINT
nCount );virtual void Flush ();virtual void Close ();BOOL
GetStatus(CFileStatus& rStatus) const;
};
struct CFileStatus{
CTime m_ctime; // creation date/time of fileCTime m_mtime; //
last modification date/time of fileCTime m_atime; // last access
date/time of fileULONGLONG m_size; // logical size of file in
bytesBYTE m_attribute; // logical OR of CFile:: Attribute enum
valuesTCHAR m_szFullName[MAX_PATH ]; // absolute path name
};
Codefragment 8.4: de header van CFile en CFileStatus
-
8.2 Files 37
In de declaratie van CFile in codefragment 8.4 zijn meteen de
Read, Write, Flush en Closemethodes te herkennen. GetStatus en de
bijhorende CFileStatus struct doen dan weer denkenaan de Unix
stat(2) functie en gelijknamige struct. Het enige grote verschil
met de C FILEAPI zit bij het opgeven van de mode bij het openen:
FILE neemt een string, CFile neemteen int opgebouwd uit
bitvlaggen.
Kijken we naar de native Linux file IO API, dan zien we dat de
open(2) functie net als CFileeen int vlag gebruikt. Maar deze
manier van werken is dan weer niet gebufferd zoals FILE,en bestaat
er geen concept van flushen. Dit betekent dat deze API veel trager
in gebruik zalzijn, gezien wegschrijven de thread zal
blokkeren.
Gezien CFile verder zo goed overeenkomt met FILE, kan het best
daarmee gëımplementeerdworden. Bij het openen moeten de
modevlaggen vertaald worden:
BOOL CFile::Open(LPCTSTR lpszFileName , UINT nOpenFlags
,CFileException* pError)
{//close current file if already openif (m_file != NULL){
fclose(m_file ); //will flush firstm_file = NULL;
}//save filenamem_filename = lpszFileName;
// translate nOpenFlags to fopen (3) formatstd:: string
linFlags;linFlags.reserve (5);
if (nOpenFlags & modeRead){
linFlags += 'r';}
if (nOpenFlags & modeWrite){
if (nOpenFlags & modeCreate)linFlags += "w+";
elselinFlags += 'w';
}
else if (nOpenFlags & modeReadWrite){
linFlags += "w+";}
//open filem_file = fopen(lpszFileName , linFlags.c_str ());
-
8.2 Files 38
//set (advisory) locksif (m_file == NULL){
return FALSE;}else{
if (nOpenFlags & shareDenyRead)flock(fileno(m_file), LOCK_EX
); // exclusive lock (advisory)
else if (nOpenFlags & shareDenyWrite)flock(fileno(m_file),
LOCK_SH ); // shared lock (advisory)
//else shareDenyNone -> default}
return TRUE;}
Codefragment 8.5: CFile::Open methode
Onder Windows worden op bestanden echte bestandssysteemlocks
gelegd, opdat andere pro-cessen hier geen toegang toe zouden
krijgen. Op Linux wordt dat niet gedaan, als er al ietsgelockt
wordt is dit advisory en kan het door een proces gerust genegeerd
worden.
De CFile implementatie gebruikt een combinatie van de C FILE API
en de stat(2) functie.Voor die laatste was de bestandsnaam nodig,
die niet meer opgevraagd kan worden met enkelde FILE pointer.
Daarom werd dit bij het openen ook in een privaat veld
opgeslagen.
CFile ::˜ CFile(){
if (m_file != NULL){
Flush ();Close ();
}}
ULONGLONG CFile:: GetLength () const{
struct stat stbf;if (stat(m_filename.c_str(), &stbf) !=
0)
return stbf.st_size;//TODO throw CFileException bij foutreturn
0;
}
UINT CFile::Read(void* lpBuf , UINT nCount){
if (m_file != NULL)return fread(lpBuf , sizeof(char), nCount ,
m_file );
return 0;}
-
8.2 Files 39
void CFile::Write(const void* lpBuf , UINT nCount){
if (m_file != NULL)fwrite(lpBuf , sizeof(char), nCount , m_file
);
}
void CFile::Flush(){
if (m_file != NULL)fflush(m_file );
}
void CFile::Close(){
if (m_file != NULL){
flock(fileno(m_file), LOCK_UN ); // release lockfclose(m_file
);
}}
BOOL CFile:: GetStatus(CFileStatus& rStatus) const{
struct stat stbf;if (stat(m_filename.c_str(), &stbf) !=
0){
rStatus.m_ctime = stbf.st_ctime;rStatus.m_mtime =
stbf.st_mtime;rStatus.m_atime = stbf.st_atime;rStatus.m_size =
stbf.st_size;rStatus.m_attribute = 0;if (access(m_filename.c_str(),
W_OK) != 0)
rStatus.m_attribute |= readOnly;realpath(m_filename.c_str(),
rStatus.m_szFullName );
return TRUE;}return FALSE;
}
Codefragment 8.6: Enkele CFile methodes
8.2.2 CFileFind
Met CFileFind kunnen alle bestanden in een opgegeven map, die
voldoen aan een opgegevenpatroon (met wildcards) worden
teruggevonden.
Met de systeemfunctie fnmatch(3) kan een bestandsnaam gematcht
worden tegen een patroon,rest ons nu nog een lijst van alle
bestanden te bekomen. Op Linux zijn er 2 systeemfunctiesdie ons
hierbij kunnen helpen: scandir(3) en ftw(3) (file tree walk). Het
verschil zijnde datde laatste recursief werkt en ook in submappen
zoekt. Beiden nemen een callback naar een
-
8.2 Files 40
filterfunctie. Geen van beiden laten echter toe van een patroon
mee te geven.
De manier waarop CFileFind paden met wildcards behandelt doet
sterk denken aan shellwildcard expansion, en daar bestaat ook een
systeemfunctie voor: wordexp(3). Met dezefunctie kan CFileFind
volledig gëımplementeerd worden.
class CFileFind{public:
CFileFind ();virtual ˜CFileFind ();
virtual BOOL FindFile(LPCTSTR pstrName = NULL , DWORD dwUnused =
0);virtual BOOL FindNextFile ();BOOL IsDirectory () const;BOOL
IsDots () const;virtual CString GetFilePath () const;virtual BOOL
GetCreationTime(CTime& refTime) const;void Close ();
private:wordexp_t m_wexp;size_t m_idx;
};
/* ****** implementation ****** */
CFileFind :: CFileFind () : m_idx (0){
m_wexp.we_wordc = 0;}
CFileFind ::˜ CFileFind (){
wordfree (& m_wexp );}
void CFileFind ::Close(){
wordfree (& m_wexp );}
BOOL CFileFind :: FindFile(LPCTSTR pstrName , DWORD
dwUnused){
//resetm_idx = 0;wordfree (& m_wexp );
//findif (0 == wordexp(pstrName , &m_wexp , WRDE_NOCMD
))
return TRUE;else
return FALSE;}
-
8.2 Files 41
BOOL CFileFind :: FindNextFile (){
if (++ m_idx < m_wexp.we_wordc)return TRUE;
elsereturn FALSE;
}
BOOL CFileFind :: IsDirectory () const{
if (m_wexp.we_wordv == NULL || m_idx >=
m_wexp.we_wordc)return FALSE;
struct stat st;if (0 ==
stat(m_wexp.we_wordv[m_idx],&st))
if (S_ISDIR(st.st_mode ))return TRUE;
return FALSE;}
// return ABSOLUTE pathCString CFileFind :: GetFilePath ()
const{
char buf[PATH_MAX ];CString out("");
if (m_wexp.we_wordv != NULL && m_idx <
m_wexp.we_wordc)if (NULL != realpath(m_wexp.we_wordv[m_idx],
buf))
out = buf;
return out;}
BOOL CFileFind :: GetCreationTime(CTime& refTime) const{
// Ext4 FS does have this information , but I can't access it.//
Linux filesystems historically never stored creation time.// Hence
the target centos release doesn 't have the API (yet).//// =>
return modification time
if (m_wexp.we_wordv == NULL || m_idx >=
m_wexp.we_wordc)return FALSE;
struct stat st;if (0 !=
stat(m_wexp.we_wordv[m_idx],&st))
return FALSE;
refTime = st.st_mtime;return TRUE;
}
-
8.3 CRC-32 en Zip 42
// "." or ".."BOOL CFileFind :: IsDots () const{
//will never show up in wordexp resultsreturn FALSE;
}
Codefragment 8.7: CFileFind implementatie
CFileFind kan in zijn resultaten ook . en .. teruggeven, het
huidige resultaat kan hierop wor-den nagekeken met de methode
IsDots. Wordexp(3) zal deze nooit in zijn resultaten opnemen,en
zijn in HardwareClient alvast ook nooit relevant, dus worden ze in
deze implementatie ookniet toegevoegd: de IsDots methode geeft
altijd false terug.
Wanneer een bestand matcht, wordt het pad geëxpandeerd naar een
absoluut pad met dehulp van de systeemfunctie realpath(3).
GetCreationTime zorgt ook voor problemen, omdat creation time op
Linux niet bestaat. Inplaats daarvan wordt in deze implementatie
daarom de modification time teruggegeven.
8.2.3 Unicode BOM
Om aan te geven welke encodering en endianness een Unicode
bestand of stream heeft, kanvooraan een specifieke reeks bytes
toegevoegd worden: een Byte Order Mark (BOM). VoorUTF-16 is dit
0xFEFF. UTF-8 heeft ook een BOM, maar het gebruik daarvan wordt
afgeraden.[Unicode Consortium 2006]
Gezien we bestanden in deze Linux versie in UTF-8 zullen opslaan
kunnen we de BOM codegerust uitschakelen:
#ifndef __linux__ //UTF -16 BOM , for linux we'll use UTF -8
without BOMelse{
if (SetFilePointer(m_fileDebug ,