Experimenting with KitCreator

Difference between version 0 and 0 - Previous - Next
[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 [http://kitcreator.rkeene.org/fossil/index] 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:

======none
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 http://www.rkeene.org/devel/kitcreator/kitbuild/nightly/%|%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`:

======c
#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 [http://www.equation.com] 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:

======tcl
> 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:
======c
#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):
======none
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`.

<<categories>>Example|Tclkit