Interfacing with the Tcl C API from Fortran

Arjen Markus (30 september 2016) A few days ago I started a revised interface between Fortran and Tcl (the old one is at [L1 ]). This revised version relies on the standardised C binding that became available with the introduction of the Fortran 2003 standard. It is exemplified by the ISO_C_BINDING module. The great advantage is that the full interface can be implemented in Fortran, rather than via an intermediate layer in C that translates the various data types from one language to the other.

I specifically target the Tcl stub interface. But that consists of about 630 functions. Not all of these will be useful for a typical extension, but you never know. So, generating the Fortran code to describe the interface requires automation - or extreme patience.

To sketch the task, here is an abstract from the current code (it is limited to a handful of functions to get the Example of a Tcl extension in Fortran going:

  • First of all, I define specific interfaces for all C functions in the stub table. Such an interface describes in detail how the function should be called:
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
  • The stub mechanism puts function pointers in a large table and this table gets filled by the Tcl_InitStubs function. To get access within Fortran to these pointers, I define procedure pointers and initialise them from the filled stub table:
    procedure(tcl_pkgprovide_api), pointer, public :: tcl_pkgprovide
    ...
    call c_f_procpointer( tcl_stubs%f0,   tcl_pkgprovide )

With this in place I can call the Tcl C function Tcl_PkgProvide as if it were an ordinary function/subroutine. (I cannot rely on C preprocessor macros)

Now the full Tcl stub table: the C code is generated from the file tcl.decls. And that is actually a Tcl script. That makes it easy to transform the API into the style required by Fortran. Here is the first part - pick the C interface code apart and transform it into a list of names and types:

# mktclstub.tcl --
#     Use the file tcl.decls to generate the interfaces to the various
#     C functions in the Tcl API
#

set ::api         {}
set ::generate    1
set ::uniqueTypes {}

# dummy commands --
#     We only need "declare"
#
foreach name {library interface hooks scspec} {
    proc $name {args} {}
}

# interface --
#     Do only do "tcl"
#
proc interface {name} {
    if { $name != "tcl" } {
        set ::generate 0
    }
}

# export --
#     Perhaps needed as well
#
proc export {signature} {}

# declare --
#     Define the stub entry
#
proc declare {index signature {extra {}}} {
    global api

    if { $extra != "" } {
        set signature $extra
    }

    if { $::generate } {
        set nameTypeList [getNamesTypes $signature]
        getUniqueTypes $nameTypeList

        puts $nameTypeList

        lappend api $index $nameTypeList
    }
}

# getNamesTypes --
#     Transform the C signature
#
# Arguments:
#     signature       The C code defining the signature
#
proc getNamesTypes {signature} {
    set namesTypes {}

    set signature [lrange [split [string trim $signature] (,)] 0 end-1]

    foreach element $signature {
         set element [string trim $element]
         regexp {[A-Za-z0-9_\[\]]+$} $element name
         set length [string length $name]
         set type [string trim [string range $element 0 end-$length]]

         lappend namesTypes $name $type
    }

    return $namesTypes
}

# getUniqueTypes --
#     Determine what types are required
#
# Arguments:
#     namesTypes      List of names and types
#
proc getUniqueTypes {namesTypes} {
    global uniqueTypes

    foreach {name type} $namesTypes {
         if { [lsearch $uniqueTypes $type] < 0 } {
             lappend uniqueTypes $type
         }
    }
}

# analyse and transform
#
source tcl.decls

puts "Types:"
puts [join $uniqueTypes \n]

This code is independent of the target language, so a second part (not finished yet) is to take the result of the analysis and generate the Fortran interfaces from that.

Because it is independent of the target language, it can be useful if you have similar requirements for your programming language.