Sunday, August 8, 2010

TCL and Fortran

FTCL

The Ftcl library offers an easy-to-use set of subroutines and functions, callable from Fortran to run Tcl commands within a Fortran program or to create an extension in Fortran useable by Tcl programs. This manual page describes the use of these routines.

INITIALISATION

The library can be used in two ways:  
  • Interacting with Tcl from within a Fortran program
  • Creating an extension (package of functions, commands and so on) to Tcl, written in Fortran
In the first case, you will need to call the routine ftcl_start to create a Tcl interpreter and initialise it. During this initialisation phase, the usual start-up sequence is run, which means the Tcl routines will look for a useable file "init.tcl" and a script library.
In the second case, you need to provide a routine called package_init that takes care of registering the commands this package provides and registering the name of the package. Tcl will be already running in this case.
New Tcl commands are registered via the routine ftcl_make_command in each case.
OVERVIEW OF ROUTINES
The Ftcl library contains the following functions and subroutines:  

use ftcl 
The Ftcl library is to be accessed via the Ftcl module. If you are using FORTRAN 77, then you will have to use the specific routines.  

call ftcl_get( varname, value )  

Get the value of the Tcl variable whose name is stored in varname.  

character(len=*) varname 

Name of the Tcl variable to get. 
integer/real/double precision/logical/character(len=*) value  Variable that will get the value, cast to whatever type the variable is.  
call ftcl_get_arg( iarg, value )  
Get the value of the iarg'th argument. Used in Fortran-based command routines (i.e. the ones registered via ftcl_make_command).  
integer/real/double precision/logical/character(len=*) value 
Variable that will get the value of the argument, cast to whatever type the variable is.  

call ftcl_get_array( varname, values ) 
Get the values of the Tcl array whose name is stored in varname.  

character(len=*) varname
Name of the Tcl variable to get.  
integer/real/double precision/logical/character(len=*), dimension(:) value
One-dimensional Fortran array that will get the values, cast to whatever type the Fortran array is. Note: The index into the Tcl array is assumed to be an ordinary number. The dimension on the Fortran side determines which elements are filled.

 call ftcl_main_loop  
Enter the Tcl event loop. Useful when creating GUIs. It will not return. 
call ftcl_make_command( procedure, cmdname ) 
Register a Fortran routine to serve as a Tcl command

subroutine procedure
The Fortran routine in question. The interface is this: 
subroutine procedure( cmdname, noargs, ierror )
character(len=*) :: cmdname 
integer :: noargs 
integer :: ierror 
end subroutine procedure 
where: cmdname is the name of the command that was used to call the routine noargs is the number of arguments that was given ierror can be used to indicate success or an error (0 means success) nteger/real/double precision/logical/character(len=*) value Variable that will get the value, cast to whatever type the variable is.
call ftcl_provide_package( pkgname, version, error )  
Register a package by name and version number. To be used from within a package initialisation routine (package_init).
character(len=*) pkgname
Name of the package
character(len=*) version
A version string like "1.0" or "2.2.1"
integer error Variable that indicates whether the package was successfully registered (0) or not (1).
call ftcl_put( varname, value )
Transfer the value to the Tcl variable whose name is stored in varname.  
character(len=*) varname
Name of the Tcl variable to set.
integer/real/double precision/logical/character(len=*) value 
Variable that will get the value, cast to whatever type the variable is. Note: if the Tcl variable is a (Tcl) array, then this may also be a one-dimensional array. The index into the Tcl array is assumed to be an ordinary number. The dimension on the Fortran side determines which elements are filled. 
call ftcl_put_array( varname, values )  
Transfer the values in the one-dimensional Fortran array to the Tcl variable whose name is stored in varname. Note: The index into the Tcl array will be an ordinary number. The dimension on the Fortran side determines which elements are filled.  
character(len=*) varname
Name of the Tcl variable to set.
integer/real/double precision/logical/character(len=*), dimension(:) value 
Variable that will get the value, cast to whatever type the variable is. Note: The index into the Tcl array will be an ordinary number. The dimension on the Fortran side determines which elements are filled.
call ftcl_script( script )  
Run the script contained in the variable "script".  
character(len=*) script 
String containing the Tcl script to be run.  
call ftcl_set_result( value ) 
Set the result of the command - used within command routines.  
integer/real/double precision/logical/character(len=*) value 
The value to be set as the result of the command. This is the value that Tcl returns.  
call ftcl_start( filename )   
Start-up routine for Ftcl. The filename argument should ideally point to the executable, so that the init.tcl file can be found.
character(len=*) filename 
Full name of the executable or another string that enables Tcl to find the file init.tcl and the script library. (See section on deployment) 
EXTENSIONS AND PACKAGES
Tcl itself provides a concise mechanism to load shared libraries or DLLs (the terminology depends on the operating system, we will use "DLL" to mean either). This allows you to extend any Tcl shell (tclsh, wish or a custom interpreter) with your own commands. Ftcl makes it possible to create such extensions in Fortran:
 You should write a Fortran routine with the fixed name package_init. Since the routine must be callable from C, it can not reside in a module. This routine has the task of initialising the package:
  
The routine takes one argument, an integer that on return indicates whether everything was okay or whether there was an error. Here is a sketch:
subroutine package_init( error )
use mypkg 
integer :: error 
call ftcl_provide_package( "mypkg", "1.0", error ) 
if ( error .ne. 0 ) return 
call ftcl_make_command( ... ) 
...
end sobroutine package_init 
Register one or more commands (via ftcl_make_command)  
Do whatever is required for the package to become properly initialised  
Register the package by name and version (via ftcl_provide_package)
Registering the package properly means that Tcl's package mechanism knows about it. It will not attempt to load the DLL again on the next "package require" command.  
You need to write a package index file, called pkgIndex.tcl, that loads the DLL. This takes the following form: 
package ifneeded Mypkg version [list load [file join $dir mypkg.dll Ftclpkg]] 
For your convenience, this can be done via the script mkpackage.tcl in the tools directory. There are three arguments, in the given order:
The name of the package (in the example: Mypkg). This name is case-sensitive  
The version, a string like "1.0" or "2.2.1"  
The name of the actual DLL (without the extension). This name is case-sensitive on some operating systems. The proper extension is added by the script.  
The reason that the argument "Ftclpkg" appears, is that this argument is used by Tcl to construct the name of the initialisation routine for the package. By keeping that name fixed we can avoid any C programming, however simple.  
All that is needed now is to build the DLL with the proper compile and link commands. The section DEPLOYMENT has more to say about possible issues with runtime libraries, so please consult that as well.

No comments: