Skip to content

Python's Tcl interpreter does not link shared objects with Tcl library by default #127858

@MayeulC-ST

Description

@MayeulC-ST

Bug report

Bug description:

Hello,

I am using Python's Tcl interpreter roughly as suggested in https://wiki.tcl-lang.org/page/Python-Tcl-Interactions

import tkinter
interpreter = tkinter.Tcl()
interpreter.eval('puts "hello from Tcl"')

And everything works fine. However, this is a large Tcl legacy codebase, and some procedures are provided as shared objects that are loaded with Tcl's load procedure:

load -global -- /path/to/shared_object.so myso

This works for some shared objects, but not all: some imports fail with

E   _tkinter.TclError: couldn't load file "/path/to/shared_object.so": /path/to/shared_object.so: undefined symbol: Tcl_NewStringObj

I have reproduced this error by compiling a sample shared object and omitting -ltcl when compiling it:

// Example inspired from https://www.tcl-lang.org/man/tcl/TclCmd/load.htm
// Compile with: gcc -shared -o tclext.so -fPIC tclext.c -ltcl
// load in tclsh with: load -- tclext.so Foo
// Then you can call the command foo
#include <tcl.h>
static int fooCmd(void *clientData,
        Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) {
    // Returns a Tcl string "called with %d arguments\n"
    Tcl_Obj *result = Tcl_NewStringObj("called with ", -1);
    Tcl_AppendPrintfToObj(result, "%d", objc);
    Tcl_AppendToObj(result, " arguments\n", -1);
    Tcl_SetObjResult(interp, result);
    return TCL_OK;
}
int Foo_Init(Tcl_Interp *interp) {
    if (Tcl_InitStubs(interp, "8.1", 0) == NULL) {
        return TCL_ERROR;
    }
    // Create foo command
    Tcl_CreateObjCommand(interp, "foo", fooCmd, NULL, NULL);
    return TCL_OK;
}

In python, the above can be loaded just fine with:

import tkinter as tk
tcl = tk.Tcl()
tcl.eval('''
load ./tclext_str.so Foo
set abc [foo]
puts "abc contains ``$abc''"
''')

After compiling the .so with:

gcc -shared -o tclext_str.so -fPIC tclext_str.c -ltcl 

However, Python fails to load the .so (with the aforementioned error) when it is compiled without -ltcl:

gcc -shared -o tclext_str.so -fPIC tclext_str.c

The same load statement works perfectly from tclsh.

My suspicion is that tclsh implicitly links everything with the tcl library, while this is not the case for Python's Tcl interpreter. I think this should be fixed, if possible.

As a workaround, I can patchelf --add-needed libtcl.so tclext_str.so but I am unsure about the drawbacks, and this difference in behavior remains problematic for sharing a unified codebase.

CPython versions tested on:

3.9, 3.10, 3.11, 3.12

Operating systems tested on:

Linux

Metadata

Metadata

Assignees

No one assigned

    Labels

    pendingThe issue will be closed if no feedback is providedtopic-tkintertype-bugAn unexpected behavior, bug, or error

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions