Version 18 of load

Updated 2012-06-19 03:23:45 by AMG

Dynamically links a code library (typically written in C, C++, or some comparable language) with the program containing the Tcl interpreter. Most extensions use this mechanism.

load fileName ?packageName? ?interp?

http://www.purl.org/tcl/home/man/tcl8.5/TclCmd/load.htm

If fileName does not contain any directory separators, the library will be searched for in OS-specific ways. If packageName is not provided, it is derived from the fileName by trimming certain prefixes and suffixes and applying capitalization rules (so /usr/local/lib/libfoobar.so would become Foobar); it is used to locate the name of the initialization function inside the library. If interp is not supplied, the library is initialized for the current interpreter.

  • What does one need to create an extension to be loaded?
  • What does one need to load Tk?

Clif Flynt has made available a useful C extension to load shared libraries that don't know anything about tcl's _Init function.

The extension allows one to invoke dlopen to load libraries that do not have Tcl entry points (for use by other things that do have legitimate foo_Init entry points).

The dlopen extension is available at:

http://noucorp.com/cgi-bin/noucorp/generic.tcl?dir=/var/www/html/tcl/utilities

It's a minimal, single C file extension, but it has been known to work on both Windows and Posix (just retested on Linux)


Basics of DLL Creation for Tcl extension

You have a design for some software that will be implemented in a dll written in C (or C++ code with extern "C" {} around it). You must write the active code in a function, and add some initialisation code to register the dll's functions with tcl (Tcl_CreateCommand or Tcl_CreateObjCommand is faster).

The initialisation required in the code that compiles to make tclext.dll (the Tclext_Init name is composed from the dll name by Tcl):

  // this is your useful procedure. It can be a C++ or C linked procedure.
 int tclextfunction (ClientData clientData, Tcl_Interp *interp, int argc, char *argv[]) {
     // dosomething - clientdata is a pointer to something the programmer may or may not define
     return TCL_OK;
 }
 #ifdef __cplusplus
 extern "C" {
 #endif
 int PROBE_EXPORT Tclext_Init(Tcl_Interp *interp)
 {
 //        tcl stubs and tk stubs are needed for dynamic loading, you must have this set as a compiler option
 #ifdef USE_TCL_STUBS
    if (Tcl_InitStubs(interp, TCL_VERSION, 1) == NULL) {
                        Tcl_SetResult(interp, "Tcl_InitStubs failed",TCL_STATIC);
                return TCL_ERROR;
    }
 #endif
 #ifdef USE_TK_STUBS
    if (Tk_InitStubs(interp, TCL_VERSION, 1) == NULL) {
                        Tcl_SetResult(interp, "Tk_InitStubs failed",TCL_STATIC);
                return TCL_ERROR;
    }
 #endif
    Tcl_CreateCommand( "tclextfunction", tclextfunction ); // register your functions with Tcl
 }
 #ifdef __cplusplus
 }
 #endif

Then in Tcl script use

  load tclext.dll
  tclextfunction  a b c

to pass the strings a,b,c to tclextfunction in the array argv - argc will be 3 of course. The ClientData is a pointer to anything (perhaps a global data structure or a class defined by a ClientData statement often dynamically allocated in the Tclext_Init function).

The reader should be able to find all the required .h files from the net without any more help from me.


So what do you do when load, or, even more mysteriously, package, fails? There are many, many ways for this to happen, and typical built-in diagnostics from supplied loaders are inscrutable.

One handy tool for such situations is a dependency utility. Under Windows, DependencyWalker [L1 ] walks dependencies. When you let it load a specific dll, it assists you in determining what other DLLs it needs.


AMG: I just had a very strange problem with [load]. When I screw up and try to call my command without loading the the package first, of course I get an error for the command, but I also get an error when trying to load the package. When I do things in the right order, everything works. Yeah I know, I shouldn't be trying to do things backwards, but I still wonder why the load fails.

[andy@toaster|~/e6b]$ tclsh
% atc::get_dnlink
invalid command name "atc::get_dnlink"
% load atc.so
couldn't load file "atc.so": atc.so: cannot open shared object file: No such file or directory
[andy@toaster|~/e6b]$ tclsh
% load atc.so
% atc::get_dnlink ABCD
conversion failed
% info patchlevel
8.6b2
% exec uname -a
Linux toaster 3.4.0-andy #2 Fri Jun 1 23:52:05 CDT 2012 i686 Pentium II (Deschutes) GenuineIntel GNU/Linux

Just so you know, that "conversion failed" message is the right result.

This library is not stubs-enabled. It also does not link against -ltcl8.6. Update: linking against -ltcl8.6 doesn't help; this error still happens.

I'd file a bug report, but the above is all the information I have at the moment. It seems like more is needed, yet I don't currently have any time to research further.

Update: here's another rather interesting failure mode:

[andy@toaster|~/e6b]$ tclsh
% load per.so
% per::get_integer ABCD 0 4
10
% load atc.so
couldn't load file "atc.so": atc.so: cannot open shared object file: No such file or directory
[andy@toaster|~/e6b]$ tclsh
% load atc.so
% load per.so
couldn't load file "per.so": per.so: cannot open shared object file: No such file or directory

Even when the first command doesn't result in an error, if the second or subsequent command is a [load], it fails. This makes it impossible to load more than one library.

The next step for me is to build the very latest Tcl and see if the problem persists.

Update 2: I just built the most recent trunk (1581f9beac5715890d105a41090756a89b0f3630 2012-06-15 09:39:34 UTC), but the problem persists, exactly the same as before. The system is running Slackware 13.1, if that helps. I might be able to fiddle around in gdb if anyone can point me toward something I ought to be looking at.

Update 3: This is only a problem for interactive use. Everything works fine when [load] is called from a script.