libASSA Programmer's Manual | ||
---|---|---|
<<< Previous | Chapter 1. Application Shell | Next >>> |
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.
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 } |
On line 7, we prepend arguments string with application name because most initialization routines expect the very first argument, argv[0], to be an application name.
On line 8, we make an argument list array out of a string.
On line 10 we initialize Gtk+ toolkit, and on line 12 we release the memory allocated on line 8.
We can now pass any Gtk+ arguments to our application:
% GuiApp --log-stdout --gtk-options="--show-menubar --disable-crash-dialog" |
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 }; |
Line 52 declares an idle callback that Gtk+ event loop would call any time there is no GUI events to process.
Line 55 declares IDLE_TIMEOUT that will be used by libassa event loop.
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 (>k_argc, >k_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 } |
On line 116, an idle callback function is connected to the "idle" Gtk+ signal.
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 |
On line 87, we let libassa to execute its event loop for IDLE_TIMEOUT time interval.
Executing certain events might have caused the application to exit. We check for the exit condition in lines 88-93, and terminate main GUI event loop if necessary.
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 } |
On line 127, we let GUI event loop to take priority and server its own events as well as libassa events when appropriate.
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 } |
Lines 20-32 define X11 timer callback. This is where we have a chance to run libassa event loop.
Line 70 schedules first timer.
Line 72 enters X11 event loop for processing GUI events.
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 }; |
Lines 45 and 46 include CORBA library file and stub header generated by IDL compiler.
Lines 54 through 65 define get_gmt() interface function this service provides. It returns the host's GMT time.
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 }; |
Line 71 declares another virtual function of GenServer we haven't seen yet. init() is usually called by main() to initialize GenServer object.
Line 76 declares a data member, m_orb, to hold our instance of ORB.
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 } |
Lines 86 through 99 initialize ORB. We could have used a technique described earlier (see Section 1.7.1) to separated command-line arguments instead.
Line 101 initalizes GenServer object.
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 } |
Lines 213-214 run ORB event loop until there is nothing for it to do.
Lines 220-221 run libassa event loop for a half a second as defined on line 206.
<<< Previous | Home | Next >>> |
Handling UNIX Signals | Up | Summary |