Events en de stroom van controle
Arnout van Kempen schrijft in deze rubriek over pret maken met computers. Hij gaat aan de slag met Pascal.
We hebben een frame neergezet. Drie lege procedures, een object dat overerft van TApplication en een hoofdprogramma dat Init, Run en Done aanroept. Maar hoe werkt dat eigenlijk? Wat gebeurt er als de gebruiker een toets indrukt? Hoe komt die toetsaanslag bij de juiste code terecht? Dat is het onderwerp van vandaag: de stroom van controle in een event-driven programma.
Waarom event-driven?
Je zou kunnen denken: waarom niet gewoon een while-loop, die constant checkt of er een toets is ingedrukt? Zoiets als:
While Not Klaar Do
Begin
If KeyPressed Then
Begin
K := ReadKey;
If K = 'q' Then Klaar := True;
If K = 's' Then ShowSysInfo;
End;
End;
Dat werkt, voor een simpel programmaatje. Maar zodra je een menu wilt, een dialoog met knoppen, een muisklik die een actie triggert, wordt het een warboel. Je moet zelf bijhouden waar de focus ligt, welke toetscombinaties geldig zijn, hoe je tussen verschillende vensters schakelt. En elke keer dat je een nieuwe functie toevoegt, moet je die loop aanpassen.
Event-driven architectuur lost dat op, door het probleem om te draaien. In plaats van dat jij actief vraagt "is er iets gebeurd?", zegt het systeem tegen jou "er is iets gebeurd, hier is de informatie, wat wil je ermee doen?". Je schrijft alleen de reactie op events, niet de hele infrastructuur eromheen.
Wat is een event?
In Turbo Vision is een event een datastructuur van het type TEvent. Die zit in de unit Drivers en ziet er (vereenvoudigd) zo uit:
Type
TEvent = Record
What: Word; { soort event}
Case Word Of
evKeyDown: (KeyCode: Word);
evCommand: (Command: Word);
evMouseDown, evMouseUp, evMouseMove:
(Buttons: Byte; Where: TPoint);
End;
Het What-veld vertelt je wat voor soort event het is. De belangrijkste:
- evKeyDown: een toets is ingedrukt;
- evCommand: een commando is gegeven (bijvoorbeeld via een menu);
- evMouseDown/Up/Move: muisactiviteit.
Afhankelijk van het type event zijn er extra velden beschikbaar. Bij een toetsaanslag zit de code van die toets in KeyCode. Bij een commando staat het commandonummer in Command. Bij een muisklik staan de coördinaten in Where en de staat van de knoppen in Buttons.
Die structuur is compact en flexibel. Eén type, maar met verschillende varianten afhankelijk van wat er gebeurd is. Dat heet in Pascal een variant record.
De flow: van toets naar actie
Laten we stap voor stap volgen wat er gebeurt als de gebruiker F2 indrukt in ons programma.
- Hardware interrupt. De toetsenbord-controller genereert een hardware interrupt. De BIOS interrupt handler vangt die op en zet de scancode in een buffer.
- Turbo Vision event loop. De Run-method van TApplication draait een oneindige lus. In die lus roept het GetEvent aan, een method die checkt of er input is (toetsenbord, muis, timer). Als er een toets in de buffer zit, haalt GetEvent die op.
- Event constructie. GetEvent maakt een TEvent aan. Het zet What op evKeyDown en KeyCode op de code voor F2.
- Event dispatch. Turbo Vision kijkt: is er een menu-item gekoppeld aan F2? Ja, in ons geval staat in InitMenuBar dat F2 het commando cmSysInfo triggert. Dus het event wordt omgezet: What wordt evCommand, Command wordt 100 (de waarde van cmSysInfo).
- HandleEvent. Het event wordt doorgegeven aan HandleEvent van het actieve object. In ons geval is dat TSysInfoApp. Onze HandleEvent-method krijgt het event binnen.
- Command check. HandleEvent kijkt: is What gelijk aan evCommand? Zo ja, welk commando? Als Command gelijk is aan cmSysInfo, roept het ShowSysInfo aan.
- Actie. ShowSysInfo draait. Het toont een dialoog. De gebruiker klikt OK. De dialoog sluit. Controle keert terug naar de event loop.
En zo begint de cyclus opnieuw. Wachten op het volgende event.
Virtual methods en polymorfisme
Je ziet in de flow dat "HandleEvent van het actieve object" wordt aangeroepen. Maar TApplication heeft zelf ook al een HandleEvent. Hoe weet Turbo Vision dat het onze versie moet aanroepen en niet die van TApplication?
Dat is waar het keyword Virtual om de hoek komt kijken. Als je een method Virtual maakt, zeg je tegen Pascal: "Deze method mag worden overschreven door een afgeleid object en als je de method aanroept, gebruik dan altijd de meest specifieke versie."
Dat heet overriding: het vervangen van een method uit de parent class door je eigen implementatie. En het grotere concept erachter heet polymorfisme: hetzelfde object gedraagt zich anders afhankelijk van het exacte type.
Concreet: TApplication.HandleEvent weet hoe basiszaken te verwerken (zoals Alt-X om af te sluiten). TSysInfoApp.HandleEvent roept eerst TApplication.HandleEvent aan (met TApplication.HandleEvent(Event)) om die basiszaken te laten gebeuren en voegt daar dan zijn eigen commando's aan toe.
Zonder Virtual zou Pascal altijd TApplication.HandleEvent aanroepen, ook als je een TSysInfoApp-instantie hebt. Met Virtual krijg je de juiste versie. Dat is geen Pascal-specifieke trucage. Moderne talen als C++, Rust (via traits), Python (alles is al virtual) gebruiken hetzelfde mechanisme. De terminologie verschilt wat (virtual functions, trait implementations, method overriding), maar het principe blijft: een afgeleide class kan gedrag van de parent aanpassen en het systeem roept automatisch de juiste versie aan.
Events versus polling
De kracht van dit systeem zit in de scheiding van verantwoordelijkheden. Turbo Vision regelt:
- Het ophalen van input (keyboard interrupt, muisbuffer);
- Het omzetten naar events;
- Het dispatchen naar het juiste object.
Jij regelt wat er gebeurt als een specifiek event binnenkomt. Je hoeft niet te weten hoe een toetsenbord interrupt werkt. Je hoeft niet te weten hoe de muisdriver communiceert. Je schrijft alleen: "als commando 100 binnenkomt, toon dan systeeminformatie".
Dat maakt code modulair en uitbreidbaar. Wil je een nieuw menu-item toevoegen? Voeg een commando toe, voeg een case toe in HandleEvent, klaar. De rest blijft onaangetast.
Waarom dit nog steeds relevant is
MS-DOS is weg. Turbo Pascal is museumstuk. Maar event-driven architectuur? Die zit overal. GUI frameworks in Windows, macOS, Linux, ze werken allemaal zo. Web frameworks zoals React: event-driven. Game engines: event loops overal. Embedded systemen met interrupt handlers: conceptueel hetzelfde.
De techniek is veranderd. In plaats van keyboard interrupts heb je nu message queues, thread pools, async/await. Maar de kern blijft: het systeem vertelt jou dat er iets gebeurd is en jij schrijft de reactie. Geen actief gepeupel in while-loops, maar declaratieve code: "bij dit event, doe dat".
Gerelateerd
Een echt project: SystemInformation
Arnout van Kempen schrijft in deze rubriek over pret maken met computers. Hij gaat aan de slag met Pascal.
Turbo Vision, GUI vóór Windows
Arnout van Kempen schrijft in deze rubriek over pret maken met computers. Hij gaat aan de slag met Pascal.
Een serieus project
Arnout van Kempen schrijft in deze rubriek over pret maken met computers. Hij gaat aan de slag met Pascal.
Afronding TASM: hoe procedures echt werken
Arnout van Kempen schrijft in deze rubriek over pret maken met computers. Hij gaat aan de slag met Pascal.
Verder met TASM
Arnout van Kempen schrijft in deze rubriek over pret maken met computers. Hij gaat aan de slag met Pascal.
