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. 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:
Fortran doesn't offer the hierarchical structures for routine
and type names in the way that most OO languages do, so SIDL's
hierarchical dot-separated notation is translated into a flat
namespace using underscores in Fortran. For example,
gov.cca.Services is translated to
gov_cca_Services. A reference to that SIDL
interface would be defined as a variable in this fashion:
type(gov_cca_Services_t) :: services
Because of the requirement that all symbols in Fortran 90 be at
most 31 characters, the sometimes long names common in OO
programming styles need to be abbreviated. Babel keeps the most
significant portion of the name (the base name) and truncates
the rest, adding a hash to make it unique if necessary. For
example, our own
setServices() subroutine declaration
recursive subroutine F90Dri_setServices4khxt4z7ds_mi(self, services, & exception)
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
.drivers.F90Driver-f90. The output should look
something like this:
### Generating a f90 implementation for the drivers.F90Driver component. /san/cca/cca-tools_gcc_intelF90_PIC/bin/babel -s f90 -R../xml_repository \ -R/san/cca/cca-tools_gcc_intelF90_PIC/share/cca-spec-babel-0_8_0-babel-1.0.0/xml \ -g -u -E -l -m drivers.F90Driver. --suppress-timestamp drivers.F90Driver Babel: Resolved symbol "drivers.F90Driver"... touch .drivers.F90Driver-f90
and in the
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 (
the corresponding module file (
_Mod.F90) are generated.
In your editor, take a look through both
to familiarize yourself with their structure before you make any
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.
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 31 character limit if necessary), however
it should be fairly easy to match them up with corresponding
SIDL names. Furthermore, Babel also generates a "default" method
implementation that throws a "method not implemented" exception.
You will need to delete the exception-throwing code (or comment it
out) before adding code that actually implements the method under
In this case, both the
method inherited from the
setServices method inherited
definition are there, along with several others associated with
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
like this), along with some
comments about different sections.
... ! ! Method: setServices ! recursive subroutine F90Dri_setServices4khxt4z7ds_mi(self, services, & exception) use sidl use sidl_NotImplementedException use gov_cca_CCAException use gov_cca_Services use sidl_BaseInterface use sidl_RuntimeException use drivers_F90Driver 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
type(gov_cca_Port_t) :: myPort type(SIDL_BaseInterface_t) :: excpt type(drivers_F90Driver_wrap) :: dp call drivers_F90Driver__get_data_m(self, dp)
! Set my reference to the services handle dp%d_private_data%frameworkServices = services
call addRef(services, excpt) ! Create an empty TypeMap call createTypeMap(dp%d_private_data%frameworkServices, &
myTypeMap, excpt) call checkExceptionDriver(excpt, 'setServices createTypeMap call') ! Provide a GoPort call cast(self, myPort, excpt)
call addProvidesPort(dp%d_private_data%frameworkServices, &
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, &
'IntegratorPort', & 'integrator.Integrator', & myTypeMap, excpt) call checkExceptionDriver(excpt, & 'setServices registerUsesPort: IntegratorPort') call deleteRef(myTypeMap, excpt)! DO-NOT-DELETE splicer.end(drivers.F90Driver.setServices) end subroutine F90Dri_setServices4khxt4z7ds_mi ...
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.
When the framework calls
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
When Babel creates
In order to register the ports that our component will provide
with the framework, we use the
... 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
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
We must also tell the framework which ports we expect to
use from other components. Looking in
... 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
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.
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 ...
We also need to add the
use directives for the
F90 module for
... ! 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) ...
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
make drivers. This will install
MakeIncl.user files in
Then, change directories to
make. If you get any compiler
errors, you should fix them before going on.
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.
and find the constructor method, which Babel abbreviates
The constructor must allocate the space for the private data,
initialize the private data as appropriate (in this case, we set
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
... ! ! 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 ...
Find the destructor method, which Babel abbreviates
dtor. The destructor's job is to undo what the
... ! ! 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 ...
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
If you get any compiler errors, you should fix them before going
Once again, edit
and add the implementation of the
... ! ! Method: go ! recursive subroutine drivers_F90Driver_go_mi(self, retval, exception) use sidl use sidl_NotImplementedException use sidl_BaseInterface use sidl_RuntimeException use drivers_F90Driver use drivers_F90Driver_impl ! DO-NOT-DELETE splicer.begin(drivers.F90Driver.go.use) ! Insert use statements here...
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 type(sidl_BaseInterface_t) :: exception ! out ! DO-NOT-DELETE splicer.begin(drivers.F90Driver.go) ! Insert the implementation here...
type(gov_cca_Port_t) :: generalPort
type(SIDL_BaseInterface_t) :: excpt type(integrator_IntegratorPort_t) :: integratorPort
! Private data reference type(drivers_F90Driver_wrap) :: dp ! local variables for integration real (kind=sidl_double) :: lowBound
real (kind=sidl_double) :: upBound integer (kind=sidl_int) :: count real (kind=sidl_double) :: value ! Initialize local variables count = 100000
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, &
'IntegratorPort', generalPort, excpt) call checkExceptionDriver(excpt, &
'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, excpt)
call checkExceptionDriver(excpt, 'cast(generalPort, integratorPort, excpt)') if (not_null(integratorPort)) then
value = -1.0 ! nonsense number to confirm it is set ! operate on the port call integrate(integratorPort, lowBound, upBound, count, &
value, excpt) call checkExceptionDriver(excpt, 'integrate(integratorPort, lowBound, upBound, count, value, excpt)') write(*,*) 'Value = ', value else ! integratorPort is null write(*,*) 'DriverF90: incompatible IntegratorPort' endif ! release the port call releasePort(dp%d_private_data%frameworkServices, &
'IntegratorPort', excpt) call checkExceptionDriver(excpt, 'releasePort(''IntegratorPort'')') retval = 0
return! DO-NOT-DELETE splicer.end(drivers.F90Driver.go) end subroutine drivers_F90Driver_go_mi ...
Declarations for modules we need to use in this routine.
Setup the variables and parameters with which to call the integrator.
These portions of the code are associated with getting a handle 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_t in Fortran 90). Recall
Then, we invoke the
Finally, we use Babel's
This code checks that the
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
Here we actually call the
Finally, once we're done using the port, we call
It is considered impolite for a component to call
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.
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 type(sidl_BaseInterface_t) :: throwaway if (not_null(excpt)) then write(*, *) 'drivers.F90Driver Exception: ', msg call deleteRef(excpt, throwaway) end if end subroutine checkExceptionDriver! DO-NOT-DELETE splicer.end(_miscellaneous_code_end) ...
Congratulations, you have completed the implementation of the
F90Driver! To check your work, run
. If you
get any compiler errors, you should fix them before going on.
At this point, it is a good idea to go up to
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”.