XCOM
In this tutorial we develop a simple XCOM component and a test program
that access the component. We assume that the installation
steps are completed, XCOM header files are placed into
directory ~/include
,the libxcom.so
is placed into directory ~/lib
and the
XCOM's idl files are placed in directory ~/idl
.
XCOM contains many similarities with other component systems especially Microsoft's COM and Mozilla XPCOM. All these systems are interface based binary component standards where there is a root interface that provides capability querying and lifetime management services through reference counting.
At the very heart of the component based programming lies interfaces. A component encapsulates implementation that can be accessed using the provided interfaces by the component. In simple terms an interface is a named list of functions. In XCOM you specify the interfaces and the datatypes in a special declarative language which resembles most of the other IDL's.
In the following code snipped a simple interface named
simple.IHello
is defined:
import "xcom/IUnknown.idl"; namespace simple { interface IHello ("91c16a9e-b609-47cf-885a-644218dd4067") extends xcom::IUnknown { void sayTo(in string who); } }
There are a few things noteworthy to explain. The first line
is an import statement which is used to bring definitions from
other IDL files. You must directly or indirectly import
xcom/IUnknown.idl
file in your IDL files.
The second point is the namespace line. Namespaces are used to partition the namespace of datatypes to prevent name collisions. Namespaces can be nested arbitrarily deep.
Then the interface definition comes. Following the interface
keyword the textual name of the interface comes, here it is IHello
then the numeric name follows. This numeric name is an 128-bit identifier
that is globally unique, which is called a GUID. This number can be
generated using the uuidgen
utility. After that, the
extends part comes. Every interface must inherit from another
interface except the root interface xcom.IUnknown
. This
means that, at the end, every interface inherit from xcom.IUnknown
directly or indirectly. More comes for this interface. Then the list
of functions comes, specifying return type, name and the parameters. The
parameters must specify an access mode, parameter type and a name. The access
mode can be one of in
, out
or inout
.
Save the code listing given above to a file named Simple.idl
.
Now comes generation of the header files that will be used by the component
implementation and the client.
The xcomidl
utility is used to generate C++ headers from
IDL files. Its usage is given below:
$ xcomidl -I include_path1 -I include_path2 ... idlFile1.idl idlFile2.idl ...
Now if you run xcomidl
on the Simple.idl
:
$ xcomidl -I ~/idl Simple.idl
This generates two files Simple.hpp
and
SimpleTie.hpp
respectively. The first file is common to
both component implementors and component users. The second file is used
only by the component implementor.
The following listing gives a complete implementation for our component:
#include <xcom/ImplHelper.hpp> #include <xcom/ExcImpl.inc> #include <iostream> #include "SimpleTie.hpp" namespace { struct HelloImpl : public xcom::Supports<HelloImpl, simple::IHello>, public xcom::RefCounted<HelloImpl> { void sayTo(xcom::Char const* name) { std::cout << "hello " << name << '\n'; } }; struct DLLAccess : public xcom::DLLAccessBase { DLLAccess() { classes.push_back("MyHelloImpl"); xcom::TypeDesc<simple::IHello>::addSelf(types); addInterface("IHello"); } xcom::IUnknown dllCreateObject(xcom::Char const* cname) { if(strcmp(cname, "MyHelloImpl") == 0) { return new HelloImpl; } return 0; } }; } XCOM_PROVIDE_DLL_ACCESS(DLLAccess)
The ImplHelper.hpp
header provides support classes for
implementing the addRef
, release
and
queryInterface
methods.
Here the real component implementation is the HelloImpl
class.
The Supports
base class implements the inherited
queryInterface
method and the RefCounted
class
provides implementations for the addRef
and release
methods. To implement an interface in a component all methods given in
the interface must be implemented. As the only method in the IHello
interface is sayTo
, only it's implementation is given.
The remaining DLLAccess
class and the macro invocation
at the end provides the implementations of required functions that
all XCOM component shared libraries must export. For the implementor,
he must add the names of classes he wants to create to a list. Any
number of classes can be specified as long as the
dllCreateObject
function creates instances when any of
the registered names are given. The second line in the constructor
adds metadata descriptions of the used interfaces in the component
to an internal list. The third line registers the name of the given
interface to an internal list. The last two functions are used to
provide metadata outside world.
You can compile the component to a shared library using the following commands:
$ gcc -shared -Wl,-Bsymbolic -Wall -O0 -g -o comp.so -I ~/include Component.cpp -L ~/lib -lxcom
You may have to adjust the -I and -L switches in case the location of XCOM headers and libraries are different.
In this part we'll develop a program that uses our simple component. Before
that a little explanation about how XCOM locates components given their
name. At program startup XCOM searches for a whose name is .config
suffix appended to executable's name. This file must contain a directory
name per line. XCOM adds these directories to an internal search list.
When a request is made to load a component given its name, XCOM searches
these directories for a file with the same name as component and a
suffix depending on the platform. Given the file is found, it is loaded and
a handle to it returned in the form of xcom.ILibrary
interface.
Here is the sample application that uses our component:
#include "Simple.hpp" #include <xcom/Loader.hpp> int main() { xcom::loadAsBuiltin("comp"); simple::IHello hello(xcom::createObjectAs<simple::IHello>("MyHelloImpl")); hello.sayTo("ali"); hello.sayTo("veli"); }
The first include header, is the generated one which contains
the class generated for simple.IHello
interface.
The second header is a standard XCOM header which provides component loading
functions.
The first line in main loads the component named comp and
make the classes in it available for instantiating. The
createObjectAs
template function takes an interface
as template parameter and a string as first argument, which tries
to instantiate given the class name by searching the loaded libraries.
If it founds a match, creates an instance and casts to the given
interface using IUnknown.queryInterface
method behind the
scenes. After we have an interface to a component at hand we can easily
call the functions through.
You can compile the client program using the following command:
$ gcc -o client -I ~/xcom/include Main.cpp -L ~/lib -lxcom
When you run the program directly you should get an Aborted
message even you have compiled the component before as XCOM does not
know where it can find the library. You can show where is the component
by creating a file named client.config
in the same
directory as the client
executable and putting the directory
in which our component resides. After that step you can run the program
and see the following messages:
hello ali hello veli
This tutorial just scratched surface of XCOM by creating a very simple component and a client. The idl compiler of the XCOM can be examined for a more complex program as it is also component based.