Arjen Markus (15 august 2022) During this month's monthly meet-up we discussed, among other things, the possibilities of DLLs (or shared objects) that contain the Tcl and Tk libraries as well as all the configuration files. Such "KitDLLs" are part of the KitCreator build system by Roy Keene. There are more solutions, like tclkit, BAWT, with similar goals and their own pros and cons. But I decided to have a closer look at KitCreator, as I thought it might be used to build Fortran programs with Tcl/Tk as embedded resource. The fact that all configuration files are part of the KitDLL library makes distribution very easy.
So, I downloaded the source code at [L1 ] and started building under MinGW-w64/MSYS2, as that is the build environment on Windows that is close to native Windows and still support the autotools build tools. Unfortunately, this failed. The crucial error occurred in building "tclvfs". I have not found the apparent quoting error yet in the generated code:
checking for a BSD-compatible install... /usr/bin/install -c
checking whether make sets $(MAKE)... ./configure: eval: line 2963: unexpected EOF while looking for matching `"'
./configure: eval: line 2964: syntax error: unexpected end of file
+ for tryopt in "${tryopts[@]}" __fail__
+ '[' __fail__ = __fail__ ']'
+ return 1
+ die 'configure failed'
+ local msg
+ msg='configure failed'
+ echo 'configure failed'
configure failed
+ exit 1As is usual with such code in a configure file, it is rather convoluted.
Therefore I tried instead a prebuilt KitDLL - the Tclkit for Tcl version 8.6.12 for Win64 on amd64 .
The file is called libtclkit-8.6.12-win64-amd64-kitdll-xcompile - this name does not end with .dll or .lib as a typical compiler on Windows would expect. I renamed the file to libtclkit-8.6.12-win64-amd64.dll and I used the following tiny source file for tclsh:
#include <tcl.h>
int Tcl_AppInit(Tcl_Interp *interp) {
return(Tcl_Init(interp));
}
int main(int argc, char **argv) {
Tcl_Main(argc, argv, Tcl_AppInit);
return(1);
}The relevant include file tcl.h comes from a suitable Tcl installation or source tree.
There was one small problem still: on Windows a DLL should be accompanied by a so-called import library. The GNU gcc compiler, however, circumvents this problem. Or you could use a utility to create one from the DLL.
NOTE: You better use, instead, the "software development kit", sdk, that is associated with the KitDLL. It contains the required libraries and include files in a ready form. It does not contain the above sample code for tclsh.
Anyway, I used the distribution of the GNU compilers from [L2 ] as they can be used in an ordinary DOS box (command window). The build step was easy and I got a tclsh.exe. However, the runtime name of the DLL is libtcl8612.dll, so I had to rename the DLL to that and it worked! (As noted above: the SDK contains just about all the files you need with the right names.)
Simple session:
> package require Tk > pack [canvas .c] > exit
I also tried to use a Fortran program to call Tcl_Main() and get an easy way to embed Tcl and Tk in such a program. That did not work immediately, most likely because of the argv argument. By using a C main program and passing the arguments argc and argv to a Fortran routine, I did get things to work - albeit in a rather primitive way:
The C main program calls a Fortran routine:
#include <tcl.h>
void ftcl_main( int argc, char **argv );
int main(int argc, char **argv) {
ftcl_main(&argc, argv);
return(1);
}The Fortran code looks like this (mimicking the interfaces to the two Tcl functions):
module ftcl
use iso_c_binding
implicit none
interface
integer function Tcl_Init( interp ) bind(C, name = "Tcl_Init")
import :: c_ptr
type(c_ptr), value :: interp
end function Tcl_Init
end interface
interface
integer function Tcl_Main( argc, argv, inifunc ) bind(C, name = "Tcl_Main")
import :: c_ptr, c_funptr
integer, value :: argc
type(c_ptr), value :: argv
type(c_funptr), value :: inifunc
end function Tcl_Main
end interface
contains
integer function ftcl_appinit( interp ) bind(C, name = "ftcl_appinit")
type(c_ptr), value :: interp
write(*,*) 'In ftcl_appinit'
ftcl_appinit = Tcl_Init( interp )
end function ftcl_appinit
integer function ftcl_main( argc, argv ) bind(C, name = 'ftcl_main')
integer, value :: argc
type(c_ptr), value :: argv
write(*,*) 'In ftcl_main'
ftcl_main = Tcl_Main( argc, argv, c_funloc(ftcl_appinit) )
end function ftcl_main
end module ftclThe ideal is to use a Fortran main program, but the argv argument presented some problems. It took me a bit of experimentation and soul searching, but this Fortran program gives me the tclsh I was looking for:
module ftcl
use iso_c_binding
implicit none
interface
integer function Tcl_Init( interp ) bind(C, name = "Tcl_Init")
import :: c_ptr
type(c_ptr), value :: interp
end function Tcl_Init
end interface
interface
integer function Tcl_Main( argc, argv, inifunc ) bind(C, name = "Tcl_Main")
import :: c_ptr, c_funptr
integer, value :: argc
type(c_ptr), value :: argv
type(c_funptr), value :: inifunc
end function Tcl_Main
end interface
contains
integer function ftcl_appinit( interp ) bind(C, name = "ftcl_appinit")
type(c_ptr), value :: interp
ftcl_appinit = Tcl_Init( interp )
end function ftcl_appinit
integer function ftcl_main( inifunc )
interface
integer function inifunc( interp) bind(C)
import :: c_ptr
type(c_ptr), value :: interp
end function inifunc
end interface
integer :: i, length, argc
type(c_ptr), dimension(:), allocatable, target :: argv
character(len=256), dimension(:), allocatable, target :: arg
argc = command_argument_count()
allocate( argv(argc+1), arg(argc+1) )
do i = 0,argc
call get_command_argument( i, arg(i+1) )
length = len_trim(arg(i+1))
arg(i+1)(length+1:) = c_null_char
argv(i+1) = c_loc(arg(i+1))
enddo
ftcl_main = Tcl_Main( argc+1, c_loc(argv), c_funloc(inifunc) )
end function ftcl_main
end module ftcl
program ftclsh
use iso_c_binding
use ftcl
implicit none
type(c_ptr), pointer :: argv
integer :: rc
rc = ftcl_main( ftcl_appinit )
end program ftclshMost of the above code is dedicated to mimicking the proper interfaces and can be put into a library. The iso_c_binding features hide all the platform dependencies: the program works with both gfortran and Intel Fortran oneAPI.