3D PLM Enterprise Architecture

Middleware Abstraction

The Callback Mechanism

Making objects collaborate
Technical Article

Abstract

The callback mechanism is the way CAA V5 implements publish/subscribe between objects. An object can play the role of an event publisher to warn other objects which have subscribed for that object to call them whenever a given event happens. The base class for event publishers and for event subscribers are CATBaseUnknown and CATEventSubscriber. Other types of callbacks exist for specific events, such as returning in the main loop for onidle callbacks, exceeding a given time for timeout callbacks, or modifying a file descriptor for file descriptor callbacks.


What Is the Callback Mechanism?

The callback mechanism offers the publish/subscribe capability. Let's remind what's the matter by means of a comparison taken from the daily life.

When a newspaper issues, it publishes news for all the newspaper's readers, who can be classified into those who buy the newspaper every day, and those who receive it at home because they have subscribed to it. Each time the newspaper issues, a specific team mails the newspaper to the subscribers. This team also manages the subscriber list, records new subscribers, and deletes those who revoke their subscription or who do not pay their subscription fees when the subscription period is over.

The callback mechanism is very near. An object (the newspaper company) can publish an event (the news), by means of a notification (the newspaper issue). This object requests its callback manager (the mailing team), to dispatch the notification to all the subscribers. A subscriber subscribes for a given event published by a given object, and the subscriber list is managed by the publishing object's callback manager.

Note that in the real life, an object can be a publisher for certain events, and a subscriber for others. For example, a member of the newspaper company can subscribe to other newspapers or magazines. This is also the case with CAA V5 and with your client applications.

[Top]

Making an Object an Event Publisher and/or Subscriber

There are two ways of making an object an event publisher and/or subscriber:

  1. The most simple and common way is to derive from CATBaseUnknown. Global functions allow for subscribers to subscribe and remove subscriptions, and for publishers a default callback manager implemented as an extension of the CATBaseUnknown class processes the dispatch without you need to worry about. This is recommended way for most publish/subscribe usages.
  2. Another way for advanced usages is to derive the publisher or the subscriber class, or both, from the CATEventSubscriber class. This allows, for example, an object to set a callback in place of another object if the latter derives from CATEventSubscriber, and also to override the publishing and subscribing methods to add your specific tasks.

[Top]

Deriving from CATBaseUnknown

This is the way you should use in most cases. To illustrate how to do, let's take the following example. Assume that your apartment is protected using an alarm that rings when a burglar attempts to enter when you are not there. In the daily life, the alarm notifies the burglar with the emitted sound that he should not stay here, and usually the burglar runs away. Maybe he knows how to neutralize the alarm, but this is another story.

With this scenario, we have an event publisher, the alarm, and an event subscriber, the burglar. The alarm publishes the event "I am triggered" by means of the notification: "the sound emitted". The burglar has few to do to subscribe to this event, since his ears do this for him. In our object world, instances of burglars have no ears and to make them advised burglars, we need to make them subscribe to such events if they don't want to be captured. In this case, we also need to design police objects, but this is also another story.

CAASysCallback1.gif (5612 bytes)

This is how to create such objects, and make it run together:

  1. Create the publisher: the ApartmentAlarm class. It derives from the CATBaseUnknown class and has a BeginRinging method:
    #include "CATBaseUnknown.h"
    
    class ApartmentAlarm : public CATBaseUnknown
    {
      CATDeclareClass;
      public:
        ApartmentAlarm();
        virtual ~ApartmentAlarm();
        void BeginRinging();
    };
  2. Create the published notification: the AlarmRinging class. It derives from the CATNotification class and its header file just includes the CATDeclareClass macro:
    #include "CATNotification.h"
    class AlarmRinging : public CATNotification
    {
      CATDeclareClass;
    };

    and its source file includes the CATImplementClass macro:

    #include "AlarmRinging.h"
    CATImplementClass(AlarmRinging,
                      Implementation,
                      CATBaseUnknown,
                      CATNull);

    These macros are required to make a CAA V5 component from this class.

  3. Now we need to code the BeginRinging method which publishes this notification:
    void ApartmentAlarm::BeginRinging()
    {
      printf("Alarm begins to ring!\n");
      AlarmRinging * RingingNotification = new AlarmRinging();
      GetDefaultCallbackManager(this)->DispatchCallbacks
                 (RingingNotification, this);
      delete RingingNotification;
    }

    The GetDefaultCallbackManager methods returns the callback manager attached as an extension to the CATBaseUnknown class from which the ApartmentAlarm class derives. This callback manager publishes the notification thanks to the DispatchCallbacks method.

  4. The apartment alarm and the alarm ringing notification are ready to use. We create now the subscriber: the AdvisedBurglar class, which must derive from the CATBaseUnknown class, and include a method to call back when the alarm ringing notification is published, here the RunAway method.
    #include "CATEventSubscriber.h"
    #include "ApartmentAlarm.h"
    
    class AdvisedBurglar : public CATBaseUnknown
    {
      public:
        AdvisedBurglar();
        virtual ~AdvisedBurglar();
        void RunAway(CATCallbackEvent   iAlarmEvent,
                     void             * iAlarm,
                     CATNotification  * iAlarmNotification,
                     CATSubscriberData  iBurglarData,
                     CATCallback        iCallbackId);
    };

    The CATEventSubscriber.h file is included to get the AddCallback global function signature.

    The RunAway method signature is the one of any callback method. Let's detail its parameters:

    This RunAway method is called by the DispatchCallbacks method we have inserted in the third step. Its body could be as follows:

    void AdvisedBurglar::RunAway(
                CATCallbackEvent   iAlarmEvent,
                void             * iAlarm,
                CATNotification  * iAlarmNotification,
                CATSubscriberData  iBurglarData,
                CATCallback        iCallbackId)
    {
      ApartmentAlarm * RingingAlarm;
      RingingAlarm = (ApartmentAlarm *) iAlarm;
    
      printf("AdvisedBurglar: I RUN AWAY!!\n");
    }
  5. The advised burglar should now, to be fully advised, set the callback. Otherwise the RunAway method would never be called. To set this callback, we add the method ComeNearTheApartment and the _Alarm data member to the AdvisedBurglar class. The callback itself is set using the AddCallback global function:
    void AdvisedBurglar::ComeNearTheApartment(
                 ApartmentAlarm *iAlarm)
    {
      _Alarm = iAlarm;              // Record the alarm
      AddCallback(this,             // Set the callback
                  _Alarm,
                  AlarmRinging::ClassName(),
                  (CATSubscriberMethod)&AdvisedBurglar::RunAway,
                  NULL);
    }
  6. To be more fully advised, the callback should be removed when it is unused. Imagine a burglar that could hear the alarm of all the apartments he tried to visit when they ring! To remove a callback, our advised burglar uses the RemoveCallback method in the RunAway method (new statements are shown in bold typeface):
    void AdvisedBurglar::RunAway(
                CATCallbackEvent   iAlarmEvent,
                void             * iAlarm,
                CATNotification  * iAlarmNotification,
                CATSubscriberData  iBurglarData,
                CATCallback        iCallbackId)
    {
      ApartmentAlarm * RingingAlarm;
      RingingAlarm = (ApartmentAlarm *) iAlarm;
      RemoveCallback(this, RingingAlarm, iCallbackId);
    
      printf("AdvisedBurglar: I RUN AWAY!!\n");
      _Alarm=NULL;
    }

    It could be also advisable to remove, or not to set the callback if it is already set. The ComeNearTheApartment method is modified as follows (new statements are shown in bold typeface):

    void AdvisedBurglar::ComeNearTheApartment(
                   ApartmentAlarm *iAlarm)
    {
      if ( ! iAlarm ) return;    // No alarm
      if ( _Alarm )              // I know an alarm
      {
    // This alarm is different from the current one
        if ( _Alarm != iAlarm )
        {
         // remove all the callbacks I have set on that alarm
         RemoveSubscriberCallbacks(this, _Alarm);
        }
    // This alarm is the same than the current one
        else
          return; // nothing to do: callback already set
      }
      _Alarm = iAlarm;             // Record the alarm
      AddCallback(this,            // Set the callback
                  _Alarm, AlarmRinging::ClassName(),
                  (CATSubscriberMethod)&AdvisedBurglar::RunAway,
                  NULL);
      printf("Burglar subscribes to the apartment alarm\n");
    }

    The RemoveSubscriberCallbacks method removes all the callbacks set for a given couple of event subscriber, here the advised burglar, and event publisher, here the apartment alarm. Another method, RemoveCallbacksOn, is more selective, since it remove all the callbacks for a given couple of event subscriber and event publisher, but for a given notification and for a given CATSubscriberData instance.

A simple program to make these objects run could be:

#include "AdvisedBurglar.h"
#include "ApartmentAlarm.h"

main()
{
  ApartmentAlarm    * Alarm;
  AdvisedBurglar    * Burglar;

  Alarm = new ApartmentAlarm();
  printf("Alarm created\n");

  Burglar = new AdvisedBurglar();
  printf("Burglar created\n");

  Burglar->ComeNearTheApartment(Alarm);
  printf("Burglar comes near the apartment\n");

  Alarm->BeginRinging();
  exit(0);
  return 0;
}

This program creates the following output:

Alarm created
Burglar created
Burglar comes near the apartment
Alarm begins to ring!
AdvisedBurglar: I RUN AWAY!!

[Top]

Deriving from CATEventSubscriber

Another way of publishing events or subscribing to events is to derive the publisher or the subscriber, or both, from the CATEventSubscriber class. They can coexist with publishers and subscribers which derive from the CATBaseUnknown class.

Such publishers and subscribers have the same capabilities as if they derive from the CATBaseUnknown class, but since publishing and subscribing methods are their own methods instead of global functions, and since a publisher aggregates its callback manager, other objects can request them to publish or subscribe.

To illustrate this, let's modify our advised burglar example as follows. We simply add a controlled door that the burglar attempts to open without the key, and this attempt publishes an attempt opening notification to which the alarm subscribes, and rings whenever this notification is published. To code this, we add a ControlledDoor class which derives from the CATEventSubscriber class, and thus includes a callback manager. This illustrates the publisher. To also illustrate the subscriber, we make the AdvisedBurglar class also derive from the CATEventSubscriber class instead of CATBaseUnknown.

CAASysCallback2.gif (9755 bytes)

Below is the ControlledDoor class header file:

#include "CATEventSubscriber.h"
#include "AttemptOpening.h"

class ControlledDoor : public CATEventSubscriber
{
  CATDeclareClass;
  public:
    ControlledDoor();
    virtual ~ControlledDoor();
};

CATImplementClass(ControlledDoor, Implementation,
                  CATBaseUnknown, CATNull);

ControlledDoor::ControlledDoor() { }
ControlledDoor::~ControlledDoor() { }

The AttemptOpening notification class is like the previous AlarmRinging notification class:

#include "CATNotification.h"
class AttemptOpening : public CATNotification
{
  CATDeclareClass;
};

CATImplementClass(AttemptOpening, Implementation,
                  CATBaseUnknown, CATNull);

The AdvisedBurglar class constructor has an additional argument: the door instance, to store it as a data member. The AttemptToOpenDoorWithoutKey method is added to the AdvisedBurglar class to simulate the burglar behavior attempting opening the door. This method requests the door to publish an AttemptOpening notification class instance. This is made possible because the ControlledDoor class derives from the CATEventSubscriber class, the DispatchCallbacks method being a member function and not a global function can be called for that ControlledDoor instance. In the same way, the AddCallback method being also a member function instead of the global function, its signature changes by loosing its first parameter:

...
  AddCallback(_Alarm, AlarmRinging::ClassName(),
              (CATSubscriberMethod)&AdvisedBurglar::RunAway,
             NULL);
...

void AdvisedBurglar::AttemptToOpenDoorWithoutKey()
{
  printf("Burglar attempts to open the door\n");
  AttemptOpening * Attempt = new AttemptOpening();
  printf("Door dispatches notification AttemptOpening\n"); // Burglar requests
  _Door->GetCallbackManager()->                          // door to publish
           DispatchCallbacks(Attempt, _Door);              // opening attempt
  delete Attempt;                                          // event
}

warning.gif (206 bytes) The RemoveCallback method is also a member function, and looses its first parameter. Nevertheless, if you want to use the global functions, use the scope resolution operator :: with the appropriate signature, such as for AddCallback:

::AddCallback(Subscriber,
              Publisher,
              Notification::ClassName(),             
              Subscriber::MethodToCallback,
              SubscriberData);

The program to make these objects run is modified as follows (new lines are in bold typeface, and the removed line is commented out):

#include "AdvisedBurglar.h"
#include "ApartmentAlarm.h"

main()
{
  ApartmentAlarm    * Alarm;
  AdvisedBurglar    * Burglar;
  ControlledDoor    * Door;
  Door = new ControlledDoor();
  printf("Door created\n");

  Alarm = new ApartmentAlarm();
  printf("Alarm created\n");

  Burglar = new AdvisedBurglar();
  printf("Burglar created\n");

  printf("Burglar comes near the apartment\n");
  Burglar->ComeNearTheApartment(Alarm);

//Alarm->BeginRinging();
  Burglar->AttemptToOpenDoorWithoutKey();
  exit(0);
  return 0;
}

This program creates the following output:

Door created
Alarm created
Burglar created
Burglar comes near the apartment
Burglar subscribes to the apartment alarm
Burglar attempts to open the door
Door dispatches notification AttemptOpening
Alarm begins to ring!
AdvisedBurglar: I RUN AWAY!!

CAA V5 has the following classes to handle the callback mechanism:

CAASysCallback3.gif (5941 bytes)

The CATEventSubcriber class accommodates both the event publisher and the event subscriber. The pointer to the CATCallbackManager instance is used only if the object is an event publisher. Note that the CATCommand class derives from the CATEventSubscriber class, and thus all the objects that inherit from the CATCommand class are potential event publishers and subscribers. Events are usually designed as CATNotifcation instances, nevertheless the parameter CATCallbackEvent exists as a typedef for a char *.

A subscriber subscribes to a publisher for a given event by means of the AddCallback method which also specifies the method to call, and revokes its subscription by means of the RemoveCallback method.

A publisher declares the event to publish, or instantiate the notification to publish, and delegates to its callback manager by means of the DispatchCallbacks method the job of calling the subscriber registered method for each subscriber it manages in its subscriber's list for that event or notification.

[Top]

Other Types of Callbacks

Interactive applications can set callbacks on a command in the following cases:

When such an event occurs, the application calls the command concerned on the method declared when setting the callback.

[Top]

OnIdle Callbacks

You can set OnIdle callbacks to do specific tasks whenever the application is waiting, for example for user input. Below is the header file of a very simple application that sets such a callback:

#include "CATInteractiveApplication.h"

class MyApplication : public CATInteractiveApplication
{
  public :
    MyApplication();
    virtual ~MyApplication();
    virtual void BeginApplication();
    virtual int  EndApplication();
};
static void OnIdleCallbackMethod();          // static method

Look at the source file below. The BeginApplication method sets an OnIdle callback. The OnIdleCallbackMethod method is requested to be called whenever the application returns in the event main loop. Since this application does nothing, it is always in the event loop, and we set a counter by means of the stop variable, which is incremented each time the callback method is called, and we stop to play when stop reaches the value 10.

#include "MyApplication.h"

static int stop = 0;
MyApplication::MyApplication()
             :CATInteractiveApplication(NULL, "MyAppli")
{ cout <<"Constructing MyApplication" <<endl; }

MyApplication::~MyApplication ()
{ cout <<"Destructing MyApplication" <<endl; }

void MyApplication::BeginApplication ()
{
  cout <<"Beginning MyApplication" <<endl;
  cout <<"Setting an OnIdle callback" <<endl;
  Subscribe(CATSubscribeIdle, this, NULL,
            (void (*)()) OnIdleCallbackMethod);
}
void OnIdleCallbackMethod()
{
  stop++;
  cout << "In the OnIdle callback method - stop = " <<
          stop <<endl;
  if ( stop == 10 )
  {
    cout <<"Exiting MyApplication"<<endl;
    exit(0);
  }
  return;
}

int MyApplication::EndApplication()
{
  cout <<"Ending MyApplication"<<endl;
  return 0;
}

MyApplication Appli;

This application produces the following output:

Constructing MyApplication
Beginning MyApplication
Setting an OnIdle callback
In the OnIdle callback method - stop = 1
In the OnIdle callback method - stop = 2
In the OnIdle callback method - stop = 3
In the OnIdle callback method - stop = 4
In the OnIdle callback method - stop = 5
In the OnIdle callback method - stop = 6
In the OnIdle callback method - stop = 7
In the OnIdle callback method - stop = 8
In the OnIdle callback method - stop = 9
In the OnIdle callback method - stop = 10
Exiting MyApplication
Destructing MyApplication

[Top]

TimeOut Callbacks

You can also set TimeOut callbacks, that is callbacks triggered after a given time elapsed. We keep the previous example, and we add a command, as a data member of the application, which sets a TimeOut callback in its constructor, and which is then called when this time is over.

The command header file is as follows

#include "CATCommand.h"

class MyCATCommand : public CATCommand
{
  public :
    MyCATCommand(CATApplication *Father);
   ~MyCATCommand();
    static void MyStaticMethod();
    static int End;
};

The command MyStaticMethod static method is called by the callback set in the command constructor when 100 milliseconds elapsed:

#include "CATApplication.h"
#include "MyCATCommand.h"

int MyCATCommand::End = 0;
MyCATCommand::MyCATCommand(CATApplication * Father)
         :CATCommand (Father)
{
  cout << "MyCommand sets a timeout callback" << endl;  // Call
  Father->AddTimeOut( 100,(CATCommand*)this,NULL,                // MyStaticMethod
                (void (*)()) MyCATCommand::MyStaticMethod);         // after 100ms
}

MyCATCommand::~MyCATCommand()
{}

void MyCATCommand::MyStaticMethod()
{
  cout << "MyStaticMethod is running" << endl;
  End = 1;
}

The OnIdleCallbackMethod is modified to make the application exit when the TimeOut callback is called, that is when it has set End to 1.

void OnIdleCallbackMethod()
{
  cout <<"In the OnIdle callback method - End = " <<
         MyCATCommand::End <<endl;
  if ( MyCATCommand::End == 1 ) {
    cout <<"Exiting MyApplication"<<endl;
    exit(0);
  }
  return;
}

This produces the following output:

Constructing MyApplication
Beginning MyApplication
MyCommand sets a timeout callback
Setting an OnIdle callback
In the OnIdle callback method - End = 0
In the OnIdle callback method - End = 0
In the OnIdle callback method - End = 0
In the OnIdle callback method - End = 0
In the OnIdle callback method - End = 0
In the OnIdle callback method - End = 0
MyStaticMethod is running
In the OnIdle callback method - End = 1
Exiting MyApplication
Destructing MyApplication

[Top]

File Descriptor Callbacks

You can also set callbacks when file descriptors are modified. This applies generally to any entity homogeneous to a file, such as an input flow, a pipe, a socket, but not to disk files. The term file descriptor applies to UNIX and corresponds to file handle with Window.

[Top]


In Short

The callback mechanism enables publish/subscribe between objects. Any object can publish events, and any object can subscribe to events published by any other object. The publisher delegates to its callback manager the job of calling the subscriber on their requested methods, and the management of the subscriber list.

Objects can also subscribe to system events using onidle, timeout, and file descriptor callbacks.

[Top]


References

[1] xxx
[Top]

History

Version: 1 [Mar 2000] Document created
[Top]

Copyright © 2000, Dassault Systèmes. All rights reserved.