Example of a Tcl extension in Fortran

Arjen Markus (26 september 2016) With the introduction of the Fortran 2003 standard, Fortran compilers provide a standard way of interfacing with C. One of the advantages is that you no longer need to create intermediate routines to handle the inherent differences between Fortran and C data types and calling conventions. But you also no longer need to pay as much attention to differences between platforms as before. Such differences still exist, but they are mainly at the level of the link step - if you want to create a DLL for instance.

Below is a somewhat contrived example of an extension in Fortran that you can write via this standardised interfacing. The gory details are in the module ftcl - it defines the set of interfaces needed, initialises the Tcl stub library.

The programmer who uses this Fortran-Tcl interface can use the Tcl API as is:

! addpkg.f90 --
!     Very basic Ftcl extension: add two numbers
!
module addpkg
    use ftcl
    use iso_c_binding

    implicit none

contains

! addcmd --
!     Example of a Tcl object command procedure
!
! Arguments:
!     clientdata       Any data registered for the command
!     interp           Tcl interpreter that is running the command
!     nobj             Number of arguments
!     objv             Array of argument objects
!
!
integer function addcmd( clientdata, interp, nobj, objv ) bind(c)
    type(c_ptr), value          :: clientdata
    type(tcl_interp), value     :: interp
    integer(c_int), value       :: nobj
    type(tcl_obj), dimension(*) :: objv

    integer(c_long) :: arg1, arg2, result

    addcmd = tcl_error

    !
    ! The name of the command is the first argument, so correct for that
    !
    if ( nobj-1 /= 2 ) then
        call tcl_setresult( interp, "Wrong number of arguments" // c_null_char, c_null_funptr )
        return
    endif

    if ( tcl_getlongfromobj( interp, objv(2), arg1 ) /= 0 ) then
        return
    endif

    if ( tcl_getlongfromobj( interp, objv(3), arg2 ) /= 0 ) then
        return
    endif

    addcmd = tcl_ok

    result = arg1 + arg2

    call tcl_setobjresult( interp, tcl_newlongobj(result) )
end function addcmd

end module addpkg

! package_init --
!     Routine to set up the package, must be outside a module
!
! Arguments:
!     interp           Tcl interpreter object to be used
!     pkgname          Name of the package (output)
!     pkgversion       Version of the package as a string (output)
!
subroutine package_init( interp, pkgname, pkgversion )
    use addpkg

    implicit none

    type(tcl_interp)              :: interp
    character(len=*), intent(out) :: pkgname
    character(len=*), intent(out) :: pkgversion
    type(c_ptr)                   :: dummyptr

    pkgname    = "addpkg"
    pkgversion = "1.0"

    dummyptr = tcl_createobjcommand( interp, "add", addcmd, c_null_ptr, c_null_funptr )
end subroutine package_init

Some of the gory details:

  • For each Tcl function, a proper interface is required that describes the arguments:
abstract interface
    integer function tcl_pkgprovide_api( interp, name, version, clientdata ) bind(c)
        import :: tcl_interp, c_ptr
        type(tcl_interp), value        :: interp
        character(len=1), dimension(*) :: name
        character(len=1), dimension(*) :: version
        type(c_ptr), value             :: clientdata
    end function tcl_pkgprovide_api
end interface
  • These abstract interfaces are used to define a procedure pointer:
    procedure(tcl_pkgprovide_api), pointer, public :: tcl_pkgprovide

    ...

    call c_f_procpointer( tcl_stubs%f0,   tcl_pkgprovide )
  • The Tcl stubs table, tclStubsPtr is represented via a derived type. The derived type has entries f0, f1, ... f630 to get access to the pointers to the actual C functions in the Tcl API. For the programmer this means, however, that the functions are available as if they were ordinary functions and subroutines.

To gain access to the full Tcl library will take quite a bit of code, but luckily that code can be generated (almost) automatically from the "tclDecls.h" header file.

One thing to solve: the macros Tcl_IsShared, Tcl_IncrRefCount and Tcl_DecrRefCount - they are preprocessor macros and will have to be implemented as ordinary functions.

Musings: This is the work of one day. I am not sure if I want to keep the current design, for instance, initialise a specific package via a routine with a fixed name. It does hide a few details though.

Building the extension

To build the extension using gfortran, this command suffices:

gfortran -o ftcl.dll ftcl.f90 addpkg.f90 wrpnt.o -shared -ltclstub 

You can also use Intel Fortran. On Windows the command looks like:

ifort ftcl.f90 addpkg.f90 -dll c:\tcl\lib\tclstub86.lib

See also