Next Previous Table of Contents
The Base interface and its implementation in libkom
provide the basic
functionality for a standardized communication between CORBA objects using
KOM. This includes
KOM supports signals and slots just like in the Qt toolkit, with a few differences in usage and implementation. The first and biggest difference is that signals and slots are no more typesafe again, meaning there's no moc compiler generating meta data for KOM signals/slots to enable type checking at run-time, when connecting.
Another difference is the way you declare signal and slot functions. Signals
have to be declared with the SIGNAL_IMPL
macro from komBase.h
,
without specifying any signal arguments. Slot methods have to be defined in
your CORBA interface description.
For KOM signals the equivalent to the "emit" keyword from Qt is the
SIGNAL_CALLx
macro, were "x" is one of 0, 1, 2, depending on the
number of arguments.
Example:
In your CORBA interface description:
#include <kom.idl> interface FooSender : KOM::Base { signal void mySignal( in long foobaz ); }; interface FooReceiver : KOM::Base { slot void mySlot( in long gosh ); };
In the implementation of FooSender:
FooSender_Impl::FooSender_Impl( ... ) { ... SIGNAL_IMPL( "mySignal" ); ... } FooSender_Impl::mySignal( CORBA::Long foobaz ) { SIGNAL_CALL1( "mySignal", foobaz ); }
In the implementation of FooReceiver:
FooReceiver_Impl::mySlot( CORBA::Long gosh ) { ... }
First some words about the sender: It is not required to define the signal
in the interface description and to provide an implementation which simply
emits the signal. However in many cases this is recommended, because this makes
it easier for other developers to use your interface because they aren't required
to seek in the implementation sources just to find out about the signals this
object emits. Another way is to simply document the existence of the signal
in the interface description, without defining a method. From the technical
point only the SIGNAL_CALLx
macro counts when emitting the signal.
What's left is connecting and disconnecting. In the above described example it could like this:
... SenderObject->connect( "mySignal", ReceiverObject, "mySlot" ); ... SenderObject->disconnect( "mySignal", ReceiverObject", "mySlot");
Well, this is quite self-explaining I think. Just make sure to always disconnect from your object upon destruction.
An event consists of two elements, the event name, being a string, and an event argument, being a CORBA::Any value and therefore freely choosable by the developer.
Events, sent to a specified object, can be imagined as being put through a pipe until they reach the destination object. This "pipe" is filled with installed event filters. There are three kinds of filters.
FM_READ
)FM_WRITE
)FM_IMPLEMENT
)In the current implementation in KOM these filter modes only specify the order
how the event is processed. When an event is emitted it gets first filtered by
all event filters with the filter mode FM_WRITE
, then followed by
FM_IMPLEMENT
and finally by FM_READ
. Event filters have two
possibilities what they can do with the actual event: They can just
read it or they can discard it, which means the event is
discarded and will never receive its destination object.
The actual event name has a special meaning in regard to event filters. When
installing an event filter to an object you have to specify, beside a
reference to the filter object and the name of the filter mapping function, a
sequence of so called event type patterns. An event type pattern can be the
name of a single event as well as a special pattern (see kom.idl
for more
information about event type patterns) .
When an event is meant to be processed by a filter, the specified filter function gets called, with the event name and the event value as arguments. This filter function has to return (through a boolean value) whether the event should be discarded or not.
When all filtering is done and none of the installed filters discarded the
event, it is finally received by the destination object, by calling the
object's event()
method, defined in the KOM::Base interface. The default
implementation does actually nothing, so you may want to re-implement this
virtual function.
The very low-level usage of events is to call the receive/receiveASync methods of an object for sending an event and to re-implement KOMBase::event for mapping an event. But KOM provides some nice macros which simplify the processing of events.
For sending komBase.h defines some useful EMIT_EVENT
macros, all
using the same syntax:
EMIT_EVENT_x( destination_object, event_name, event_argument)
"destination_object" is a reference to the object which is meant to receive/process the event. The event will be filtered through all event filters which are installed in this destination object. "event_name" is self-explaining ;-) . The event argument depends on the specific macro, which are in particular:
EMIT_EVENT
, the general macro for sending. The event argument is
required to have a <<=
operator for CORBA::Any defined. You
will want to use this macro whenever the event argument is a structure
for example. (and don't forget to compile your idl file with the "--any"
option, so that the idl compiler generates the necessary operator
methods)EMIT_EVENT_BOOLEAN
, useful for boolean event arguments. The
only difference to the EMIT_EVENT
macro is that it uses
CORBA::Any::from_boolean for you to convert the boolean value. So it
doesn't really matter whether you use:
EMIT_EVENT( receiver, name, CORBA::Any::from_boolean( value ) );or
EMIT_EVENT_BOOLEAN( receiver, name, value );
EMIT_EVENT_OCTET
, similar to EMIT_EVENT_BOOLEAN, useable for
CORBA::Octet values.EMIT_EVENT_CHAR
, similar to EMIT_EVENT_BOOLEAN, useable for char
(CORBA::Char) values.EMIT_EVENT_WCHAR
, similar to EMIT_EVENT_BOOLEAN, useable for
CORBA::WChar values.EMIT_EVENT_STRING
, similar to EMIT_EVENT_BOOLEAN, useable for
char * (CORBA::Char*) values.EMIT_EVENT_WSTRING
, similar to EMIT_EVENT_BOOLEAN, useable for
CORBA::WChar* values.EMIT_EVENT_OBJECT
, similar to EMIT_EVENT_BOOLEAN, useable for
CORBA objects (CORBA::Object).
The process of receiving events is a little bit more difficult, compared to sending, since we have to process all kinds of events an object can receive in one handler method, KOM::Base::event (IDL) / KOMBase::event (C++) . Just like with sending events you can again do everything on low CORBA level, but why should we go the hard way? KOM again provides very nice and easy-to-use macros for this (defined in komBase.h) :-) . Usually all this looks like the following example:
#include <kom.idl> module MyModule { // we say: the event argument is a string const string eventFirstFoo = "MyFooEventNameOrWhateverYouNameIt"; struct MyStruct { boolean kde_rules; }; const string eventSecondFoo = "Blaafooo"; typedef MyStruct EventSecondFoo; interface SomethingElse { //.. }; const string eventThirdFoo = "KOMIsCool"; typedef SomethingElse EventThirdFoo; interface Foo : KOM::Base { //... }; }; bool FooImpl::event( const char *event, const CORBA::Any &value ) { EVENT_MAPPER( event, value ); MAPPING_STRING( MyModule::eventFirstFoo, mappingFirstFoo ); MAPPING( MyModule::eventSecondFoo, MyModule::EventSecondFoo, mappingSecondFoo ); MAPPING( MyModule::eventThirdFoo, MyModule::EventThirdFoo_ptr, mappingThirdFoo ); END_EVENT_MAPPER; //the macro executes "return false;" for us, to indicate that //we did not handle the event if we reach this point } bool FooImpl::mappingFirstFoo( const char *myArgument ) { ... //don't forget to return with a boolean value, indicating whether you sucessfully //processed the event or not. } bool FooImpl::mappingSecondFoo( MyModule::MyString anotherArg ) { //... } bool FooImpl::mappingThirdFoo( MyModule::SomethingElse_ptr whaaboo ) { //... }
As you can see an event handler usually begins with the EVENT_MAPPER
macro and ends with END_EVENT_MAPPER
. Similar to the
EMIT_EVENT_x
macros, the MAPPING
macros consist of a general
MAPPING
macro and the following friends:
MAPPING_BOOLEAN
MAPPING_OCTET
MAPPING_CHAR
MAPPING_WCHAR
MAPPING_STRING
MAPPING_WSTRING
MAPPING_OBJECT
In order to structurize the process of event handling a little bit, every event gets its own event handling function. These functions are called by the mapping macros (last argument) . The above used naming scheme is not a requirement, however it is used in most applications using KOM.
You should use adopting whenever you want to hold a reference to an object and
want to be informed when the object dies in order to free all your references
to this object. But this should only be used when you're not the parent
object, meaning you didn't reference the object directly via the KOM reference
counter. When using adopting you should re-implement the leaveNotify
(and perhaps adoptNotify
) methods of your object (and don't forget
to call the original KOMBase method!) .
KOM reference counting should be used to "express" that you possess the
object. This gives you direct control over the lifecycle of the object by
letting the reference counter act directly on the server object, in contrary
to CORBA reference counting, where the reference counter only acts on the stub
object, in case of remote objects (this is different for local objects, where
stub = server object) . When the KOM reference counter drops down to zero the
object gets destroyed. This destruction is done by calling cleanUp()
,
which closes all connections to other objects and leaves all relatives. After
this call is finished the object truly gets released. You might want to
re-implement the cleanUp()
method. In this case make sure that you
don't forget two things:
m_bIsClean
is false,
otherwise simply returnAs a short summary to this KOM reference stuff just keep in mind, that there are three ways to hold a reference to an object:
Hint: Using the KOMVar
template makes handling KOM references much
easier, they can be used similar to the CORBA _var types.
The Component interface, being derived from the Base interface, additionally provides a kind of small interface repository for only this component, combined with the possibility to provide new interfaces by dynamic aggregation and a standard way to add plugin components. This gives CORBA objects the possibility to enhance their functionality at run-time.
There are five kinds of interfaces:
Builtin interfaces are all interfaces the object directly implements. This
means they are part of the actual object implementation and can be specified
via the ADD_INTERFACE
macro (in komComponent.h) . So for example if
your interface description looks like this:
module Foo { interface MyInterface : AnotherInterface { ... }; };
You should add the following line into the constructor of an implementation of this interface:
ConstructorNameOfMyInterface::ConstructorNameOfMyInterface( ... ) { ... ADD_INTERFACE( "IDL:Foo/MyInterface:1.0" ); ... }
This way you tell your component that it supports the interface
"Foo/Interface" and therefore makes it available through the three functions
getInterface()
, interfaces()
and
supportsInterface()
.
Aggregates solve a problem with distributed objects, the problem of derivation. Since the implementation of an interface is completely encapsulated there has to be another way to extend the functionality of an already existing object. By using aggregate components you can add new interfaces to an object, at run-time. This means you extend the functionality but you do not change the behaviour of the object itself.
Builtin aggregate interfaces are the interfaces of aggregate implementations
which run in the same process as our component. See in komComponent.h the four
functions of the KOMComponent
class for adding builtin aggregates, it's easy.
Dynamic aggregates are similar to builtin aggregates, with two differences:
Plugins are the kind of counterpart to aggregates. They do not extend the functionality of an object by providing new interfaces, but instead usually change the behaviour of it, by
Containers do something simple but extremly useful: They act as repository for Container members. A container member structure consists of two elements:
KOM contains two abstract factory interfaces:
They both serve the job of creating objects and are needed for the creation of dynamic plugin and aggregate components. Whenever you want to install a such a dynamically created object to a component you have to provide an implementation of a factory interface.
KOMApplication is the drop-in replacement for KApplication, required when
using CORBA in your KDE Application. It, internally, combines CORBA event
handling with Qt event handling and initializes the ORB and the BOA on
startup. komApplication.h defines two smart macros to get a reference to the
ORB/BOA: komapp_orb
and komapp_boa
. Usually you will want
to use your own application class, derived from KOMApplication, and
re-implement start()
and/or restore()
, which will be called
>from KOMApplication::exec()
, depending on the BOA's state about
restoring objects. For further information about KOMApplication's API see
komApplication.h .
Next Previous Table of Contents