iCOM "Hello World" component

Files [hello_world.idl greeter.cc]

As with all programming examples, we will begin with the obligitory hello world...

 

Step 1 - Create IDL


The IDL file is a description of all the types that are going to be used by your project. All iCOM interfaces must be described in IDL, unlike COM where this is optional. Some languages (e.g. Python) require this information to use the interface, without it they fail.

Here is the IDL file for our example:

hello_world.idl

line 1:
line 2: #pragma prefix hello_world "{c1d3ccdf-6470-4866-964f-a75e28539536}"
line 3:
line 4: module hello_world
line 5: {
line 6: interface iGreeting
line 7: {
line 8: void greet();
line 9: };
line 10: };

Here is a line-by-line analysis:

line 2: #pragma prefix hello_world "{c1d3ccdf-6470-4866-964f-a75e28539536}"

To explain this line a little background is first needed. All user defined types (UDT's) must be uniquely identified; this goes for all component/distributed architectures, including COM and CORBA. This allow the marshalling systems to reliably locate a defintion of the type, and to transform it into another format. To define a type we use something called a "type-id" (repository id in CORBA speak). The type-ids are created by the IDL compiler, but to create type-ids that are unique to your project you need something "unique" in the type-id. iCOM requires a "universally unique identifer" or UUID, which can be created using the uuidgen program available on most computers.

The prefix pragma is what we use to tell the IDL compiler what prefix we want to use, it is required, and must be the first line in your IDL file (blanks lines and comments do not count). Here we are using the uuid of "{c1d3ccdf-6470-4866-964f-a75e28539536}" (notice the brackets, they are also required), this will produce type-ids like this "c1d3ccdf-6470-4866-964f-a75e28539536/hello_world/iGreeting". You should also notice the hello_world identifier after the prefix token. This instructs the IDL compiler to generate a string constant named hello_world_LibId, so you do not have to remember or constantly copy and paste this id in your source code, you will see how this is used later.

line 4: module hello_world

Here we are opening a new module, this will turn into a namespace in C++, and a module in Python. It is suggested (but may soon be enforced), that all definitions are in a module. This makes it easier to use IDL, from other software vendors.

line 6: interface iGreeting

Now we define a new interface

line 8: void greet(in string who)

Now we define a new method on the iGreeting interface, called greet. The method has one parameter, a string, the name of the person to greet, and returns nothing. Notice the "in" before the parameter type, this designates the direction the parameter will be passed, either in, out, or inout.

That's the meat of it. Everything else is just syntax.

 

Step 2 - Compiling the IDL


As stated before, the IDL file is a description file, not much good to you when you are creating programs in C++, or Python. Now you need to take that description and create a usable set of definitions in your language of choice; in this example we are going to use C++. To create the C++ header files that define your IDL types run the iCOM IDL compiler, iidlc, against the IDL. For example:

		iidlc -bcxx hello_world.idl

Notice the option, "-bcxx", this tells the IDL compiler which language to generate for. This command will create a header file call hello_world.h in the current directory. This is what we will use to create our component.

 

Step 3 - Registering the IDL


Dynamic languages such as Python rely on the definitions straight from IDL, to do marshalling. So if you want to access this interface from Python you will need to create and register a type library. A type library is a pre-parsed form of the IDL file, and is therefore not (easily) readable by humans, but it is much faster for the computer. To create a type library, we use the IDL compiler again with a different generation option:

		iidlc -btypelib hello_world.idl

This will create a file called hello_world.itl in the current directory. Now we need to register this in the iCOM registry so that, iCOM knows where to find the definition of our interface hello_world::iGreeting. To do this we use the registration program, iregister:

		iregister -t hello_world.itl

The "-t" option tells iregister you are registering a type library and not a component.

 

Step 4 - Create the component


Here is the C++ implementation of our component:

file: greeter.cc

line 1: #include "hello_world.hh"
line 2: #include "icomutil.hh"
line 3
line 4: namespace hello_world
line 5: {
line 6: class Greeter
line 7: : public icom::ComponentBase<Greeter, InterfaceList1(iGreeting)>
line 8: {
line 9: public:
line 10: void greet(const icom::String& who)
line 11: {
line 12: wprintf(L"Hello %ls\n", who.c_str());
line 13: }
line 14: };
line 15: }
line 16:
line 17: icomComponentMapBegin
line 18: icomComponent(hello_world::Greeter,icomMakeId(hello_world,"Greeter"),1)
line 19: icomComponentMapEnd
line 20:
line 21: icomDeclareModule

Line by line here are the major parts.

line 1: #include "hello_world.h"

This line includes the IDL generated declarations for our types. The generated header file already includes the necessary icom header file.

line 2: #include "icomutil.hh"

C++ has icomutil.hh, these are template utilities for creating iCOM components. You can create components by hand (without them), but it is a lot more cumbersome. If your compiler does not compile these templates, you will have to create components without them. That will have to covered in another tuorial.

line 4: namespace hello_world

The IDL definitions we created we in a module hello_world, IDL modules are mapped to C++ namespaces. To make it easier for us, less typing, you generally want to define your components inside the same namespace. Otherwise you would have to fully qualify every type name (e.g. "hello_world::iGreeting").

line 6: class Greeter

Create a class to implement your component. Remember the power of components comes from being able to implement many different interfaces, and along with that many components may implement the same interface. So your component name should probably reflect the component as a whole and not any one interface it implements.

line 7: public icom::ComponentBase<Greeter, InterfaceList1(iGreeting)>

This is the first taste of the component utilities we included in line 2. Here deriving from ComponentBase, it implements all the basic iCOM administrivia about our component, leaving us to only implement it's functionality. The ComponentBase class is a template class taking two template parameters. The first is the C++ type name of the component you are implementing. The second is a list of interfaces your component implements. In our example we only implement a single interface, so we use the InterfaceList1 macro to generate template list of length one, if you implemented three you would use InterfaceList3. Use of the InterfaceList* macros is highly recommended, because the templates they hide are just short of incomprehensible.

line 10: void greet(const icom::String& who)

Here we are defining the method we want to implement from our interface. The parameter it takes is an "in string who", all "in" parameters are passed by value, or by a const-reference.

line 12: wprintf(L"Hello %ls\n", who.c_str());

Now we implement the functionality of the method we defined. This is a component, so it can do anything that meets the contract. If the contract of this method is to greet "who", then any acceptable greeting would do. We simply print "Hello <WHO>" on the command line, but a dialog box, or a sound clip, or an email to a pilot instructing him to fly a banner over "who", would all do nicely.

line 17-21:

These I believe need to be explained together. Every component, needs a package. This package must be available to be loaded on demand, when the program needs it. Obviously the most common package for binary components is going to be a shared library, and that is what lines 17-21 do. The icomComponentMapBegin/End pair define ALL the components in this package, and you can only have ONE component map per package. Line 21 defines the package to be a shared library. There are other packages, and each will have their own definition method. For example, Python scripts are themselves the package. A utility loader creates components from the scripts (all hidden to you of course), therefore the definition of the package is different for Python components. The macros you see here are the C++ way. The only macro that really needs explaining is the icomComponent macro, it must go inside the icomComponentMapBegin/End pair, and its arguments are the C++ type name of your component, the iCOM component-id, and a component version. The iCOM component-id is a text string that is equivalent to a type-id, except it identifies a component. The easiest way to make this is to use the icomMakeId macro, with the name of the module your component should be defined in, and the name of your component. Remember from Step 1 - line 2, that we the IDL compiler created a constant names hello_world_LibId, and that is what iCOM make id uses as the base for your component-id. Here we use hello_world, and "Greeter", this creates a component-id of "c1d3ccdf-6470-4866-964f-a75e28539536/Greeter". Now your component is uniquely identifiable, for when you want to create one.

 

Step 5 - Compiling Component


Using your C++ compiler, compile the greeter.cc file into a shared library:

		g++ -shared -fPIC -o greeter.so -pthread -I/usr/local/include/icom -L/usr/local/lib greeter.cc -licomcxx

A couple of caveats:

 

Step 6 - Register the component


Using iregister, register the component:

		iregister greeter.so

 

 

You are done, you have built your first iCOM component.

If you actually want to run it, follow the client tutorial to create a program that uses you component.