XCOM

Home Install Tutorial Download
SourceForge.net Logo

XCOM Tutorial

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 IDL - XCOM Interface Definition Language

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.

Generating Headers

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.

Component Implementation

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.

Using Components

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

Conclusion

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.