| 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 |