IDL and its C mapping

The IDL compiler: orbit-idl

In the above section, we have seen the strength of IDL to describe objects, allowing them to be accessed from any language. IDL encapsulates all the necessary information about an object. Thus, any language can build a representation of these objects and access them just like a native object. The native representation of a CORBA-object into some native object is called a language-mapping.

A number of language mappings are part of the CORBA standard. Among the existing mappings are C, C++, Java, Smalltalk and COBOL. The C++- and Java-mappings are the most common in literature and practice. The C mapping is not widely used or explained in books dealing with CORBA. GNOME is almost entirely written in C which means that we will only look at the C mapping.

ORBit has an idl compiler which automatically generates the C headers declaring the functions which correspond to the objects' IDL definitions (it follows the standardized mapping). Its use is: orbit-idl file.idl. If you had the apple.idl-file, running the idl compiler will generate apple.h, a C header containing all the function and/or type declarations corresponding to the C-mapping. It will also generate apple-common.c, apple-stub.c and apple-skels.c. These files must be compiled and linked against the client and server to provide the default stubs and skeletons.

IDL C-mapping basics: methods and attributes

There are no 'real' objects in the C-language, but the C-mapping uses rules similar to those in GTK+, GNOME and Motif where Objects underly the C code. Here are the simplest rules:

Objects' methods must be fully qualified. ie: the C name of the eat_me-method of the Apple-interface of the FruitsBasket-module is FruitsBasket_Apple_eat_me. The IDL standard discourages the use of method names with underscores ('_'), and encourages the use of mixed case. The standard would prefer EatMe instead of eat_me, so that we have FruitsBasket_Apple_EatMe. That way, name-clashes are prevented (e.g.:FruitsBasket::Apple_eat::me vs.FruitsBasket::Apple::eat_me), and module/interface/method are easily identified. However, I noticed the standard does not follow this rule very strictly itself.

For example, our Apple interface would translate into the following C declaration:

module FruitsBasket {
        interface Apple {
                void eat_me (in boolean eat_yes_or_not );
        };
};
      

is mapped to :

typedef CORBA_Object FruitsBasket_Apple;
void FruitsBasket_Apple_eat_me (Fruits_Apple object, 
                                CORBA_boolean eat_yes_or_not, 
                                CORBA_Environment *ev);

All method-calls need an object reference as first parameter and a CORBA_Environment-object as last parameter. Object References are of the CORBA_Object-type which is an opaque type (i.e.: you cannot access any of its fields or anything directly). In fact, the CORBA_Object type is a pointer to a struct which is dependent on the CORBA implementation you are using.

Operation specific arguments are passed between the CORBA_Object and the CORBA_Environment parameters. Operations specific arguments should be passed as pointers if used as out or inout parameters. If structs, strings or arrays are used as in-parameters, pointers must be passed (a pointer to the first element of the array and to the first element of the string). Return arguments are used exactly the same way: they will be pointers for structures, unions, strings, sequences and arrays. More details on this lies on the CORBA specifications.

The mapping of attributes is special: following good OOP practice, objects' attributes may not be accessed directly: two functions are defined: a _get-function and a _set-function. For example, the following two definitions are strictly equivalent:

module FruitsBasket {
        interface Apple {
	        attribute boolean is_eaten;
        };
};

// The following is theoretically equivalent to the first 
// definition but is illegal as IDL forbids the use of 
// names preceded by an underscore.
module FruitsBasket {
        interface Apple {
                boolean _get_is_eaten (void);
                void _set_is_eaten ( in boolean b );
        };
};
	

Consequently, the following C code will allow you to access this interface's is_eaten attribute.

CORBA_Object app;
CORBA_Environment *ev;
CORBA_boolean bool;

/* here is some code to link app to the real 
 * running server object
 */

/* please, note the double underscore. One is due to the 
 * full scoped name and the other one is due to the method
 * name 
 */
 
FruitsBasket_Apple__set_is_eaten (app, TRUE, ev);
bool = FruitsBasket_Apple__get_is_eaten (app, ev);
	

Mapping of IDL-types

Constants defined with the const keyword are #define'd:

const long myconstant = 43;
	

is mapped to :

#define my_constant 43
	

Basic CORBA-types are mapped using the following table:

Table 1-2. Basic CORBA types

IDL TypeC type mapping
shortCORBA_short
unsigned shortCORBA_unsigned_short
longCORBA_long
unsigned longCORBA_unsigned_long
long longCORBA_long_long
unsigned long longCORBA_unsigned_long_long
floatCORBA_float
doubleCORBA_double
long doubleCORBA_long_double
booleanCORBA_boolean
charCORBA_char
wcharCORBA_wchar

CORBA_boolean is mapped to C unsigned char and enums are #define'd.

All other types (struct, sequence, string, wstring) are variable-length types and are specially handled. If a function returns one of these variable type, the caller must free the allocated memory with CORBA_free(), which will take care of freeing this memory.

structs are straight C-structs

The following sequence would be mapped as follows:

sequence <char,10> my_var;
      

is mapped to:

typedef struct {
        CORBA_unsigned_long _maximum;
        CORBA_unsigned_long _lenght;
        CORBA_char *_buffer;
} CORBA_sequence_char;

typedef CORBA_sequence_char my_var
	

The C-structure is self-explanatory: _maximum is used to store the maximum number of elements in the sequence. _lenght maintains the effective number of elements and _buffer is a pointer to the C array containing the data.

Sequence names are as follows: CORBA_sequence_anytype where anytype is the sequence specified type.

When passing such a structure as an in-parameter, you should take care of setting the proper values for all fields and make sure proper memory-allocation is performed. If used as an out-parameter, you will need to take care only of the deallocation by using the CORBA_free function. Finally, if using an inout-parameter, you must make sure that _maximum, _lenght and _buffer are correctly set (ie: like for the in parameter). Upon return, _lenght will hold the number of returned elements and _buffer memory may be freed using CORBA_free. It is recommended that _buffer memory is allocated with CORBA_sequence_*_allocbuf. This is because if the parameter is inout, it may be modified by the callee, and thus its allocated memory may change. Sample code:

// here is the interface we wish to access:
module my {
        interface seq {
                void SetThing ( in sequence <long long, 20> );
                void GetThing ( out sequence <long long, 20> );
                void SendReceiveThing ( inout sequence <long long, 20> );
        };
};
	
/* here is the C code to call the different methods */

CORBA_Object obj; 
CORBA_Environment ev;
CORBA_sequence_long_long *seq_one;
CORBA_sequence_long_long seq; 

/* code to link to the real object server */
my_seq_GetThing (obj, seq_one, &ev);

/* here, seq_one is correctly assigned */
/* get rid of the memory allocated to store 
 * temporarily the object sequence */
CORBA_free (seq_one); 


seq._maximum = 20;
seq._lenght = 2;
seq._buffer = CORBA_sequence_long_long_allocbuf (2);
seq._buffer = { 1, 1 };
my_seq_SetThing (obj, &seq, &ev);

my_seq_SendReceiveThing (obj, &seq, &ev);
/* here, _buffer may be anything: it may be 
 * memory not allocated by us because
 * the callee will CORBA_free our _buffer and 
 * reallocate a new buffer is its size
 * is not big enough
 */
 CORBA_free (seq._buffer);
	

To get a better understanding of how these sequences are working, you should read the CORBA standard. Interesting stuff can be found there (but nothing really vital lies there). This should be done upon a second reading of this document.

Strings are simple C CORBA_char arrays which should be allocated using CORBA_string_allocate. Their use is similar to that of sequences. Here is some sample code:


// here is the interface we wish to access:
module my {
        interface str {
                void SetThing ( in string my_str );
                void GetThing ( out string my_str );
                void SendReceiveThing ( inout string my_str );
        };
};
	
/* here is the C code to call the different methods */

CORBA_Object obj; 
CORBA_Environment ev;
CORBA_char *str;

/* code to link to the real object server */

my_str_GetThing (obj, &str, &ev);
/* here, str is correctly assigned */
/* get rid of the memory allocated to 
 * store temporarily the object sequence */
CORBA_free (str); 

str = CORBA_string_alloc (5);

my_str_SetThing (obj, str, &ev);

my_str_SendReceiveThing (obj, &str, &ev);
/* here, str may be anything: it may be 
 * memory not allocated by us because
 * the callee will CORBA_free our str and 
 * reallocate a new buffer if its size
 * is not big enough
 */
CORBA_free (str);
	

Again, to better understand their use and tune the allocation/deallocation process, please refer to the CORBA standard. If 'strange' things happen while passing or getting strings to/from methods, you should definitely read this stuff. Note that the examples may be simplifications of code you would actually use (for example: there's little error handling).

Exception handling

Exception handling in C will be explained later: it is a little complex because C has no built-in mechanism to handle exceptions, like C++ or Java.