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:
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 32 characters, 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 F90Driver component's setServices() subroutine declaration looks like:
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 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.
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.
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.
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.
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) :: myTypeMaptype(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) ! 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)
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)
! 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 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 |
||||
|
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.
|
||||
|
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. |
||||
|
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. |
||||
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 ...
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) ...
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.
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.
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 ...
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 ...
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.
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_BaseInterfaceuse 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
type(SIDL_BaseInterface_t) :: excpt type(integrator_IntegratorPort_t) :: integratorPort
! Private data reference type(drivers_F90Driver_wrap) :: dp ! local variables for integration real (selected_real_kind(15, 307)) :: lowBound
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
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)
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) 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 ...
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) ...
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.
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”.