3.3. Implementation of the F90Driver in Fortran 90

Before we begin the implementation, it is important to understand that, regardless of language, both the CCA and especially Babel/SIDL impose an object-oriented model on any of its supported languages, including Fortran. Most importantly, this means that each Fortran component has state and methods. State means that variables are associated with a particular instance component and that these state variables (sometimes referred to as private data) can take on different values for different instances. A method is a subroutine that is associated with the component. A short introduction to the way CCA/Babel deal with imposing an object model on Fortran is given in Section 3.4, “SIDL and CCA Object Orientation in Fortran” and can be read at your leisure. You should also read the Fortran 90 section of the Babel Users' Guide.

There are other limitations of the Fortran 90 standard that Babel deals with by adhering to certain conventions:

  1. The next step in implementing the driver is to get Babel to generate the skeleton code that we will fill in with the component's implementation. In the student-src/components directory, type make .drivers.F90Driver-f90. The output should look something like this:

    ### Generating a f90 implementation for the drivers.F90Driver component.
    /san/shared/cca/tutorial/bin/babel -s f90 -R../xml_repository \
     -R/san/shared/cca/tutorial/share/cca-spec-babel-0_7_0-babel-0.9.4/xml \
     -g -u -E -l -m drivers.F90Driver. --suppress-timestamp drivers.F90Driver
    Babel: Resolved symbol "drivers.F90Driver"...
    touch .drivers.F90Driver-f90
    

    and in the student-src/components/drivers/f90 directory, you should see the following files:

    drivers.F90Driver.babel.make
    drivers_F90Driver_Impl.F90
    drivers_F90Driver_Mod.F90
    glue
    

    all of which were generated by Babel. (glue is actually a directory that contains a large number of generated files that Babel needs to do its job, but which you never need to modify.) The source code files that you will need to modify in order to implement the component are the so-called Impl files. For Fortran 90, both a source file (_Impl.F90) and the corresponding module file (_Mod.F90) are generated.

  2. In your editor, take a look through both student-src/components/drivers/f90/drivers_F90Driver_Impl.F90 and student-src/components/drivers/c++/drivers_F90Driver_Mod.F90 to familiarize yourself with their structure before you make any changes.

    1. When an automatically generated file is manually modified, there is always a danger that the modifications will be overwritten the next time the file is generated. Babel solves this with a concept called splicer blocks. These structured comments that appear to the compiler as regular comments, but are interpreted by Babel as having a special meaning. Babel will preserve code within a splicer block when the file is regenerated. Code outside splicer blocks will be overwritten. Most Babel-generated files contain numerous splicer blocks -- everywhere you might need to add something to the generated skeleton. Here is an example:

      ...
      ! DO-NOT-DELETE splicer.begin(drivers.F90Driver.use)
      ! Insert use statements here...
      ! DO-NOT-DELETE splicer.end(drivers.F90Driver.use)
      ...
      

      Note that each splicer block has a name that is unique within the file, and has explicit beginning and end markers. In this case, the leading comment syntax is appropriate to Fortran 90, but of course files generated for other languages will have different ways of denoting comments.

    2. In the drivers_F90Driver_Impl.F90, You will see that Babel has already generated the signatures for all of the methods you need to implement, giving them appropriate names that conform to the Fortran 90 standard (including being hashed to remain within the 32 character limit if necessary), however it should be fairly easy to match them up with corresponding SIDL names. In this case, both the go method inherited from the gov.cca.ports.GoPort definition, and the setServices method inherited from the gov.cca.Component definition are there, along with several others associated with Babel.

3.3.1. The setServices Implementation

  1. We'll begin by implementing the setServices method in drivers_F90Driver_Impl.F90. Here is what the routine should look like (you'll need to type in the stuff marked up like this), along with some comments about different sections.

    ...
    !
    ! Method:  setServices[]
    !
    
    recursive subroutine F90Dri_setServices4khxt4z7ds_mi(self, services, &
      exception)
      use sidl_BaseInterface
      use drivers_F90Driver
      use gov_cca_Services
      use gov_cca_CCAException
      use drivers_F90Driver_impl
      ! DO-NOT-DELETE splicer.begin(drivers.F90Driver.setServices.use)
      ! Insert use statements here...
    
      use gov_cca_TypeMap      ! A CCA catch-all properties list (empty for us)
      use gov_cca_Port         ! needed to use a gov.cca.Port (we do)
      use gov_cca_ports_GoPort ! need to export our implementation of GoPort
    
      ! DO-NOT-DELETE splicer.end(drivers.F90Driver.setServices.use)
      implicit none
      type(drivers_F90Driver_t) :: self ! in
      type(gov_cca_Services_t) :: services ! in
      type(sidl_BaseInterface_t) :: exception ! out
    
    ! DO-NOT-DELETE splicer.begin(drivers.F90Driver.setServices)
    ! Insert the implementation here...
    
      type(gov_cca_TypeMap_t)    :: myTypeMap 1
      type(gov_cca_Port_t)       :: myPort
      type(SIDL_BaseInterface_t) :: excpt
      type(drivers_F90Driver_wrap) :: dp
    
      call drivers_F90Driver__get_data_m(self, dp) 2
    
      ! Set my reference to the services handle
      dp%d_private_data%frameworkServices = services 3
    
      call addRef(services)
      
      ! Create an empty TypeMap 
      call createTypeMap(dp%d_private_data%frameworkServices, & 4
                         myTypeMap, excpt)
      call checkExceptionDriver(excpt, 'setServices createTypeMap call')
    
      ! Provide a GoPort
      call cast(self, myPort) 2
    
      call addProvidesPort(dp%d_private_data%frameworkServices, & 2
                           myPort, 'GoPort', 'gov.cca.GoPort',  &
                           myTypeMap, excpt)
      call checkExceptionDriver(excpt,'setServices addProvidesPort: GoPort' )
    
      ! Register to use an integrator port
      call registerUsesPort(dp%d_private_data%frameworkServices, & 5
           'IntegratorPort',                                     &
           'integrator.Integrator',                              &
           myTypeMap, excpt)
      call checkExceptionDriver(excpt, &
           'setServices registerUsesPort: IntegratorPort')
    
      call deleteRef(myTypeMap) 4
    ! DO-NOT-DELETE splicer.end(drivers.F90Driver.setServices)
    end subroutine F90Dri_setServices4khxt4z7ds_mi
    ...
    

    1

    Declaration of variables that will be needed below. The types are defined in various modules used above. The drivers_F90Driver_wrap type is a Babel idiom for the private data associated with the particular instance of this component, in an object-oriented sense.

    3

    When the framework calls setServices, it passes in a gov.cca.Services object (in C++ gov::cca::Services) that we need to keep a copy of in the private data associated with this instance of our component. Babel uses “reference counting” to track usage of objects in order to know when it is safe to delete them. Because Fortran has no native mechanism for reference counting, we must use Babel's addRef method to indicate that we're storing a reference to the services object that the framework passed in to setServices

    4

    The services methods to register uses and provides ports requires a gov.cca.TypeMap (in Fortran TypeMap), which we create here.

    In SIDL, methods can throw exceptions. In languages like Fortran, which don't have native support for exceptions (if you're not familiar with exceptions, it is sufficient to think of them as error codes), they are translated into an additional subroutine argument (in this case excpt) which then should be checked (“caught”). We'll add the checkExceptionDriver method in Step 2.

    When Babel creates myTypeMap, it will (internally) add a reference to it. Once we're done using it, we can tell Babel that by calling Babel's deleteRef method, which you can see at the end of the routine. When the reference count goes to zero, Babel will destroy the myTypeRef object and reclaim the memory associated with it.

    [Caution] Caution

    Failure to follow proper reference counting procedures in Babel/Fortran (or other non-OO languages, such as C) code will lead to “memory leaks” in your application. See the Babel Users' Guide for more detailed information.

    2

    In order to register the ports that our component will provide with the framework, we use the addProvidesPort method of the gov.cca.Services interface. You can find this interface in the cca.sidl file (where you previously looked up gov.cca.Component and gov.cca.ports.GoPort) in order to check its signature, which is:

    ...
    void addProvidesPort(in gov.cca.Port inPort,
                         in string portName,
                         in string type,
                         in gov.cca.TypeMap properties )
           throws gov.cca.CCAException ;
    ...
    

    (Of course we're actually calling the Fortran 90 version of the interface.)

    The first argument is the object that actually provides the port. The way we wrote the SIDL, the drivers.F90Driver class provides the port, and since we're writing a method within this class, we use Babel's cast method to cast our self pointer to type gov.cca.Port.

    The second and third arguments are a local name for the port, which must be unique within the component, and a type, which should be globally unique. If the actual types of the ports don't match between user and provider, it will cause a failed cast or possibly a segmentation fault. The string type here is a convenience to the user, giving a human-readable way to identify the type of the port that can be presented in the framework's user interface. By convention, the SIDL interface name for the port is used for the type.

    The final argument is a gov.cca.TypeMap. This is a CCA-defined type that provides a simple hash table that can be used to associate properties with a provides port. In practice, it is rarely used, but must be present.

    5

    We must also tell the framework which ports we expect to use from other components. Looking in cca.sidl, we find that the method's signature is:

    ...
    void registerUsesPort(in string portName,
                          in string type,
                          in gov.cca.TypeMap properties ) 
         throws gov.cca.CCAException ;
    ...
    

    The first and second arguments are a local name for the port, following the same rules and conventions as in the addProvidesPort invocation above. The final argument is, once again, a gov.cca.TypeMap, again like addProvidesPort.

  2. The module file also requires a couple of additions. First, let's take care of declaring frameworkServices as part of the module's private data.

    Edit student-src/components/drivers/f90/drivers_F90Driver_Mod.F90 and add the following:

    ...
    type drivers_F90Driver_priv
      sequence
      ! DO-NOT-DELETE splicer.begin(drivers.F90Driver.private_data)
      
      ! Handle to framework Services object
      type(gov_cca_Services_t) :: frameworkServices
    
      ! DO-NOT-DELETE splicer.end(drivers.F90Driver.private_data)
    end type drivers_F90Driver_priv
    ...
    

  3. We also need to add the use directives for the module for gov.cca.Services.

    ...
    ! DO-NOT-DELETE splicer.begin(drivers.F90Driver.use)
    ! Insert use statements here...
    
    ! CCA framework services module
      use gov_cca_Services
    
    ! DO-NOT-DELETE splicer.end(drivers.F90Driver.use)
    ...
    

  4. Now, although the component is not complete, it is a good idea to check that it compiles correctly with the code you've added so far.

    First, change directories to student-src/components and run make drivers. This will install Makefile and MakeIncl.user files in student-src/components/drivers/f90.

    Then, change directories to student-src/components/drivers/f90 and run make. If you get any compiler errors, you should fix them before going on.

3.3.2. Implementing the Constructor and Destructor

Constructor and destructor are concepts from object-oriented programming. Specifically, they are the routines that are called to create an instance of an object, and when it is being destroyed. When using most OO languages in the CCA/Babel environment, the constructor and destructor are handled pretty much automatically. In a non-OO language, like Fortran or C, we have to do a little more work. Specifically, we have to allocate and deallocate the data needed to maintain the private state of the component instance.

  1. Edit student-src/components/drivers/f90/drivers_F90Driver_Impl.F90 and find the constructor method, which Babel abbreviates ctor.

    The constructor must allocate the space for the private data, initialize the private data as appropriate (in this case, we set frameworkServices to null), and Babel has to be told about the private data. In this component, the only private data we need to store is a pointer to the services object passed into setServices.

    ...
    ! 
    ! Class constructor called when the class is created.
    ! 
    
    recursive subroutine drivers_F90Driver__ctor_mi(self)
      use drivers_F90Driver
      use drivers_F90Driver_impl
      ! DO-NOT-DELETE splicer.begin(drivers.F90Driver._ctor.use)
      ! Insert use statements here...
      ! DO-NOT-DELETE splicer.end(drivers.F90Driver._ctor.use)
      implicit none
      type(drivers_F90Driver_t) :: self ! in
    
    ! DO-NOT-DELETE splicer.begin(drivers.F90Driver._ctor)
    ! Insert the implementation here...
    
      ! Access private data
      type(drivers_F90Driver_wrap) :: dp
      ! Allocate memory and initialize
      allocate(dp%d_private_data)
      call set_null(dp%d_private_data%frameworkServices)
      call drivers_F90Driver__set_data_m(self, dp)
    
    ! DO-NOT-DELETE splicer.end(drivers.F90Driver._ctor)
    end subroutine drivers_F90Driver__ctor_mi
    ...
    

  2. Find the destructor method, which Babel abbreviates dtor. The destructor's job is to undo what the constructor did.

    ...
    ! 
    ! Class destructor called when the class is deleted.
    ! 
    
    recursive subroutine drivers_F90Driver__dtor_mi(self)
      use drivers_F90Driver
      use drivers_F90Driver_impl
      ! DO-NOT-DELETE splicer.begin(drivers.F90Driver._dtor.use)
      ! Insert use statements here...
      ! DO-NOT-DELETE splicer.end(drivers.F90Driver._dtor.use)
      implicit none
      type(drivers_F90Driver_t) :: self ! in
    
    ! DO-NOT-DELETE splicer.begin(drivers.F90Driver._dtor)
    ! Insert the implementation here...
      
      ! Access private data and deallocate storage
      type(drivers_F90Driver_wrap) :: dp
      call drivers_F90Driver__get_data_m(self, dp)
      deallocate(dp%d_private_data)
    
    ! DO-NOT-DELETE splicer.end(drivers.F90Driver._dtor)
    end subroutine drivers_F90Driver__dtor_mi
    ...
    

  3. Now, although the component is not complete, it is a good idea to check that it compiles correctly with the code you've added so far. Run make in student-src/components/drivers/f90. If you get any compiler errors, you should fix them before going on.

3.3.3. The go Implementation

  1. Once again, edit student-src/components/drivers/f90/drivers_F90Driver_Impl.F90 and add the implementation of the go method:

    ...
    ! 
    ! Method:  go[]
    ! 
    
    recursive subroutine drivers_F90Driver_go_mi(self, retval)
      use drivers_F90Driver
      use drivers_F90Driver_impl
      ! DO-NOT-DELETE splicer.begin(drivers.F90Driver.go.use)
      ! Insert use statements here...
      
      use sidl_BaseInterface 1
      use gov_cca_Services
      use gov_cca_Port
      use integrator_IntegratorPort
    
      ! DO-NOT-DELETE splicer.end(drivers.F90Driver.go.use)
      implicit none
      type(drivers_F90Driver_t) :: self ! in
      integer (selected_int_kind(9)) :: retval ! out
    
    ! DO-NOT-DELETE splicer.begin(drivers.F90Driver.go)
    ! Insert the implementation here...
    
      type(gov_cca_Port_t) :: generalPort 2
      type(SIDL_BaseInterface_t) :: excpt
      type(integrator_IntegratorPort_t) :: integratorPort 2
    
      ! Private data reference
      type(drivers_F90Driver_wrap) :: dp
    
      ! local variables for integration
      real (selected_real_kind(15, 307)) :: lowBound 3
      real (selected_real_kind(15, 307)) :: upBound 
      integer (selected_int_kind(9)) :: count 
      real (selected_real_kind(15, 307)) :: value 
    
      ! Initialize local variables
      count = 100000 3
      lowBound = 0.0
      upBound = 1.0
    
      ! Access private data
      call drivers_F90Driver__get_data_m(self, dp)
      retval = -1
    
      ! get the port ...
      call getPort(dp%d_private_data%frameworkServices, & 2
           'IntegratorPort', generalPort, excpt)
      call checkExceptionDriver(excpt, & 4
           'getPort(''IntegratorPort'')')
      if(is_null(generalPort)) then
         write(*,*) 'drivers.F90Driver not connected'
         return
      endif
    
      ! Get an IntegratorPort reference from the general port one
      call cast(generalPort, integratorPort) 2
      
      if (not_null(integratorPort)) then 4
         value = -1.0 ! nonsense number to confirm it is set
    
         ! operate on the port
         call integrate(integratorPort, lowBound, upBound, count, & 5
                        value)
         write(*,*) 'Value = ', value
      else   ! integratorPort is null
         write(*,*) 'DriverF90: incompatible IntegratorPort'
      endif
    
      ! release the port
      call releasePort(dp%d_private_data%frameworkServices, & 6
                       'IntegratorPort', excpt)
      call checkExceptionDriver(excpt, 'releasePort(''IntegratorPort'')')
    
      retval = 0 7
      return
    
    ! DO-NOT-DELETE splicer.end(drivers.F90Driver.go)
    end subroutine drivers_F90Driver_go_mi
    ...
    

    1

    Declarations for modules we need to use in this routine.

    3

    Setup the variables and parameters with which to call the integrator.

    2

    These portions of the code are associated with getting a handle to the particular integrator.IntegratorPort that the driver's uses port has been connected to.

    First, we have to declare variables of the appropriate type to hold the port. Because of the way OO programming works in CCA/Babel, we first get the port as a generic gov.cca.Port (gov_cca_Port_t in Fortran 90) and then cast it to the specific port we need to use, integrator.IntegratorPort (integrator_IntegratorPort_t in Fortran 90). Recall that integrator.IntegratorPort is defined in student-src/ports/sidl/integrator.sidl).

    Then, we invoke the getPort on our frameworkServices object. The argument to this method is the local name we used in the registerUsesPort invocation, and it returns a gov.cca.Port (and an exception).

    Finally, we use Babel's cast method to cast the generic port to the specific integrator port that we need.

    4

    This code checks that the getPort worked, and returned a valid port. If the getPort fails, or if the driver's uses port has not been connected to an appropriate provider, then getPort will return a null port object. The is_null method is automatically available on the Fortran 90 binding of any SIDL object. Because the driver can't do anything without being properly connected to an integrator, the response to getPort failing is to abort by returning a non-zero value.

    It is also possible that a valid gov.cca.Port would be returned, but it might not be the integrator.IntegratorPort we expect. If this is the case, the cast will return a null value. The proper action in this case is also to fail gracefully by returning a non-zero result.

    [Note] Note

    getPort returning nil need not be treated as a fatal error in all cases. For example, a component may be designed so that certain ports are optional -- to be used if present, but to be ignored if not. Another possibility is that the component may be able to accomplish the same thing through several different ports, so that only one of a given group needs to be connected.

    5

    Here we actually call the integrate method on the integrator port we just got a handle for. The signature of the integrate method is defined in student-src/ports/sidl/integrator.sidl. Notice that while the SIDL definition of integrate shows it as a function, returning a double precision result, in Fortran 90, Babel translates this into a subroutine with the return value as an extra argument. This is because Fortran does not support functions returning all types (arrays, for example).

    6

    Finally, once we're done using the port, we call releasePort.

    7

    It is considered impolite for a component to call exit because it will bring down the entire application, and possibly crash the framework. Instead, components should simply return.

  2. There's one other bit of code we have to provide before we can declare this component complete. In numerous places, we've seen exceptions being returned, and we've been using a routine checkExceptionDriver to deal with them. This is a method that we have to write.

    Exceptions are a potentially powerful and sophisticated way of handling errors in software. But for the purposes of this exercise, we're going to take a very simple approach. Our exception handler routine simply test whether or not the exception is a null object, and if it is print a message and tell Babel that as far as we're concerned it can delete the excpt object. Notice that this routine does not exit or abort. As we've noted, it is not considered polite behavior for a component to exit, even in the event of an exception.

    In student-src/components/drivers/f90/drivers_F90Driver_Impl.F90 locate the splicer blocks for miscellaneous code, at the very end of the file, and enter the following:

    ...
    ! DO-NOT-DELETE splicer.begin(_miscellaneous_code_end)
    ! Insert extra code here...! 
    ! Small routine (not part of the SIDL interface) for 
    ! checking the exception and printing the message passed as
    ! and argument
    !
    subroutine checkExceptionDriver(excpt, msg)
      use SIDL_BaseInterface
      use gov_cca_CCAException
      implicit none
      type(sidl_BaseInterface_t), intent(inout) :: excpt  
      character (len=*) :: msg ! in
      if (not_null(excpt)) then
          write(*, *) 'drivers.F90Driver Exception: ', msg
          call deleteRef(excpt)
      end if
    end subroutine checkExceptionDriver
    
    ! DO-NOT-DELETE splicer.end(_miscellaneous_code_end)
    ...
    

  3. Congratulations, you have completed the implementation of the F90Driver! To check your work, run make in student-src/components/drivers/f90. If you get any compiler errors, you should fix them before going on.

  4. At this point, it is a good idea to go up to student-src and run make to insure that anything else which might depend on the existence of the new drivers.CXXDriver component gets built too.

The next step is to test your new driver component, in Section 3.5, “Using Your New Component”.