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 1
As 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 ftcl
The 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 ftclsh
Most 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.