1.7. Mixing Different Toolkits

Your application might include several libraries, each of which has some kind of an event loop to run. The prime example is writing a GUI application with libassa. Both libassa and GUI toolkit require running their event loops. In this section, we are going to look at different strategies of handling this problem.

Most toolkits also rely in their initialization on command-line arguments. This is always the point of a clash between libassa and other toolkits. We first present one way to deal with this clash.

1.7.1. Separating Command-line Arguments

Each toolkit usually relies in its configuration on a particular set of command-line arguments to be passed to its initialization function. Each toolkit has an initialization function that takes argv[] array of command-line arguments as one of its parameters. The format of the arguments varies vastly from one toolkit to another. It is best to group all arguments of a particula toolkit in a double-quoted string and then later process the string by splitting and reconstructing an argv[] argument array before calling toolkit's initialization function.

For example, we might want to write a GUI application, GuiApp, using Gtk+ toolkit. In order to hold all Gtk+ command-line arguments in one string, we define {--gtk-options=STRING} command-line argument.


class GuiApp {
public:
    GuiApp ();
    // ...

private:
    // ...
    string  m_gtk_options;

    Gtk::Main* m_kit;
};
		

Contructor for GuiApp adds new command-line argument:


GuiApp::GuiApp () : m_kit (NULL)
{
    // ...
    add_opt (0, "gtk-options", &m_gtk_options);
}
		

During initialization, we parse the value of m_gtk_options argument and create an argument array out of it to be used with Gtk+ initialization function.


001 void GuiApp::initServer ()
002 {
003     // ...
004    int gtk_argc = 0;
005    char** gtk_argv = NULL;
006
007    m_gtk_options = getCmdLineName () + " " + m_gtk_options;
008    ASSA::CmdLineOpts::str_to_argv (m_gtk_options, gtk_argc, gtk_argv);
009
010    m_kit = new Gnome::Main (PACKAGE, VERSION, gtk_argc, gtk_argv);
011
012    ASSA::CmdLineOpts::free_argv (gtk_argv);
013
014    // ...
015 }
		

We can now pass any Gtk+ arguments to our application:


% GuiApp --log-stdout --gtk-options="--show-menubar --disable-crash-dialog"
		

1.7.2. Gtkmm Event Loop

Gtkmm is a popular C++ wrapper for Gtk+ GUI toolkit. Basic support for Gtkmm library is provided by assa-genesis utility. Use command-line argument {--gtk2-app} to generate stub files ready to be compiled into a basic Gtkmm2 application.


% assa-genesis --gtk2-app GuiApp
		

All necessary event loop code is generated as well. For the sake of completeness, we are going to examine the code generated.


// GuiApp.h

038 class GuiApp :
039     public virtual SigC::Object,
040     public ASSA::GenServer,
041     public ASSA::Singleton<GuiApp>
042 {
043 public:
... 
052     bool idle_cb ();
053 
054 private:
055     static const int IDLE_TIMEOUT;
056     std::string m_gtk_options;
057     Gtk::Main* m_kit;
...
060 };
		


095 const int GuiApp::IDLE_TIMEOUT = 500;
096
097 void
098 GuiApp::
099 initServer ()
100 {
101     trace_with_mask("GuiApp::initServer",GUITRACE);
102 
103     ASSA::Log::disable_timestamp ();
104     int gtk_argc = 0;
105     char** gtk_argv = NULL;
106 
107     m_gtk_options = "GuiApp --g-fatal-warnings " + m_gtk_options;
108     CmdLineOpts::str_to_argv (m_gtk_options, gtk_argc, gtk_argv);
109     
110     m_kit = new Gtk::Main (&gtk_argc, &gtk_argv);
111     
112     CmdLineOpts::free_argv (gtk_argv);
113     
114     m_main_window = new MainWindow;
115     
116     Glib::signal_idle().connect (slot (*this, &GuiApp::idle_cb));
117     
118     DL((ASSA::APP,"Service has been initialized\n"));
119 }
		

Idle callback executes libassa event loop for a period of time to serve all outstanding events.

...
082 bool
083 GuiApp::
084 idle_cb ()
085 {
086     ASSA::TimeVal timeout (IDLE_TIMEOUT);
087     REACTOR->waitForEvents (&timeout);
088     if (stopServer ()) {
089         REACTOR->stopReactor ();
090         DL((ASSA::USR1,"Terminating main GUI loop\n"));
091         Gtk::Main::quit ();
092         return false;
093     }
094     return true;
095 }
096 
		


120 
121 void
122 GuiApp::
123 processServer ()
124 {   
125     trace_with_mask("GuiApp::processServer",GUITRACE);
126     
127     m_kit->run (*m_main_window);
128     
129     DL((ASSA::APP,"Service stopped!\n"));
130 }
		

1.7.3. Motif Event Loop

You can integrate libassa with Motif GUI by scheduling an X11 timer and running libassa event loop from the timer callback. This is how HelloWorld main() might look like if it were a Motif application:

Example 1-10. Running Event Loop with Motif


// HelloWorld-main.cpp

020 static void 
021 TimerCB (XtPointer /* data */, XtIntervalId* /* timerid */)
022 {
023    // Run libassa event loop to process events
024    //
025    TimeVal timeout(0.001);
026    REACTOR->waitForEvents (&timeout);
027
028    // Reschedule the timer
029    // 
030    XtIntervalId timerid;
031    timerid = XtAppAddTimeOut (app, 100, TimerCB, (XtPointer)NULL);
032 }

...

053 main (int argc, char* argv[])
054 {
055     static const char release[] = "1.0";
056     int patch_level = 0;
057 
058     HELLOWORLD->set_version (release, patch_level);
059     HELLOWORLD->set_author  ("Joe the Hacker");
060     HELLOWORLD->set_flags   (ASSA::GenServer::RMLOG);
061 
062     HELLOWORLD->init (&argc, argv, help_msg);
063 
064     HELLOWORLD->initServer ();
065     
066     // Initialize Motif library
067        ...
068
069     // XtIntervalId timerid;
070     timerid = XtAppAddTimeOut (app, 100, TimerCB, (XtPointer)NULL);
071
072     XtAppMainLoop (app);
073
074     return HELLOWORLD->get_exit_value ();
075 }
		  

1.7.4. CORBA Event Loop

Integration of libassa event handling loop with CORBA event handling loop is similar. We alternate between letting CORBA event loop to drain its event queue and libassa event loop with a timer to process its events.

To illustrate, we are going to examine the relevant snippets of code from a simple CORBA-based TimeServer. Here is its IDL:


015 struct TimeOfDay
016 {
017     short hour;         // 0 - 23
018     short minute;       // 0 - 59
019     short second;       // 0 - 59
020 };
021 
022 interface Time
023 {
024     TimeOfDay get_gmt ();
037 };
		

Here is the implementation of the IDL interface:


045 #include <orbsvcs/orbsvcs/CosNamingC.h>
046 #include "timeS.h"
047 
048 // Class Time_impl
049 
050 
051 class Time_impl : public virtual POA_Time
052 {
053 public:
054     virtual TimeOfDay get_gmt ();
055     {    
056         time_t time_now = time (0);
057         struct tm* time_p = gmtime (&time_now);
058 
059         TimeOfDay tod;      // created on a stack
060         tod.hour   = time_p->tm_hour;
061         tod.minute = time_p->tm_min;
062         tod.second = time_p->tm_sec;
063
064         return tod;     // return a copy
065     }
066 };
		

We further declare our application shell class, TimeServer.


064 class TimeServer :
065     public GenServer,
066     public Singleton<TimeServer>
067 {
068 public:
069     TimeServer ();
070 
071     virtual void init (int* argc_, char* argv_[], const char* help_);
072     virtual void initServer ();
073     virtual void processServer ();
074 
075 private:
076     CORBA::ORB_var m_orb;
077 };
		

We overload GenServer::init() because we want to initalize ORB before initializing GenServer object:


084 void TimeServer::init (int* argc_, char* argv_[], const char* help_)
085 {
086     int largc=N;
087     char* largv[5] = { "arg1", "arg2", ... , "argN" , NULL };
088 
089     try {
090         m_orb = CORBA::ORB_init (largc, largv);
091     }
092     catch (const CORBA::Exception& ex_) {
094         setStopServerFlag ();
095         return;
096     }
097     catch (...) {
098         Assert_exit (false);
099     }
100 
101     GenServer::init (argc_, argv_, help_);
102 }
		

initServer() goes throught the multitude of steps of initializing CORBA service. Everything here is CORBA-related.


105 void TimeServer::initServer ()                 
106 {
...
112     try {
113         CORBA::Object_var obj;
114         obj = m_orb->resolve_initial_references ("RootPOA");
115 
116         PortableServer::POA_var poa;
117         poa = PortableServer::POA::_narrow (obj.in ());
...
121         PortableServer::POAManager_var mgr;
122         mgr = poa->the_POAManager ();
123         mgr->activate (); 
...
128         Time_impl timer_servant;
...
131         PortableServer::ObjectId_var obj_id;
132 
133         obj_id = poa->activate_object (&timer_servant);
...
136         CORBA::Object_var time_service_obj;
137         time_service_obj = poa->id_to_reference (obj_id.in ());
...
144         CORBA::Object_var naming_obj;
145 
146         naming_obj = m_orb->resolve_initial_references ("NameService");
...
151         CosNaming::NamingContext_var inc;
152 
153         inc = CosNaming::NamingContext::_narrow (naming_obj.in ());
154 
155         if (CORBA::is_nil (inc.in ())) {
156             DebugMsg(APP,"[%s] Nil Naming Context reference",self);
157             Assert_exit (false);
158         }
...
162         CosNaming::Name name;
163         name.length (1);
164 
165         name[0].id = CORBA::string_dup ("Examples");
166         name[0].kind = CORBA::string_dup ("");
167 
168         try {
169             CosNaming::NamingContext_var nc;
170             nc = inc->bind_new_context (name);
171         }
172         catch (const CosNaming::NamingContext::AlreadyBound& ) {
173             DebugMsg(APP,"[%s] Context \"Examples\" already exists",self);
174         }
175 
177         name.length (2);
178         name[1].id = CORBA::string_dup ("TimeService");
179         name[1].kind = CORBA::string_dup ("");
180 
181         /*--- Bind Name=IOP pair ---*/
182 
183         inc->rebind (name, time_service_obj.in ());
184 
186     }
187     catch (const CORBA::Exception& ex_)
188     {
190         setStopServerFlag ();
191     }
192     catch (...) {
193         Assert_exit (false);
194     }
		

And finally processServer runs two event loops back to back:


204 void TimeServer::processServer ()
205 {   
206     TimeVal timeout (0.5);   // half a second
207     TimeVal tv;
208 
209     while (!stopServer()) {
210 
211         /*-- Run ORB event loop --*/
212 
213         if (m_orb->work_pending ()) {
214             m_orb->perform_work ();
215         }
216 
217         /*--- Run libassa Reactor event loop ---*/
218         tv = timeout;
219 
220         while (tv != TimeVal::zeroTime ()) {
221             m_reactor.waitForEvents (&tv);
222         }
223     }
224     m_reactor.stopReactor ();
225     m_orb->shutdown (1);
228 }