Crash Course in SIDL C++ Bindings

Gary Kumfert <kumfert@llnl.gov>
BABEL Version 0.8.x
06 Sep 2001, updated 21 Jan 2003

Introduction

The intent of this file is to provide the minimum information necessary for people who are familiar with object-oriented/component oriented software development and the SIDL (Scientific Interface Definition Language) to implement classes in C++ or use classes implemented by someone else from a C++ driver. If you are unfamiliar with SIDL, additional material is available from http://www.llnl.gov/CASC/components/.

The assumption for this document is that you already have a SIDL file for a software library, and you need to call it from C++ or implement it in C++.

Installation

Unlike C or FORTRAN 77, there is no runtime library created for a particular C++ compiler at installation. Instead, when you generate C++ from SIDL, you will find Stubs (aka proxy classes) generated for SIDL base classes and will have to compile and link them into your application.

That said, if you switch to a different compiler after installation, there may be some values set in babel_config.h that become invalid. This can be overcome by copying the headerfile, making the necessary changes, and placing the modified headerfile earlier in the include path than the original one.

SIDL C++ Header Suffix

The first thing that C++ users will notice is that C++ headers have a ".hh" suffix to distinguish them from C's ".h" suffix. This convention was born out of necessity to distinguish both differing headerfiles and their include guards.

SIDL's Main C++ Header File

All C++ code generated by Babel #includes a file called "SIDL_cxx.hh". This file includes babel_config.h, the C header file that defines configuration information. SIDL_cxx.hh also puts things like std::string and std::complex into the global namespace. Finally, SIDL_cxx.hh defines some C++ classes in the SIDL namespace that

Basic Information about types

The basic types in SIDL are mapped into C++ according to the following table:
 
SIDL TYPE C++ TYPE NOTES
int int32_t
long int64_t
float float
double double
bool bool
char char
string std::string
fcomplex sidl::fcomplex
dcomplex sidl::dcomplex
enum enum
opaque sidl::opaque
interface class
class class
array sidl::array (template specialization)

Calling methods from C++

Since C++ is an object-oriented language, there is a lot less programmer overhead in using SIDL from the C++ perspective than from non-OO languages such as C or FORTRAN 77. Here's a table summarizing how SIDL features are mapped to C++.
SIDL Feature C++ Implementation
packages C++ namespaces (no name transformations)
version numbers ignored
interface C++ class, (called "stub", serves as a proxy to the implementation)
class C++ class, (called "stub", serves as a proxy to the implementation)
methods C++ member functions, no name mangling
NOTE: member functions beginning with a leading underscore "_" may be Babel internals, or specific to C++ binding.
static methods static C++ member functions, no name mangling, even works for dynamically loaded objects
exceptions thrown and caught using C++ exception handling
reference counting SIDL C++ stubs can be treated as smart-pointers.
Constructors, destructors, and operators are overloaded so that explicit calls to addRef() or deleteRef() are rarely needed.
casting
  • Assignment operators are overloaded to handle safe casting up and down the inheritance heirarchy
  • User should never call dynamic_cast<>() on a SIDL object... the stubs inheritance heirarchy does not follow the SIDL inheritance heirarchy. 
  • Attempted downcasts using assignment should be checked by a call to (is_nil(), or not_nil()). 
instance creation Use static member function "_create". The default constructor for a C++ stub creates the equivalent of a NULL pointer. Works only with non-abstract classes. 

These proxy classes (we call "stubs") serve as the firewall between the application in C++ and Babel's internal workings. As one would expect, the proxy classes maintain minimal state so that (unlike C or F77) there is no special context argument added to non-static member functions.

Here are example using standard classes:

       SIDL::BaseClass object = SIDL::BaseClass::_create();
       SIDL::BaseInterface interface = object;
Here is an example call to the addSearchPath in the SIDL.Loader class:
      std::string s('/try/looking/here');
      SIDL::Loader::addSearchPath( s );
Here is another example adapted from the BABEL regression tests. Package ExceptionTest has a class named Fib with a method declared in SIDL as follows:
    int getFib(in int n, in int max_depth, in int max_value, in int depth)
    throws NegativeValueException, FibException;
Here is the outline of a C++ code fragment to use this method.
       ExceptionTest::Fib fib = ExceptionTest::Fib::_create();
       try { 
         int result = fib.getFib( 4, 100, 32000, 0 );
         cout << "Result of fib.getFib() = " << result << endl;
       } catch ( ExceptionTest::NegativeValueException  e ) { 
         // ...
       } catch ( ExceptionTest::FibException e ) { 
         // ...
       }
Here is how you should invoke BABEL to create the C++ stubs from a SIDL file.
      babel --client=C++ file.sidl
or simply
      babel -cC++ file.sidl
This will create a babel.make file, some C headers and sources, and many C++ headers and sources. Files ending in ".c" or ".h" are in C, files ending in ".cc" or ".hh" are C++.

You will need to compile and link the files together to use the C++ stubs.

Implementing SIDL Classes in C++

Much of the information from the previous section is pertinent to implementing a SIDL class in C++. The types of the arguments are as indicated in the type table. Your implementation can call other SIDL methods, in which case follow the rules for client calls.

To create the implementation, you must first have a valid SIDL file, then invoke Babel as follows:

       babel --server=C++ file.sidl
or use the shorter arguments
       babel -sC++ file.sidl
This will create a makefile fragment called babel.make, several C headers and source files, and numerous C++ header and source files. To create a working implementation, the only files that need to be hand-edited are the C++ "Impl" files (header and source files that end in _Impl.hh or _Impl.cc). All your additions to this file should be made between code splicer pairs. Code splicing is a technique Babel uses to preserve hand-edited code between multiple invoactions of Babel. This allows a developer to refine their SIDL file without ruining all their previous implementations. Code between splicer pairs will be retained by subsequent invocations of BABEL; code outside splicer pairs is not.

Here is an example of a code splicer pair in C++. In this example, you would replace the line "// Insert code here... " with your implementation.

void MyPackage::MyClass::myMethod() {
  // DO-NOT-DELETE splicer.begin(MyPackage.MyClass.myMethod)
  // Insert code here...
  // DO-NOT-DELETE splicer.end(MyPackage.MyClass.myMethod)
}
It is important to understand where and why splicer blocks occur. Splicer blocks appear at the beginning and end of each Impl header and source file; for developers to add #includes and other miscellaneous items respectively. In the headers, there is a splicer block inside the class definition for developers to add any data members to the class that they wish. There is not currently a splicer block that allows a user to make the impl class inherit from some other class. This may change in the future, but current schools of thought hold that best practice is to use delegation instead of inheritance if developers want to hook SIDL generated impl classes to some existing C++ class library. In the source files, splicer blocks appear in each method implementation. There are two implicit methods (i.e. methods that did not appear in the SIDL file) that must also be implemented. The _ctor method is a constructor function that is run whenever an object is created. The _dtor method is a destructor function that is run whenever an object is destroyed. If the object has no state, these functions are typically empty.

Accessing SIDL Arrays from C++

Although it would be feasible to expose the underlying C array API to create, destroy and access array elements and meta-data, the C++ bindings provide a sidl::array<T> template mechanism that is more in keeping with C++ idioms.

For SIDL built-in types, template specializations of sidl::array<T> are defined in SIDL_cxx.hh. For SIDL interface and classes, the array template is again specialized in the corresponding stub header. The reason for the extensive use of template specialization is an effort to hide the detail that the array implementation is really templated on three terms: the type of the C struct that represents the array internally, the internal representation of each item in the array, and the C++ representation of each item in the array. (See array_mixin in SIDL_cxx.hh for grungy implementation details.)

An example is given below.

        int32_t len = 10; // array length=10
        int32_t dim = 1;  // one dimensional
        int32_t lower[1] = {0}; // zero offset
        int32_t upper[1] = {len-1};
        int32_t prime = nextPrime(0);

        // create a SIDL array of primes.
        SIDL::array<int> a;
        a.create(dim,lower,upper); 
        for( int i=0; i<len; ++i ) { 
           prime = nextPrime( prime );
           a.set(v,i);
        }
Of course, the example above is only one way to create an array. The list of member functions for all C++ array classes is:
        // constructors
        array ( array_ior_t * src ); // internal
        array () ;                   // empty

        // destructor
        ~array() ;

        // creation 
        bool create( int32_t dimen, const int32_t lower[], 
                const int32_t upper[]);

        bool borrow( item_ior_t * first_element, int32_t dimen,
                const int32_t lower[], const int32_t upper[], 
                const int32_t stride[]);

        void destroy();

        // get/set
        item_cxx_wrapper_t get(int32_t i, int32_t j=0, 
                int32_t k=0, int32_t l=0);

        void set(item_cxx_wrapper_t element, int32_t i, int32_t j=0, 
                int32_t k=0, int32_t l=0);

        // other accessors
        int32_t dim() const;

        int32_t lower( int32_t dim ) const;

        int32_t upper( int32_t dim ) const;

        bool is_nil() const;

        bool not_nil() const;

        // get a const pointer to the actual array ior 
        const array_ior_t* _get_ior() const { return d_array; }

        // get a non-const pointer to the actual array ior
        array_ior_t* _get_ior() { return d_array;}
where