topical media & game development

talk show tell print

object-oriented programming

Crush -- extending hush with CORBA

This section describes how the hush toolkit has been extended with CORBA functionality. The nickname for this effort was crush. The major problem that arises when extending a given toolkit or framework, such as hush, with CORBA IDL interfaces and classes to implement these interfaces is to provide for a seamless integration of the already existing code with the CORBA-based extensions. In crush we have included facilities for object creation and access, as well as client-side adaptors for the hush CORBA objects, to resolve the type clash between the original hush class hierarchy and the hush CORBA object classes.

Extending a framework with CORBA


slide: Extending a framework with CORBA

Extending a given framework with CORBA is not as straightforward as it may seem. First of all, one has to decide which interfaces may become public, that is may be exported as IDL interfaces. Secondly, one has to decide how object references become known to clients, and what rights clients have to create objects within a particular server. The most important problem, however, concerns the type clash between the CORBA classes implementing the IDL interfaces and the `native' class hierarchy offered by the framework itself.

The legacy problem -- integrating CORBA

CORBA technology is well suited to develop distributed applications. For new projects, the restrictions imposed by CORBA can be taken into consideration from the start. For projects that carry the legacy of existing code, a decision must be made to what extent the CORBA functionality is integrated with the legacy code. On one side of the spectrum, CORBA technology can be used simply for wrapping the legacy code. For example a database may be embedded in a CORBA server, without affecting the database itself. However, for an object-oriented framework such as hush such a solution is not very satisfying. Instead, one would like to have the basic interfaces of hush available to develop distributed components of arbitrary granularity.

Object creation and access

The CORBA Naming Service may be used to provide access to an object residing somewhere on a server. Alternatively, the server may export a reference to a factory object that allows the client to create objects within the server.

For giving access to objects within a particular hush component, we have provided dots (distributed object tables) for both hush and the widgets components. Using the dot the client can access an object of a given type by the name it is given by the server. The object must already exist in the server.

In case clients are allowed to create objects within the server, a factory is provided for creating hush or widget objects.

Client-side adaptors

The intermediary between clients and servers in a CORBA-based system are the CORBA IDL classes generated by the idl compiler from the IDL interfaces. These classes (using the C++ language binding) inherit directly from the CORBA::Object class and hence do not fit within the given class hierarchy.

To allow clients the use of CORBA IDL classes wherever one of the original hush classes is expected, client-side adaptors have been provided for each of the hush or widgets IDL classes. An additional advantage of client-side adaptors is that they allow for overcoming the `weaknesses' of IDL with respect to overloading member functions, parametrized types and operator definitions.

Typically, client-side adaptors have their corresponding hush class as a base class and simply delegate method invocations to the CORBA object they encapsulate.

Events versus object method invocation

Since GUI components are in some way typically event-driven, one may be inclined to limit the communication between such components to exchanging events. The CORBA Event Service would suffice for such communications.

Nevertheless, in our opinion events should be used in a very restricted manner. Events tend to break the `crisp' object interfaces that are one of the benefits of an object-oriented approach to design.

For the hush CORBA extensions, we have chosen for retaining the original hush object interfaces. Note however that the IDL interfaces are somewhat more abstract than the corresponding C++ interfaces. Nevertheless, the event interface is part of the hush module. Together with the dispatch function of the handler interface incoming events resulting from user actions may be dispatched directly to remote components.

Interfaces

The IDL interfaces reflect to a large extent the functionality of the original hush and widgets interfaces. In this section a partial listing of the interfaces will be given. In comparison with the corresponding C++/Java classes, the IDL interfaces are much more abstract in the sense that many member functions required for the actual implementation of the hush framework may be omitted.

The hush module

The hush module contains interfaces corresponding to the basic hush classes, handler, event, kit, widget and item, as well as the auxiliary classes for iterators and containers.


  interface handler { 
handler
event dispatch( in event data ); };

slide: handler

The handler interface provides only a method for dispatching events. It may be extended in the future though. In hush almost every class is derived from handler. This is directly reflected in the hush IDL interfaces.


  interface event : handler { 
event
attribute long type; attribute long x; attribute long y; };

slide: event

The event interface offers attributes to determine the type of event and its location. Also the event interface will very likely be extended in the future, to allow for a greater variety of events.


  interface kit : handler { 
kit
void source(in string file); void eval(in string command); string result(); widget root(); };

slide: kit

In hush, a kit provides an interface to some embedded interpreter, such as a Tcl interpreter or a logic engine. The kit also gives access to the underlying window environment; in particular it may be asked to provide a reference to the root window.


  interface widget : handler { 
widget
string path(); void eval( in string cmd ); void configure( in string options ); void pack( in string options ); };

slide: widget

A widget is a user interface gadget. The widget interface collects the functions that all these gadgets have in common.


  interface item : handler { 
item
void move( in long x, in long y ); };

slide: item

An item is obtained when creating a graphical object for a canvas. Subsequently, the item reference suffices to manipulate such objects. Also the item interface will very likely be extended in the future.

Iterators and lists

As an alternative for CORBA arrays and sequences, the hush module offers interfaces for iterators and containers.


  interface iterator { 
iterator
Object next(); };

slide: iterator

From a client's perspective, an iterator is a data generator. To deal with typed iterators, the hush C++ library offers template client-side adaptor classes encapsulating the untyped CORBA iterators.


  interface container { 
container
long length(); Object first(); Object next(); Object current(); iterator walk(); };

slide: container

The container interface offers access to the hush list class. It offers functions for cursor-based list traversal as well as the walk function that may be used to obtain an iterator.

Factories and distributed object tables

To obtain references to objects, clients may use either factory object or distributed object tables.


  interface factory { 
factory
hush::kit kit(in string name); hush::event event(in long type); };

slide: factory

The factory interface allows only for creating a kit and for creating an event. Note that handler objects may not be created directly.


  interface dot {  
dot
hush::kit kit(in string name); hush::container container(in string name); hush::iterator iterator(in string name); hush::factory hush(in string name); };

slide: dot

Apart from giving access to a hush factory, the dot interface allows for getting access to a kit, a container and an iterator. When obtaining references through a dot object, these objects are assumed to exist within the server.

The widgets module

The widgets module provides the actual user interface gadgets for hush. Below we have included only the (partial) interfaces for a canvas and a message widget.


  module widgets {
  
  interface canvas : hush::widget { 
canvas
canvas create( in hush::widget anc, in string path ); hush::item circle( in long x, in long y, in long radius, in string options ); // other items ... }; interface message : hush::widget {
message
message create( in hush::widget anc, in string path ); void text(in string txt); }; interface factory : hush::factory {
factory
widgets::canvas canvas(in string name, in string options); widgets::message message(in string name, in string options); }; interface dot : hush::dot {
dot
widgets::canvas canvas(in string name); widgets::message message(in string name); widgets::factory widgets(in string name); }; };

slide: module widgets

Note that each widget type has a method create, with which an actual widget of that type can be created. In effect this means that each widget object may act as a factory for widget objects of that type. (The server may however refuse to create such objects!) In addition to the specific gadget interfaces, the widgets module provides a factory and dot interface, extending the respective hush interfaces.

Examples

The hush CORBA extensions may be used in a number of ways. For example, the client does not need to be linked with hush when only the server side is given a graphical user interface. In the case that also the client has a graphical user interface, the client side may dispatch incoming events to the server, as illustrated in the canvas example. The communication between server and clients can be arbitrarily complex. The final example shows how to employ iterators and containers to give clients accesses to collections of information.

A remote interpreter kit

This example uses a remote kit. It shows how to get access to a message widget and a kit via a dot (which is a distributed object table). The client access the kit and the message widget by using a name (hello for the message widget and tk for the kit). The client can send Tcl/Tk interpreter commands to the kit.

client



      hush::dot*      hush;      // (distributed) object tables
      widgets::dot*   widgets;   // widgets contains hush
  
      hush::kit* tk;             // remote kit object
      widgets::message* banner;
  
      try {
          hush = widgets = widgets::dot::_bind (SERVER, argv[1]);
  
          tk = hush->kit("tk");
          banner = widgets->message("hello"); // must exist
  
      } catch (...) {
           cerr << "Unexpected exception ..." << endl;
           return -1;
      }
  
  
      while (1) {
  	char text = readtext(); // from stdin
  	
  	banner->text( text );  // display text
  	tk->eval(text);
          }
  

slide: A tk client

This fragment shows how a distributed object table is obtained via the bind function. From this table, the client obtains a kit and a message area. Queries are read in from standard input, displayed in the message area and evaluated. Queries may be arbitrary Tcl/Tk commands. In this way the client may even construct a complete user interface through Tk commands.

server



  class application : public session {
  public:
  application(int argc, char* argv[]) : session(argc,argv,"hello") {
  }
  void corba();
  int main() {
      tk->trace();
      kit::declare("tk",tk);
  
      message* m = new hello(".hello"); 
      m->pack();
  
      message::declare("hello",m);
  
      corba(); // make yourself available as a server
  
      return OK;
      }
  };
  

slide: The tk server

The server is realized as a standard hush program, except for the call to corba (for which the code is given below). Note that the calls to declare for both the kit and message objects is needed to make these objects accessible via the dot.


  void application::corba() {
  
  widgets::dot* dw = new widgets_dot_srv(); // create dot for widgets
  
  try {
   CORBA::Orbix.registerIOCallback(it_orbix_fd_open, FD_OPEN_CALLBACK);
   CORBA::Orbix.registerIOCallback(it_orbix_fd_close, FD_CLOSE_CALLBACK);
  
   CORBA::Orbix.impl_is_ready(SERVER,0);
   CORBA::Orbix.processEvents(0);
   }
  catch (...) {
       cout << "apparently something went wrong" << endl;
     }
  

slide: A tk application

In application::corba() a distributed object table is created. This object is exported as a server by a call to Orbix.impl_is_ready(SERVER,0), where SERVER is a macro defining the name of the server. Calling registerIOCallback is needed to merge the (Orbix) CORBA server event loop with the window event loop for hush.

Evaluating logical queries

With a few minor changes, the client program can be adapted for accessing a logical query evaluator.

client



      try {
  
          tk = hush->kit("bp");       // A kit for BinProlog
          tk->eval("consult(facts)");
          }
      catch(...) {
          cout << "An exception ... " << endl;
      }
  
      while (1) {
          char* text = readtext();
          tk->eval(text);
          char* q = 0;
          while ( (q = tk->result()) )
                  cout << "Result: " << q << endl;
  	}
  

slide: Evaluating logical queries

This fragment show how to obtain a kit for BinProlog and consult a facts database. Since queries may produce multiple answers the client must iterate over the term resulting from the query.

A remote canvas

This example shows how a client canvas can be used to draw on a remote canvas.

  class draw_clt : public canvas {  
draw_clt
public: void plug(widgets::canvas* x) { draw = x; } int operator()() { hush::event* e = hush->event(_event->type()); cerr << "Getting event " << e->type() << endl; e->x(_event->x()+10); e->y(_event->y()+10); hush::event::_duplicate(e); // CORBA 2.0 hush::event* res = draw->dispatch(e); return canvas::operator()(); } draw_clt(const widget* w, char* path ) : canvas(w,path) { configure("-background white"); geometry(200,100); self()->bind(this); dragging = 0; } draw_clt(char* path ) : canvas(path) { configure("-background white"); geometry(200,100); self()->bind(this); dragging = 0; } void press( event& ) { dragging = 1; } void motion( event& e) { if (dragging) { self()->circle(e.x(),e.y(),2,"-fill black"); draw->circle(e.x(),e.y(),3,"-fill yellow"); } } void release( event& ) { dragging = 0; } protected: int dragging; widgets::canvas* draw; };

slide: A (remote) canvas client

This fragment shows the implementation of a canvas which is simultaneously the client side of a remote canvas. The method plug allows for declaring the remote canvas, which is accessed via the instance variable draw in both the operator method and the motion method (when dragging). In the operator method an event is created which is dispatched to the remote canvas. Note that this is possible since a canvas is a handler. In the motion method, a large yellow dot is drawn on the remote canvas, whereas the local canvas draws a black dot. Combined, the actions on the remote canvas result in drawing parallel yellow and black dots.


  class draw_srv : public canvas { 
draw_srv
public: draw_srv( const widget* w, char* path ) : canvas(w,path) { geometry(200,100); self()->bind(this); dragging = 0; } void press( event& ) { dragging = 1; } void motion( event& e) { if (dragging) circle(e.x(),e.y(),10,"-fill black"); } void release( event& ) { dragging = 0; } protected: int dragging; };

slide: A canvas server

The canvas implementation on the server side straightforwardly implements a hush canvas. It is embedded in a CORBA server when an object reference is given to it via the distributed object table.

Moving items

This example is similar to the canvas example, but shows some additional features, such as how to manipulate a list of items.

server



      list* rlist =  new list;
      item* it = draw->circle(40,40,10,"-fill yellow");
      hush::item* rit = new item_srv(it);
      rlist->insert(rit);
      it = draw->circle(30,30,10,"-fill red");
      rit = new item_srv(it);
      rlist->insert(rit);
  
      hush::container* rx = new list_srv(rlist);
      list::declare("items",rx); // store server
  
      iter* riter = rlist->walk();
      iter::declare("riter",riter);
  

slide: Moving items

The fragment above illustrates the creation of a list of items. In addition it shows how to obtain an iterator and how the iterator may be declared to make it accessible via the distributed object table.

Discussion

From a design point of view, the hush framework proved to be sufficiently abstract to allow for recapturing its design by means of IDL interfaces. Lacking in the current realization of crush though, are proper exceptions to indicate possible error conditions.

This work shows how to integrate CORBA functionality with an already existing framework. In particular the need for client-side adaptors for resolving the type clash between the `native' classes and the CORBA IDL classes has been amply demonstrated. Enriching hush with CORBA makes crush a potential competitor of Fresco, the CORBA based GUI toolkit derived from the Interviews library.



(C) Æliens 04/09/2009

You may not copy or print any of this material without explicit permission of the author or the publisher. In case of other copyright issues, contact the author.