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:
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
procedure(tcl_pkgprovide_api), pointer, public :: tcl_pkgprovide ... call c_f_procpointer( tcl_stubs%f0, tcl_pkgprovide )
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.
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