Event Handling in C#, Python and C | Example Explained

YouTube CSEstack

Events handling is the most important “code construction” enabling the individual parts/modules of a program (application) to communicate among themselves.

Do we need such communication? 

Absolutely!

Open any application on your PC and what you find there are a lot of “gadgets” (graphical components), like buttons, text boxes, radio buttons, etc.

Now, when you go with mouse over, or click on any of them, the application is immediately responding to your action.

What is actually happening is, that with your mouse “action” you “triggered” an event, which invoked a certain method of an object, to which that component is tied. And that method launches a certain function/method belonging to another object – listener, which has subscribed to this callback function for such an event.

The object, which is launching out those callback functions is usually called the publisher

Is such an inter-module communication important only for the PC application?

Of course that not!

Embedded systems relay on such communication on 100%. So, does C provide such event-based communication?

No, C doesn’t provide directly any such tools. But it doesn’t mean that you can’t write an event-driven application in C.

You can, but you would need to understand the hardware aspect of the embedded system and its CPU much deeper, as you would need to use hardware and software interrupts for such a purpose.

C# and Python make such a design (well, limited to PC applications) much easier.

So, let’s look at how those three programming languages (C#, Python, and C) can be used for writing such event-driven applications.

Event Handling in C# | Event-driven Windows Forms App

 C# is one of the most elaborated programming languages, especially useful for the event-driven applications running under MS Windows on PCs.

C# has huge support from the .Net framework’s enormous library, so no doubt that such applications can be written very efficiently.

Let’s look at a simple application written in C# using the MS Visual Studio.

If you are familiar with C# programming, the following text will help you to understand the entire problem.         

In MS Visual Studio C# has been open a new project, C# Windows Forms APP with the nameSimpleEvenForms. Immediately the Form1.cs source file (with theForm1 class inside) and the Form1.cs[Design] the platform has been created.

The Form1.cs file has been renamed to more appropriate, Publisher.cs. file. Upon selection of the Publisher.cs[Design] the Form1window name has been changed to Publisher.

Three graphical components (official terminology is .NET Components) were added to this form. They were “dragged” from Toolbox. Those components are two buttons and one TextBox.

The first button has been renamed to buttonListener, and the second one to buttonSend.

As their names suggest, the first button will be used to create new listeners, i.e. forms, which will be receiving messages (actually, their registered methods would be launched) whenever an event to which they have been registered, occurs.

The second button will be used to generate the event, which will launch the event-handling method (of the publisher), which in turn will launch the registered callback function(with or without the argument of the appropriate listener(s).

The argument in our case will be a text string, which the user writes into TextBox.

After double-clicking at those two buttons (while in the Publisher.cs[Design]window), the corresponding event handlers (methods) are automatically generated in the Publisher.cs source file.

Before finalizing all the code, a new form/source file needs to be added to the project. Its name is Listener.cs and at its Listener.cs[Design] file, just one TextBoxgadget has been added (dragged) from Toolbox, renamed to textReceived.

Now, as we have all the needed resources in their place, the application coding can be finalized.

Here is the entire content of the Publisher.cs file.

Most lines of the code have been automatically generated by VS, so we really need only to finalize them. The using directives are not shown, they are automatically generated by Visual Studio:

namespaceSimpleEvenForms
{
  //EventHandler<TEventArgs> is a standard (.NET) delegate. 
  //Doesn't need to be defined:
  //public delegate void EventHandler<TEventArgs>
  //(object sender, TEventArgs e);

  publicpartialclassPublisher : Form
  {
    //Define empty Eventhandler-typeobject for callback functions 
    publiceventEventHandler<MyEventArgs>OnMyEvent;          

    publicPublisher()
    {
      IsMdiContainer = true; //All created forms will be "children"
      InitializeComponent();
    }

    privatevoidButtonListener_Click(object sender, EventArgs e)
    {
      Listener listForm = new Listener(this);
      listForm.Show();
      listForm.MdiParent = this;  //Make it child form
    }

    privatevoidButtonSend_Click(object sender, EventArgs e)
    {
      stringvar = textForListener.Text;
      //create event argument object
      MyEventArgsmyArg = newMyEventArgs(var);
      //Invoke all callback functions with sender and 
      //MyEventArgs as arguments
      OnMyEvent?.Invoke(this, myArg);
    }
  }


  //Event argument class for standard EventHandler<TEventArgs>
  // delegate has to be derived from a standard, EventArgs class 

  publicclassMyEventArgs : EventArgs
  {
    publicMyEventArgs(string txt)
    {
      Text = txt;
    }
    publicstring Text { get; set; }
  }
}

With the most important statement:

publiceventEventHandler<MyEventArgs>OnMyEvent; 

we have defined OnMyEvent to be an EventHandlerclass-type delegate object, which will carry a list of the callback functions (event handlers), which individual listener objects will register when they are created.

Those callback functions will be getting two arguments, a reference to the sender, object sender, and the object e of the class TEventArgs. This argument class is derived from the generic, EventArgsclass, which otherwise can’t take any argument. As EventHandler<TEventArgs> is a standard (.NET) delegate, it doesn’t need to be defined.

As we want to have all the Listener forms appear inside the Publisher form, we need to make them children. This requires to use the

IsMdiContainer = true; 

statement in the Publisher() constructor. Mdi stays for Multiple Document Interface.

The InitializeComponent() method is a standard method, which each form-class constructor has to call.

The next is the ButtonListener_Click() method, which has been automatically created (its frame) when we double clicked at the buttonListener after its creation.

By clicking at the buttonListener button we want to create another Listener object/form. We want to show (display) it and we want it to be a child of the Publisher form. Those three statements will do so.

The ButtonListener_Click() method itself is an event-handling method, as it is indicated by its arguments. This event handler method has been registered for thebuttonListener.Click events by VS. A corresponding code was automatically generated in the second part of the partialclassPublisherform, which you can find in the automatically generated Publisher.Designer.cs source file. Even though it gets two arguments, they have no practical use.

So, the ButtonListener_Click() event handler will instantiate a new, Listener, class object to which the parent object (of the Publisher class) is provided as the argument this.

The following, ButtonSend_Click(), event handler method will be invoked when the user clicks the Send button. It is this method, which will be launching callback functions(with an argument) of the registered clients/listeners.

In this application, those arguments will contain a text, which the user writes into the textForListener text box.

So the first action what this method has to do, is to assign the reference to the text string, which the user previously placed in the textForListener text box, to the local variable, var.

The next step is to instantiate an object of the MyEventArgs class, myArg, with var as the argument. And finally, all the callback functions currently registered at OnMyEvent are invoked.

The last part of the Publisher.cs source file is the MyEventArgsclass definition. This is an event argument class replacing a generic, TEventArgs class. This class has to be derived from (i.e. has to inherit) the standard, EventArgs class.

It has just one property, a string-type Text.

When an object of this class is instantiated inside the publisher’s, ButtonSend_Click method, a text string (which is a content of the textForListener text box) will be assigned to the Textproperty.

When the receiver (registered callback function) gets this object, it can easily extract its (only) property and display it in its own text box.

Now let’s look at the Listener class code in the Listener.cs source file: 

namespaceSimpleEvenForms
{
  publicpartialclassListener : Form
  {
    Publisher myPub;

    publicListener(Publisher pub)
    {
      myPub = pub;
      myPub.OnMyEvent += this.CallBack;
      InitializeComponent();
    }

    //custom callback function
    publicvoidCallBack(object sender, MyEventArgs e)
    {
      textReceived.AppendText(e.Text + "\n");
    }

    privatevoidListener_FormClosing(object sender, FormClosingEventArgs e)
    {
      myPub.OnMyEvent -= CallBack;
    }
  }
}

Its first field is a local variable, myPub (my publisher) definition, to which will be assigned the reference to the actual publisher object, pub, inside the Listener() constructor which follows.

The second statement in the Listener() constructor is the crucial one. With this statement, the listener’s callback function, CallBack, is added (registered) to the publisher’s event delegate object, OnMyEvent.

The last statement is the InitializeComponent() method call, which is a standard method which each form/class constructor has to call to initialize all form/class properties.

Then follows the callback function definition. It simply displays the Text string, which it obtains via the e object – argument, which is an instance of the MyEventArgs class. 

The last method is interesting and very important. Its purpose is to de-register the callback function from the event delegate object if the current instance of the (Listener) class cease to exist.

If the publisher continues to call a callback function of an already non-existing object, the program will crush! That’s why this event-handling method is very important. And it is an event handler, which is invoked when the user closes the listener object form (by clicking at its [X]).

We haven’t touched the Program.cs source file, which has been automatically generated when we created our application.

There is a very simple body of the Main() function, which at first sets some system parameters and finally it instantiates a new Publisher (originally Form1) object and starts running a forever loop.

This loop will end (together with the entire program) when the user closes the publisher’s form.

namespaceSimpleEvenForms
{
  staticclassProgram
  {
    ///<summary>
    /// The main entry point for the application.
    ///</summary>
    [STAThread]
    staticvoid Main()
    {
      Application.EnableVisualStyles();
      Application.SetCompatibleTextRenderingDefault(false);
      Application.Run(new Publisher());
    }
  }
}

Finally, here is how the application appears when launched.

You can close any Listener and to send the (same, or different) text to the remaining listeners via their callback functions. 

Event-driven Windows Forms App in C#

Event-driven Application written in Python

The following Python application will try to imitate the previous, in C# written event handling application.

As this web-site is devoted predominantly to the Python programming, the readers, assuming the Python programmers themselves, shouldn’t have any difficulties to understand the following code.

Just keep in mind, the application is written in the Python v. 2. 7., so certain modifications will be needed if run in the Python 3 version.

Even though I wrote the following program on a PC under the MS Windows, the Python environment doesn’t use the .Net framework as a supporting library, instead, the Tkinter library has to be used for all GUI components.

You can also use the PyQt library for GUI. But in this example, we are using the Tkinter library.

For simplification, the entire application resides in a single source file, but you may split it into 2 or 3 parts, similarly like the C# application has been done. So, let’s start with the code explanation.

Code Explanation:

At first, we need to install the Tkinter library using the pip tool. And then import all the needed graphical components from the Tkinter library.  

from Tkinter import *

We can create similar classes, which we found in the C# application above. Not that we can’t use a different approach, but really, the C# applications are “master pieces”, at least regarding the event handling, so our Python programs can’t lose anything by following the same approach. Let’s start with the EventArgs class:

class EventArgs:
  def __init__(self, text):
    self.Text = text

It is a straight forward code, no explanation required. Just keep in mind, that sometimes a more generic EventArgsclass definition, like the following one, can be more desirable. Such a way the messages – arguments can provide more information items, all packed into one tuple:

class EventArgs:
  def __init__(self, *args):
    self.args = args

Let’s continue with the Publisher class definition.

Before we build Publisher as a window with few gadgets, we need to define similar objects (like its C# lookalike application) needed for the clients/listeners registration.

As Python doesn’t provide any dedicated event handling delegate class, we can easily replace it with a List, or even with a Dictionary (preferable), which will keep the callback methods registered by the clients.

With the dictionary, we can register not only the callback functions but their ownersIds as well. Then we can selectively launch the callback functions of the individual clients.

With a simple list of the callback functions, this is not possible. The rest of the __init__() code is devoted to the graphical design, for the explanation please look at the Tkinter documentation.

class Publisher:
  def __init__(self, parent):
  self.__callback = {} #Dictionary of the Listeners' callback functions
  self.Id = 0 #Id of the Listener (part of the registration)

  frame = Frame(parent)
  frame.pack(side='top')

  font = 'times 18 bold'
  title = Label(frame, text='I am Publisher!', font=font)
  title.pack(side='top', pady=20)

  lButton = Button(frame, text='Create Listener', command=self.new_listener)
  lButton.pack(side='top')

  self.s = StringVar() 
  self.s.set('Hello')    
  entry = Entry(frame, width=12, textvariable=self.s)
  entry.pack(side='top', pady=20)

  sButton = Button(frame, text='Send Message out', command=self.send_message)
  sButton.pack(side='top')

The following,new_listener(), method is associated with the lButton object. Its purpose is to create a new listener.

Again, look at the Tkinter documentation for better understanding how a new, listener window is being created.

From the event handling point of view is important the registration statement, in which the listener’s callback method, show_message, which of course will have to be defined in the Listener class, is being registered at the Publisher.

So, this way of the callback function registration is a little bit different from the previous (C#) example. Here, it is the Publisher, who is registering (and deregistering as well) callback functions – methods.

Both approaches are equivalent in a case like designing GUI.

defnew_listener(self):
  #create base child window "winl"
  winl = Toplevel()
  #this will be the child window Id  
  self.Id += 1            
  winl.title('Listener '+str(self.Id))

  #create new Listener object/window with Id
  lis = Listener(winl, self.Id) 

  #register callback function with Id as the key
  self.__callback[lis.Id] = lis.show_message

  #define "on_closing" function, 
  #i.e. action when thechild's window X is pressed 
  defon_closing():       
    if lis.Id in self.__callback:
      #at first remove callback function
      del(self.__callback[lis.Id]) 
      #and finally destroy child window

winl.destroy()
#winl closing action 		
winl.protocol("WM_DELETE_WINDOW", on_closing)

The following,send_message(), method is associated with the sButton object, which will launch the callback functions of the registered clients.

It retrieves the text string from the text box (a default one, or entered by the user), creates the e object and finally it calls the callback functions (one after the other) registered in the __callback dictionary, with e as the argument.

defsend_message(self):
  #get the message/text
  text = self.s.get() 

  #create the event object with the "text" argument
  e = EventArgs(text) 
  for k in self.__callback:
    self.__callback[k](e)

While the above shown method broadcasts the same text string to each registered client, the following, event(), method can send a different text message to individual client.

This is possible because clients were able to register not only their callback functions but their Ids as well.

That’s why Publisher uses the __callback dictionary, instead of a simpler list, as we saw it in the C# example. Well, the same dictionary we could use in our C# application as well.

def event(self, n):
  text = "Message for #" + str(n)

  #create the event object with the "text" argument
  e = EventArgs(text)

  for k in self.__callback:
    #selective call the callback function with argument
    if (k == n):        
      self.__callback[k](e)

The following code belongs to the Listener class. This class is derived from a parent, which is a child window, winl, created in the publisher.

And it gets the window/listener Id from the publisher as well. It has just one “gadget”, the TextBox text box, where its callback function will be printing its text argument, whenever it is invoked by the publisher. 

class Listener:
  def __init__(self, parent, Id):
  self.Id = Id
  frame = Frame(parent)
  frame.pack(side='top')

  font = 'times 18 bold'
  title = Label(frame, text='I am Listener '+str(Id), font=font)
  title.pack(side='top', pady=20)

self.S = Scrollbar(frame)
self.TextBox = Text(frame, height=10, width=20)
self.S.config(command=self.TextBox.yview)
self.TextBox.config(yscrollcommand=self.S.set)        
self.TextBox.pack(side=LEFT)
self.S.pack(side=RIGHT, fill=Y)

Here is the listener’s callback function

#callback function with EventArgs argument
defshow_message(self, e):  
  text = e.Text#extract the message/text
  self.TextBox.insert(END, e.Text+'\n')

The last class in the application is named Program, for the consistency with the naming convention used by the MS Visual Studio and the C# programming.

That’s the author’s preference, you are free to use any names as you wish.

At first, the__init__(), the method creates the root window for the publisher object and then creates the pub object.

The task() method is a special method, which runs periodically every 1000 msec. It is based on a special, root.after(), method belonging to Tkinter. Its purpose is to generate periodical events, which launch the pub.event() method, which in response calls the individual listener callback functions.

The last, __call__(), the method will enable to call the Program object as a function, as you can see in the last statement of this program. Its purpose is to launch the periodical task, task(), and to remain running in a for-ever loop.

class Program:
  def __init__(self):
    self.count = 1
    #create base root window
    self.root = Tk()    
    self.root.title('Publisher-Listener communication')
    # W x H + Xoffset + Yoffset
    self.root.geometry("400x300+400+200") 
    #create "Publisher" object from base root window
    self.pub = Publisher(self.root)  

    def task(self):
      self.pub.event(self.count)
      self.count += 1
      if self.count>self.pub.Id:
        self.count = 1
        #reschedule event in 1 second intervals
        self.root.after(1000, self.task)

    #main() function definition
    def __call__(self):    	
      self.task() #launch periodical task
      self.root.mainloop() #remain in the forever loop


if __name__ == '__main__':
  main = Program()
  main() #call main() function

When you run the application in your Python interpreter, you will see something like shown below.

Here I launched four listeners, and the screen shot shows a situation after several seconds.

The listeners are showing received text strings via their callback functions from the periodical event “generator”. By clicking the “Send Message out” publisher’s button, each listener would receive the “Hello” text string as well.

Btw. in the code discussed above the “Goodbye” button has been removed for simplicity, as it has no relations with the event-handling process.

Event-driven Application written in Python using Tkinter

Event-driven Application Written in C Language

While two previous applications written in C# and Python are very alike, as they are some kind of GUI applications, what we are going to see below is a completely different application, as its purpose is very different.

Imagine you are writing firmware for an embedded system, which can’t be supported by any real-time operating systems (like VxWorks).

Then you have to design absolutely everything.

Imagine a group of designers working on such a project. One is dealing with the signals sampling/processing, another designer is responsible for communication with other systems, etc.

There will have to be one designer, who will be responsible for the coordination of all actions, calculations, and processing (let’s call them tasks) required by the system.

Many of those tasks will be triggered by some external interrupts, or signals on specific GPIO input pins, many of them will be triggered by the internal interrupts (e.g. timer expiration interrupts) and many tasks will be demanded by other tasks (via S/W interrupts for example). So, what this designer will very likely do as the first step, he/she will ask all other designers to provide a list of their tasks and events, which ought to trigger those tasks.

Then he will try to assign those tasks to the individual interrupts (as the interrupt routines), for other tasks he will design some kind of a task scheduler, and so on.

Fine, eventually everything will work. But later, some F/W upgrades will require certain tasks rescheduling. But the designer who designed this meantime left the company and now no one understands his design.

Well, this situation could be avoided, if the entire task scheduler was written as a flexible, event-driven system, where the new tasks could be added, renamed, deleted as is needed. So, the following C-written code is going to implement exactly this idea.  

Our event-driven system will follow rather the above shown Python program, i.e. the callback functions will be stored together with another attribute, in our case the event number, in a suitable, Dict, Dictionary.

The callback functions will be receiving arguments in the form of an EvArg structure. In addition, we will allow having more than one listener’s callback function registered to the same event, which eventually will have to be “collected” into a suitable list, that’s why we need to have the ListList created.

While Python and C# have all such Collections classes predefined, in C we need to create everything from scratch. You should be familiar with the C-C++ code.

You can find more information about such “collection” types, like List in different parts of this web-site, so let’s skip the explanation of the following two source files, Dictionary.handDictionary.c.

Dictionary.h

#ifndef DICT_H
#defineDICT_H

typedefstructEvArg;
typedefstructList; 
typedefstructDict;
typedefvoid (*Action) (EvArg);

structEvArg
{
  char *text;
  int event;
};

structList
{
  Action act;
  List* nNode;
};

structDict // Dict type definition 
{
  int key;
  Action act;
  Dict *nNode;
};

// Dictionary related functions prototypes
List* ConstrList(Action);
voidDestrList(List*);
List* AppListNode(List*, Action);

Dict* ConstrDict(int, Action);
voidDestrDict(Dict*);
Dict* AppendNode(Dict*, int, Action);
Dict* RemoveNode(Dict*, int, Action);
List* RetDictVal(Dict* myDict, intk);

#endif

Dictionary.c

#include<iostream>
#include<stdlib.h>
#include"Dictionary.h"

List* ConstrList(Actionact)
{
  List* Obj = (List*)malloc(sizeof(List));
  if (Obj)
  {
    Obj->act = act;
    Obj->nNode = NULL;
    returnObj;
  }
  returnNULL;
}

voidDestrList(List* myList)
{
  List* curr = myList;
  List* next;
  while (curr)
  {
    next = curr->nNode;
    free(curr);
    curr = next;
  };
}

List* AppListNode(List* myList, Actionact)
{
  List* temp = myList;
  List* newNode = ConstrList(act);
  if (newNode)
  {
    while (temp->nNode != NULL)	// find the last node in link
      temp = temp->nNode;
    temp->nNode = newNode;  // append new node
  }
  returnmyList;
}

Dict* ConstrDict(intk, Actionact)
{
  Dict* Obj = (Dict*)malloc(sizeof(Dict));
  if (Obj)
  {
    Obj->key = k;
    Obj->act = act;
    Obj->nNode = NULL;
    returnObj;
  }
  returnNULL;
}

voidDestrDict(Dict* myDict)
{
  Dict* curr = myDict;
  Dict* next;
  while (curr)
  {
    next = curr->nNode;
    free(curr);
    curr = next;
  };
}

Dict* AppendNode(Dict* myDict, intk, Actionact)
{
  Dict* temp = myDict;
  Dict* newNode = ConstrDict(k, act);
  if (newNode)
  {
    while (temp->nNode != NULL)	// find the last node in link
      temp = temp->nNode;
      temp->nNode = newNode; // append new node
  }
  returnmyDict;
}

Dict* RemoveNode(Dict* myDict, intk, Actionact)
{
  Dict* curD = myDict;
  Dict* prevD = NULL;
  while (true)
  {
    if (curD->key == k&amp;&amp;curD->act == act)
    {
      if (prevD) // removal other than first Dict
      {
        prevD->nNode = curD->nNode;
        free(curD);
        break;
      }
      else  // removal of the first Dict
      {
        myDict = curD->nNode;
        free(curD);
        break;
      }
    }
    prevD = curD;
    curD = curD->nNode;
    if (curD == NULL)	// key value not found
      break;
    }
  returnmyDict;
}

List* RetDictVal(Dict* myDict, intk)
{
  List* tempL = NULL;
  Dict* tempD = myDict;
  while (tempD != NULL)
  {
    if (tempD->key == k)
    {
      if (tempL == NULL)
        tempL = ConstrList(tempD->act);
      else
        tempL = AppListNode(tempL, tempD->act);
    }
    tempD = tempD->nNode;
  }
  returntempL;
}

The following two source files are based on the Dictionary.handDictionary.c files and they keep entire code needed for the publisher and listener “object” creation and usage.

In the EventHandling.h file are all required type definitions and the functions prototypes associated with the Publisher construction/destruction, the Listener construction/destruction, registration/deregistration of the Listener and the Publisher, and finally, a function, which will analyze an event register and based on which event is set, the appropriate listener’s callback function will be launched.

#ifndef EVENT_HANDLING
#defineEVENT_HANDLING

#include"Dictionary.h"

typedefstruct	// This is Publisher type definition
{
  char name[10];
  Dict *actDict;
} Publisher;

typedefstruct	// This is Listener type definition
{
  char name[10];
  inteventId;
  ActionMyAct;
} Listener;

Publisher* ConstrPub(char[]);
voidDestrPub(Publisher*);

Listener* ConstrLis(char[], int, Action);
voidDestrLis(Listener*);

void Register(Publisher * , Listener *);
void DeRegister(Publisher *, Listener *);
void EventsEnquiry(Publisher *, int);

#endif

The following is the EventHandling.c implementation source file, where all previously listed functions are implemented.

The code is pretty straight forward, except for the last function, EventsEnquiry(), which might require some explanation.

The idea is to provide the content of an event register (for example one 16-bit GPIO register), where the events can be found as set flag bits. The EventsEnquiry() will “traverse” throughout this register, bit after bit, starting with the lowest bit.

If it finds the bit at a specific position set, it means that the event occurred. Then the function at first prepares the argument to be passed to any callback function registered for this event.

In the following step, it retrieves all the callback functions from the publisher’s dictionary of the callback functions into a local list of such callback functions.

In the next step, one function after the other is retrieved from this list and launched (with the previously prepared argument).

Finally, that list of functions is destroyed (remember, even if it was originally defined as a local variable, as it was filled with the items, the memory from the heap was dynamically assigned to it).   

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include"EventHandling.h"

void Dummy(EvArge)
{	
}

Publisher* ConstrPub(charname[])
{
  Publisher* objPub = (Publisher*)malloc(sizeof(Publisher));
  if (objPub)
  {
    strcpy_s(objPub->name, name);
    objPub->actDict = ConstrDict(0, Dummy);
    returnobjPub;
  }
  returnNULL;
}

void DestrPub(Publisher* obj)
{
  if (obj != NULL)
  {
    DestrDict(obj->actDict);
    free(obj);
  }
}

Listener* ConstrLis(charname[], intid, ActionAct)
{
  Listener* objLis = (Listener*)malloc(sizeof(Listener));
  if (objLis)
  {
    strcpy_s(objLis->name, name);
    objLis->eventId = id;
    objLis->MyAct = Act;
    returnobjLis;
  }
  returnNULL;
}

voidDestrLis(Listener* obj)
{
  if (obj != NULL)
  free(obj);
}

void Register(Publisher* pub, Listener* lis)
{
  pub->actDict = AppendNode(pub->actDict, lis->eventId, lis->MyAct);
}

voidDeRegister(Publisher* pub, Listener* lis)
{
  pub->actDict = RemoveNode(pub->actDict, lis->eventId, lis->MyAct);
}

void EventsEnquiry(Publisher *pub, inteventReg)
{
  int event;
  for (event = 1; event <= 0x80; event <<= 1)
  {
    if (event &amp;eventReg)
    {
      Action act;
      EvArgarg = { pub->name, event };
      List* tempL = RetDictVal(pub->actDict, event);
      List* copyL = tempL;
      while (tempL != NULL)
      {
        act = tempL->act;
        act(arg);
        tempL = tempL->nNode;
      }
      DestrList(copyL);
    }
  }
}

The last source file is Program.c. Here all previously defined data types and the associated functions can be finally used. Please notice, no global variables are used, though in real embedded systems you would need to use, if nothing else, some H/W registers of a processor/microcontroller, where the events will be gathered.

Before we create any new publisher or listener object, we need to prepare all the required information. Actually, we need to define only the callback functions (Actions), which individual listeners are going to use when their events occur.

In the main() function we have defined (simulated) one such event-gathering register, where we set the lowest 8 bits. Then we defined and have created the publisher (just one) object and several listeners objects.

The listeners registered their callback functions at the publisher, including the event number.

The event number actually represents the bit position in the event-gathering register. The lowest bit position is referred to as 1.

You can use only numbers having just one-bit set, i.e. 1, 2, 4, 8, 0x10, 0x20, etc. And notice that more than one listener can be registered (its callback function) for one event.

In our example, lis1 and lis5 are registered for the event 1.  

#include<stdio.h>
#include<stdlib.h>
#include"EventHandling.h"


void Act1(EvArge)
{
  printf("Action 1 launched by: %s, event: %d\n", e.text, e.event);
}

void Act2(EvArge)
{
  printf("Action 2 launched by: %s, event: %d\n", e.text, e.event);
}

void Act3(EvArge)
{
  printf("Action 3 launched by: %s, event: %d\n", e.text, e.event);
}

void Act4(EvArge)
{
  printf("Action 4 launched by: %s, event: %d\n", e.text, e.event);
}

void Act5(EvArge)
{
  printf("Action 5 launched by: %s, event: %d\n", e.text, e.event);
}


int main()
{
  shortinteventReg = 0xFF;
	
  Publisher *pub1 = ConstrPub((char *)"PUB 1");
  Listener* lis1 = ConstrLis((char*)"List1", 0x01, Act1);
  Register(pub1, lis1);
  Listener* lis2 = ConstrLis((char*)"List2", 0x02, Act2);
  Register(pub1, lis2);
  Listener* lis3 = ConstrLis((char*)"List3", 0x20, Act3);
  Register(pub1, lis3);
  Listener* lis4 = ConstrLis((char*)"List4", 0x04, Act4);
  Register(pub1, lis4);
  Listener* lis5 = ConstrLis((char*)"List5", 0x01, Act5);
  Register(pub1, lis5);

  printf("When Lis1, Lis2, Lis3, Lis4 and Lis5 registered at Pub1:\n");
  EventsEnquiry(pub1, eventReg);
  DeRegister(pub1, lis1);
  printf("\nAfter Lis1 de-registration:\n");
  EventsEnquiry(pub1, eventReg);
  Register(pub1, lis1);
  printf("\nAfter Lis1 registration:\n");
  EventsEnquiry(pub1, eventReg);


  DestrPub(pub1);
  DestrLis(lis1);
  DestrLis(lis2);
  DestrLis(lis3);
  DestrLis(lis4);
  DestrLis(lis5);

  return 0;
}

Finally, here is a screenshot showing the situation after launching this application.

In a real system, this application would have a forever loop running as the last action in the main() function.

Inside this loop, the EventsEnquiry() function will be running and periodically checking the event register.

Well, once the flag has been detected and the callback function launched, we need to use some mechanism not to launch the same function again and again, only when the same flag is being set again after the previous clearing. So, the event register will have to detect only a change from the clear to set states of the individual bits. But it’s easy to implement.

In the forever loop, we might have running more EventsEnquiry() functions, each one checking a different event register and launching the callback functions of different listeners, registered at different publishers. It is even possible dynamically to create, register and deregister (and then even to destroy) any listener.

Well, this system, which is a nice example of an event-driven application, can be really used as a simple (though very flexible) task scheduler. Especially, if it is combined with the additional, interrupt-driven system of routines, which can pre-empt any lower priority procedures.

Microsoft Visual console for Event-driven Application written in C language

In this tutorial, I have described every little thing and practical event-driven applications for learning Event Handling in C#, Python and C. If you have any questions, let me know by commenting below.

Leave a Reply