Crash Course in SIDL FORTRAN 77 Bindings

Tom Epperly <tepperly@llnl.gov>
BABEL Version 0.8.x
17 July 2001, updated 21 January 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 FORTRAN 77 or use classes implemented by someone else from a FORTRAN 77 driver. If you are unfamiliar with SIDL, I suggest seeking additional material 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 FORTRAN 77 or implement it in FORTRAN 77.

Installation

During the installation of BABEL, it makes a runtime library for a particular FORTRAN 77 compiler. You must use the same FORTRAN 77 compiler that was used for the BABEL installation. If you're forced to work with a number of FORTRAN 77 compilers, you will need a separate installation of BABEL for each FORTRAN 77 compiler.
 

Basic Information about types

The basic types in SIDL are mapped into FORTRAN 77 according to the following table:
SIDL TYPE FORTRAN 77 TYPE
int INTEGER*4
long INTEGER*8
float REAL
double DOUBLE PRECISION
bool LOGICAL
char CHARACTER*1
string CHARACTER*(*)
fcomplex COMPLEX
dcomplex DOUBLE COMPLEX
enum INTEGER
opaque INTEGER*8
interface INTEGER*8
class INTEGER*8
array INTEGER*8

For pointer types, such as opaque, interface, class, and array, a 64-bit integer is used, so FORTRAN 77 code will be portable between systems with a 32 bit address space and systems with a 64 bit address space. On a 32 bit system, the upper 32 bits of these quantities are ignored. Systems with more than 64-bit pointers aren't currently supported.

Generally, clients should treat opaque, interface, class and array values as black boxes. However, there is one value that is special. A value of zero for any of these quantities indicates that the value does not refer to an object. Zero is the FORTRAN 77 equivalent of NULL . Any nonzero value is or should be a valid object reference. Developers writing in FORTRAN 77 should initiailize values to be passed as in or inout parameters to zero or a valid object reference.

When mapping the SIDL string type into FORTRAN 77, some capability was sacrificed to make it possible to use normal looking FORTRAN 77 string handling. One difference is that all FORTRAN 77 strings have a limited fixed size. When implementing a subroutine with an out parameter, the size of the string is limited to 1024 characters.

Enumerated types are just integer values. The constants are defined in an includable file assuming your FORTRAN 77 compiler supports some form of including.
 

Calling methods from FORTRAN 77

All SIDL methods are implemented as FORTRAN 77 subroutines regardless of whether they have a return value or not. For object methods, the object or interface pointer is passed as the first argument to the subroutine before all the formally declared arguments. For static methods, the formally declared arguments appear first.

When a method has a return value, a variable to hold the return value should be passed as an argument following the formally declared arguments. When a method can throw an exception (i.e., its SIDL definition has a throws clause), a variable of type INTEGER*8 should be passed to hold a SIDL.BaseException pointer if an exception is thrown. After the call, the client should test this argument. If it's non-zero, an exception was thrown by the method, and the method should respond appropriately. When an exception is thrown, the value of all other arguments should be ignored.

The name of the subroutine that FORTRAN 77 clients should call is derived from the fully qualified name of the class and the name of the method. To determine the name, take the fully qualified name, replace all the periods with underscores, append an underscore, append the method name and then append "_f".

For example, to call the deleteRef method on a SIDL.BaseInterface interface, you would write:

       integer*8 interface1, interface2
       logical areSame
C      code to initialize interface1 & interface 2 here
       call SIDL_BaseInterface_deleteRef_f(interface1)
To call the isSame method on a SIDL.BaseInterface , you would write:
       call SIDL_BaseInterface_isSame_f(interface1, interface2, areSame)
To call the queryInt method on a SIDL.BaseInterface, you would write:
       call SIDL_BaseInterface_queryInt_f(interface1, 'My.Interface.Name', interface2)
For interfaces and classes, there is an implicit method called _cast . Its return type is opaque, and it has one formal argument, a string in addition to the implicit object/interface reference. The _cast method attempts to cast the object/interface to the named type. It is similar to the queryInt method in SIDL.BaseInterface except it does not increment the reference count of the return object or interface, and it may return an object or an interface pointer. The queryInt method always returns an interface pointer.

For non-abstract classes, there is an implicit method called _create . It creates and returns an instance of the class.

Here are examples of the use of these two methods:

       integer*8 object, interface
       call SIDL_BaseClass__create_f(object)
       call SIDL_BaseClass__cast_f(object, 'SIDL.BaseInterface', interface)
Please note the presence of two underscores between BaseClass and create and between BaseClass and cast ; the extra underscore is there because the first character of the method name is an underscore.

Here is an example call to the addSearchPath in the SIDL.Loader class:
call SIDL_Loader_addSearchPath_f('/try/looking/here')

Your FORTRAN 77 must manage any object references created by the calls you make.

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 FORTRAN 77 code fragment to use this method.
 
       integer*8 fib, except, except2
       integer*4 index, maxdepth, maxval, depth, result
       call ExceptionTest_Fib__create_f(fib)
       index = 4
       maxdepth = 100
       maxvalue = 32000
       depth = 0
       call ExceptionTest_getFib_f(fib, index, maxdepth,
     $       maxvalue, depth, result, except)
       if (except .ne. 0) then
         call SIDL_BaseException__cast_f(except, 
     $       'ExceptionTest.FibException', except2)
         if (except2 .ne. 0) then
c           do something here with the FibException
         else
           call SIDL_BaseException__cast_f(exception,
     $        'ExceptionTest.NegativeValueException',
     $         except2)
c          do something here with the NegativeValueException
         endif
         call SIDL_BaseException_deleteRef_f(except)
       else
         write (*,*) 'getFib for ', index, ' returned ', result
       endif
       call ExceptionTest_Fib_deleteRef_f(fib)
Here is how you should invoke BABEL to create the FORTRAN 77 stubs for an IDL file.
babel --server=f77 file.sidl
This will create a babel.make file, numerous C headers, numerous C source files, and some FORTRAN 77 files. The files ending in _fStub.c are the FORTRAN 77 stubs that allow FORTRAN 77 to call a SIDL method. As a client, you should ignore and/or delete files ending in _Impl.f and _fSkel.c. These files are used if you're implementing an object in FORTRAN 77.

You will need to compile and link the files ending in _fStub.c into your application (i.e. STUBSRCS in babel.make). Normally, the IOR files (_IOR.c) are linked together with the implementation file, so you probably don't need to compile them.

If you have some enum's defined in your SIDL file, BABEL will generate FORTRAN 77 include files in the style of DEC FORTRAN (Compaq FORTRAN?) %INCLUDE. These files are named by taking the fully qualified name of the enum, changing the periods to underscores, and appending .inc . Here is an example of a generated include file.
 

C       File:          enums_car.inc
C       Symbol:        enums.car-v1.0
C       Symbol Type:   enumeration
C       Babel Version: 0.8.2
C       Description:   Automatically generated; changes will be lost
C      
C       babel-version = 0.8.2
C       source-line   = 25
C      
        integer porsche
        parameter (porsche = 911)
        integer ford
        parameter (ford = 150)
        integer mercedes
        parameter (mercedes = 550)

Implementing Classes in FORTRAN 77

Much of the information from the previous section is pertinent to implementing a SIDL class in FORTRAN 77. 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.

You should invoke BABEL:

babel --server=f77 file.sidl
This will create a babel.make, numerous C headers, numerous C source files and some FORTRAN 77 source files. Your job, should you choose to accept it, is to fill in the FORTRAN 77 source files with the implementation of the methods. The files you need to edit all end with _Impl.f . All your changes to the file should be made between code splicer pairs. 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 this example, you would replace the line "C Insert extra code here... " with your lines of code.
 
C       DO-NOT-DELETE splicer.begin(_miscellaneous_code_start)
C       Insert extra code here...
C       DO-NOT-DELETE splicer.end(_miscellaneous_code_start)
Each _Impl.f file contains numerous empty subroutines. Each subroutine that you must implement is partially implemented. The SUBROUTINE statement is written, and the types of the arguments have been declared. You must provide the body of each subroutine that implements the expected behavior of the method.

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.

The SIDL IOR keeps a pointer (i.e. C void *) for each object that is intended to hold a pointer to the object's internal data. The FORTRAN 77 skeleton provides two functions that the FORTRAN 77 developer will need to use to access the private pointer. The name of the function is derived from the fully qualified type name as follows. Replace periods with underscores and append __get_data_f or __set_data_f. The first argument is the object pointer (i.e. self), and the second argument is an opaque . These arguments are 64 bit integers in FORTRAN 77, but the number of bits stored by the IOR is determined by the sizeof(void *).

BABEL/SIDL does not provide a mechanism for FORTRAN 77 to allocate memory to use for the private data pointer.

Accessing SIDL Arrays from FORTRAN 77

The normal SIDL C function API is available from FORTRAN 77 to create, destroy and access array elements and meta-data. The function name from FORTRAN has _f appended.

For SIDL types dcomplex, double, fcomplex , float, int and long, SIDL provides a method to get direct access to the array elements. For the other types, you must use the functional API to access array elements.

For type X, there is a FORTRAN 77 function called SIDL_X__array_access_f to provide a method to get direct access. An example is given below. Of course, this will not work if your FORTRAN 77 compiler does array bounds checking.
 

        integer*4 lower(1), upper(1), stride(1), i, index(1)
        integer*4 value, refindex, refarray(1), modval
        integer*8 nextprime, tmp
        lower(1) = 0
        value = 0
        upper(1) = len - 1
        call SIDL_int__array_create_f(1, lower, upper, retval)
        call SIDL_int__array_access_f(retval, refarray, lower,
     $       upper, stride, refindex)
        do i = 0, len - 1
           tmp = value
           value = nextprime(tmp)
           modval = mod(i, 3)
           if (modval .eq. 0) then
              call SIDL_int__array_set1_f(retval, i, value)
           else
              if (modval .eq. 1) then
                 index(1) = i
                 call SIDL_int__array_set_f(retval, index, value)
              else
C this is equivalent to the SIDL_int__array_set_f(retval, index, value)
                 refarray(refindex + stride(1)*(i - lower(1))) =
     $                value
              endif
           endif
        enddo
To access a two dimensional array, the expression referring to element i, j is
        refarray(refindex + stride(1) * (i - lower(1)) + stride(2) * (j - lower(2))
To access a three dimensional array, the expression referring to element i, j k is
        refarray(refindex + stride(1) * (i - lower(1)) + stride(2) * (j - lower(2)) + stride(3) * (k - lower(3))
You can call things like LINPACK or BLAS if you want, but you should check the stride to make sure the array is packed as you need it to be.  stride(i) indicates the distance between elements in dimension i. A value of 1 means elements are packed densely in dimension i. Negative stride values are possible, and when an array is a slice of another array, there may be no dimension with a stride of 1.

For a dcomplex array, the reference array should a FORTRAN array of REAL*8 instead of a FORTRAN array of double complex to avoid potential alignment problems. For a fcomplex array, the reference array is a complex*8 because we don't anticipate an alignment problem.