| D | E | L | P | H | I | R | O programmeren linux gitaar foto's email |
| sdl sql delphi componenten cpp c |
C code gebruiken
Je kunt over het algemeen prima C code in C++ code mengen. Zo zal de functie
printf uit <stdlib.h> prima werken onder de C of C++ compiler.
Als je echter niet-standaard C code (die niet door het systeem geleverd wordt)
in een C++ programma wilt gebruiken en in de c header files is geen
rekening gehouden met C++ (zoals hier aangegeven) dan
zul je de volgende code om de header toe moeten voegen; |
Callback
Wat is een callback?Dit laat zich het beste uitleggen met een praktijk voorbeeld. Stel je werkt in een groot gebouw met veel collega's en je kent ze niet allemaal bij de naam. Vandaag moet je wat doorgeven aan je collega dus loop je door het gebouw naar zijn werkplek en je laat er een briefje achter met de door te geven tekst en met vriendelijke groet, Hans. Je collega komt het briefje tegen, leest het en denkt 'goed dat Hans dat doorgaf.. Hij weet niet welke Hans want er werken er veel meer in het bedrijf maar die informatie heeft je collega niet nodig om het bericht verder af te handelen.Nu moet diezelfde collega wat voor je doen. Je gaat weer naar zijn kamer en legt er ditmaal een briefje met de tekst 'Kun je me de resultaten even teruggeven, groeten Hans Worst (kamer 111)'. Je collega leest het, doet het werk en stuurt vervolgens het resultaat naar Hans Worst op kamer 111 want dat was de afzender die het resultaat wil weten. Jij komt weer terug, leest het resultaat en gaat ermee aan de slag. Dit is nu een callback. De afzender laat zijn adres achter dat gebruikt kan worden om het resultaat aan af te leveren. NB. Het had ook zodanig kunnen zijn dat je het adres van een ander had achtergelaten zodat je collega de resultaten naar die persoon had gestuurd. Je kunt je voorstellen dat dit in de programmeerwereld helemaal geen ongebruikelijke situatie is. Je stuurt gegevens naar een functie, deze functie bewerkt de gegevens en stuurt ze vervolgens terug naar het adres dat de aanroepende functie heeft achtergelaten (hetzij zichzelf, hetzij een andere functie). Een plek waar callbacks aan de orde van de dag zijn, zijn grafische user interfaces (GUI's). De gemiddelde GUI heeft veel knoppen, listboxes, radiobuttons etc. etc. en als je op knop A drukt moet listbox B een gegeven verwijderen waardoor de radiobutton C weer verspringt.. afijn je snapt het wel.. een wirwar van afhankelijkheden waarvoor je zogenaamde events (bv. de klik op een knop) en eventhandlers (bv. wat moet ik doen als er op de knop gedrukt is) moet schrijven die vrijwel altijd gebaseerd zijn op callback functionaliteit. Callback in CCallbacks zijn in C vrij eenvoudig te realiseren. Hier een (vrij nutteloos) voorbeeld waarin gedemonstreerd wordt hoe simpel de implementatie in C kan zijn:
01 #include <stdlib.h>Dit voorbeeld is nu niet echt een nuttig programma (we hadden ook gewoon PrintInt(2) en PrintInMaalTwee(2) kunnen zeggen) maar
het gaat hier even om het principe. Ik laat het aan jezelf over om in C iets
nuttigs met callback te realiseren.We hebben hier twee functies PrintInt en PrintIntMaalTwee, beide
leveren geen resultaat (void) en krijgen als argument één integer (int i). Om nu een callback
te maken naar één van deze functies definiëren we in regel 03 een type
MyCallBack die hetzelfde opgebouwd is als de voornoemde functies (dus void en als
argument één integer).In regel 17 definiëren we een callback functie waar nog geen waarde (NULL) aan toe is gekend. In regel 18 vertellen we dat Callback gelijk is aan de functie
PrintInt. Het resultaat van de aanroep in regel 19 is dan ook gewoon 2. In regel
20 wijst de functie Callback naar de functie PrintIntMaalTwee waardoor
in regel 21 het resultaat 4 is.Resumerend, in C definieer je een type dat overeenkomt met de opbouw van de functie (dus het juiste type voor het resultaat en het juiste aantal parameters) waaraan je vervolgens elke willekeurige functie kunt hangen (mits zelfde resultaat en parameters). Uiteraard kan het in C geavanceerder maar ik ga nu verder in op het gebruik in C++. Dat is hele andere koek!. Callback in C++In C++ wordt het veel lastiger omdat er met objecten wordt gewerkt. Een callback is dan van een zodanige aard dat het een verwijzing is naar een functie van een object. Het is heel belangrijk om je dit te realiseren. In C++ is alles aan objecten gekoppeld (tenminste als je ook op Object georiënteerde wijze programmeert, hetgeen wel de bedoeling is!). Je moet een callback zo flexibel maken dat het niet uitmaakt welk object de functie oproept.Het praktijkgevalWe gaan uit van een praktijkvoorbeeld waarmee ik geconfronteerd werd en welke mij tot het schrijven van deze howto deed overgaan. Ik heb 2 classes cWindow en cAnimation. De eerste, cWindow, is een class die een window maakt waarin een spel gespeeld kan worden. De tweede, cAnimation, is een class waarin een animatie op de window uitgevoerd wordt (bv. een explosie of een bewegend figuurtje). De code beperkt zich tot de voor deze howto essentiële regels.Het probleem waarmee ik geconfronteerd werd was dat mijn class cAnimation aan cWindow door moest geven dat de animatie beëndigd was. Hiertoe wilde ik aan de class cAnimation een functie uit cWindow meegeven die aangeroepen moest worden als de animatie beëndigd was. Laten we eerst mijn meest voor de hand liggende (maar helaas foutieve) code bekijken.
01 class cWindowHet idee hierachter is dat ik in regel 13 een callback definieer die in regel 18 aan de juiste functie wordt gehangen. Zodra de animatie stopt wordt deze functie uitgevoerd... helaas... De compiler zal bij het compileren klagen over het feit dat de functie HandleAnimationEnd onderdeel van een object is en geen functie. De compiler heeft dus herkend dat de functie onderdeel is van een object en geen losstaande functie. Dit zal dus niet werken. Nu kunnen we de volgende noodgreep toepassen waarbij we aan de compiler doorgeven waar de functie gezocht moet worden;
01 class cWindow;Dit ziet er beter uit; we doen een voorwaartse declaratie van cWindow in regel 01 zodat de compiler niet over regel 06 struikelt. Nu is in regel 6 duidelijk aangegeven dat de aan te roepen functie onderdeel is van het object cWindow. In regel 09 roepen we de functie aan en het werkt! Ja het werkt maar het is fout en wel om de volgende redenen; We hebben nu het object cAnimatie afhankelijk gemaakt van het object cWindow. Stel nu dat ik de callback ook vanuit een willekeurig ander object aan zou willen roepen ik noem maar een class cMenu waarin ook een animatie zit. Dan zou ik daar weer in cAnimatie een aparte callback structuur voor moeten maken en bovendien wordt het object cAnimatie dan afhankelijk van cWindow en cMenu. Niet echt ideaal, we willen een callback structuur die onafhankelijk is van het object dat de functie of variabele (ook wel member genoemd) in zich draagt. Leuk dat we dat willen maar dat brengt wel een probleem met zich me want onafhankelijkheid is leuk maar we moeten wel weten wat voor soort object het is omdat we de pointer naar het object naar het juiste object moeten 'casten'. We gaan de eisen omzetten in code; we willen het type van het object kwijt omdat de callback onafhankelijk van het object moet zijn dat de member in zicht heeft, dat kan als volgt:
void* Callback;We hebben toch informatie nodig over de eigenaar van de member om het type te kunnen bepalen;
void* Class;We gebruiken dit om een class te maken die als callback structuur gaat dienen:
01 class FunctorBaseIn regel 04 zie je weer de bekende structuur waarmee de defenitie van de callback functie wordt aangegeven (geen resultaat en geen parameters). In regel 05 is een standaard constructor als er geen parameters worden meegegeven en regel 06 bevat de constructor waarbij een class en een functie wordt meegegeven. In regel 08 wordt nagegaan of er een class meegegeven is (oftewel dat _Class niet gelijk is aan NULL), zo ja dan wordt in regel 09 het type bewaard en in regel 10 de ruimte voor de de callback-member gereserveerd, zo nee dan wordt enkel het adres van de callback member bewaard. In het laatste geval is er dus een gewone functie (en niet een functie van een object) meegegeven. De code kan nu als volgt aangeroepen worden: Met het meegeven van een globale functie als callback;
FunctorBase Base(0, NormalCallback, sizeof(NormalCallback);of met het meegeven van een functie van een object; FunctorBase Base(&win, cWindow::HandleAnimationEnd, sizeof(TMemberfunction));Deze class slaat onze data op en weet het adres van het object waar het om gaat en kent de functie die bij de het object hoort. Het volgende wat we nodig hebben is de functionaliteit om de void pointers weer in herkenbare (en juiste!) types om te zetten. We beperken ons voorlopig tot functies die geen resultaat opleveren (void) en één integer als parameter meekrijgen. Geen zorgen, de code om dit flexibeler te maken volgt nog. Goed we schrijven een class die een callback naar een functie kan maken die geen resultaat opleverd en één integer meekrijgt:
01 class CallbackVoidFuncInt : public FunctorBase
Volgens regel 01 erven we van de class FunctorBase, we hebben immers de variabelen en functionaliteit van deze class nodig. In regel 04 wordt het type callback funtie gespecificeerd (void en één integer parameter). De constructor in regel 05 is vrijwel gelijk aan die van de parent (FunctorBase) met uitzondering dat er nu direct het juiste type functie meegegeven wordt (die uit regel 04). De parameters uit deze constructor worden rechtstreek aan de parent doorgegeven. Met regel 06 wordt er door operator overloading (zeg maar het vervangen van het standaard gedrag van de in dit geval () operator) voor gezorgd dat de aanroep van de callback automatisch de juiste vorm krijgt.De code is nog niet compleet, we kunnen nu weliswaar een callback functie meegeven maar we kunnen nu nog niet achterhalen uit welke class deze functie komt. Deze vertaalslag volgt nog. We concentreren ons nu op het vereenvoudigen van het schrijven van de voorgaande class. We willen niet voor elke mogelijke functie samenstelling (type resultaat en meegegeven parameters) een eigen class schrijven dus gaan we dit in een template stoppen om de boel te automatiseren. We beginnen met een functie zonder resultaat en met één willekeurige (int, double, char, char* etc) parameter;
01 template <class Parameter1>Het probleem zit hem nu in de uitwerking van de operator (). Bij een normale functie (dus geen onderdeel van een object) werkt het volgende al;
01 void operator () (Parameter1 param1) const{Met de voorgaande code converteren we in regel 02 de normale functie terug van een void * naar het juiste type Parameter1. Omdat de constructor van de Functor1 class protected is kunnen we deze nooit overschrijven (m.u.v. ge&235;rfde classes) en zal er enkel een constructor geaccepteerd worden die aan de functie samenstelling voldoet.Nu komt het lastige deel want we willen dat deze operator ook werkt voor functies van members. Hiertoe moeten we de void* Class en de void* CallbackMember weer naar de juiste types omzetten. Hiertoe kunnen we niet de Functor1 class gebruiken omdat we dan weer afhankelijkheden introduceren die we niet willen hebben. We maken een class die de vertaalslag doet en baseren deze op Functor1:
01 template <class P1, class Callee, class Func>We weten met deze informatie het type van de Callee (degene die de functie aanroept) en kunnen daarmee de cast van void* Class naar het juiste type realiseren. In code wordt dit:Callee* Who = (Callee*)(Class);Nu moeten we in de Functor1 class de mogelijkheid bieden om code uit de overervende classen uit te voeren. Dit is nodig omdat we tot nu tie wel normale functies met de in Functor1 gedefinieerde operator () kunnen behandelen, maar we niet met functies van objecten overweg kunnen omdat we daar informatie over tekort komen. De afgeleide klass MemberTranslator heeft deze informatie wel en om deze uit te voeren passen we de code van de operator in Functor1 aan.
01 void operator() (P1 p1) const{In de constructor wordt nu een functie meegegeven naar UpgradeAndCall die het werk doet om de void* om te zetten in hun originele types. We moeten deze UpgradeAndCall functie dan ook in de MemberTranslator1 class definiëren en aan Functor1 meegeven:
01 MemberTranslator1(Callee &Class, const Func &MemberFunction) Dit stukje code krijgt vanuit de opgeslagen referentie (regel 02) naar de Functor1 (ftor) toegang tot de void* Class en de void* CallbackMember. De MemberTranslator weet welke types het zijn en via Functor1 kan nu UpgradeAndCall aangeroepen worden die in de MemberTranslator class uitgewerkt is. Alle functionaliteit rondom de typecasts zit in de MemberTranslator class. Let op de we in regel 02 static gebruiken om de functie globaal te maken. Dit is de enige manier om in Functor1 een pointer naar het type MemberTranslator te maken. De functie UpgradeAndCall zou anders een pointer naar een functie van een object zijn... precies het probleem wat we nu juist willen kwijtraken. Omdat de functie static is moeten we de eigenschappen van de Functor1 in regels 03 en 04 via een reference benaderen (ftor), directe toegang is er niet. meer meer meer! |