3D PLM Enterprise Architecture |
Middleware Abstraction |
The Callback MechanismMaking objects collaborate |
Technical Article |
AbstractThe 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. |
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]
There are two ways of making an object an event publisher and/or subscriber:
[Top]
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.
This is how to create such objects, and make it run together:
BeginRinging
method:
#include "CATBaseUnknown.h" class ApartmentAlarm : public CATBaseUnknown { CATDeclareClass; public: ApartmentAlarm(); virtual ~ApartmentAlarm(); void BeginRinging(); }; |
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.
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.
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:
iAlarmEvent
is the event that happens. Since we do not
define any event, but only a notification, it will be passed to NULLiAlarm
is the publisher object, that is the
ApartmentAlarm instanceiAlarmNotification
is the published notification, that is
the RingingNotification instanceiBurglarData
is data that the advised burglar could
request to execute the callback. It is set to NULL hereiCallbackId
is the identifier of the callback: it is a
long integerThis 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"); } |
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); } |
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]
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.
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 } |
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:
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]
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]
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]
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]
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]
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]
[1] | xxx |
[Top] |
Version: 1 [Mar 2000] | Document created |
[Top] |
Copyright © 2000, Dassault Systèmes. All rights reserved.