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 edit
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 edit