0
votes

I am working on app which uses tcl package implemented in C++ and linked as static library (app is developed long time ago). It does following:

// Library code
extern "C" int  testlib_SafeInit _ANSI_ARGS_((Tcl_Interp *interp))
{
    return Tcl_PkgProvide(interp, "testlib", "1.6");
}

extern "C" int testlib_Init _ANSI_ARGS_((Tcl_Interp *interp))
{
    return testlib_SafeInit(interp);
}


// Application code
extern "C" int  testlib_SafeInit _ANSI_ARGS_((Tcl_Interp *interp));
extern "C" int testlib_Init _ANSI_ARGS_((Tcl_Interp *interp));

int main()
{
    Tcl_Interp* interp = Tcl_CreateInterp();
    Tcl_Init(interp);
    Tcl_PkgProvide(interp, "testlib", "1.6");
    Tcl_StaticPackage(interp, "testlib", testlib_Init, testlib_SafeInit);
    Tcl_Eval(interp, "package require testlib");
    std::cout << "Res = " << Tcl_GetStringResult(interp);
    return 0;
}

When I am removing line Tcl_PkgProvide(interp, "testlib", "1.6"); from main, package becomes invisible. Also I have noticed that testlib_Init and testlib_SafeInit are not called. I am expecting that they must be called from package require testlib. As I understand from docs each package must have pkgIndex.tcl in auto_path or tcl_pkgPath which must contain line (package ifneeded testlib 1.6 {load {} testlib}), but here both variables does not contain such index file.

Is this a correct way of providing packages? Is there a documentation related with providing packages using static libraries?

1

1 Answers

1
votes

Well, the simplest technique for statically providing a package is to just install it directly. The package init code should be the one calling Tcl_PkgProvide — you don't do so from main() usually — and you probably don't need Tcl_StaticPackage at all unless you're wanting to install the code into sub-interpreters.

int main(int argc, char*argv[])
{
    Tcl_FindExecutable(argv[0]);

    Tcl_Interp* interp = Tcl_CreateInterp();
    Tcl_Init(interp);
    testlib_Init(interp);
    // OK, setup is now done

    Tcl_Eval(interp, "package require testlib");
    std::cout << "Res = " << Tcl_GetStringResult(interp) << "\n";
    return 0;
}

However, we can move to using Tcl_StaticPackage. That allows code to say “instead of loading a DLL with this sort of name, I already know that code: here are its entry points”. If you are doing that, you need to also install a package ifneeded script; those are done through the script API only.

int main(int argc, char*argv[])
{
    Tcl_FindExecutable(argv[0]);

    Tcl_Interp* interp = Tcl_CreateInterp();
    Tcl_Init(interp);
    Tcl_StaticPackage(interp, "testlib", testlib_Init, testlib_SafeInit);
    Tcl_Eval(interp, "package ifneeded testlib 1.6 {load {} testlib}");
    // OK, setup is now done

    Tcl_Eval(interp, "package require testlib");
    std::cout << "Res = " << Tcl_GetStringResult(interp) << "\n";
    return 0;
}

The testlib in the load call needs to match the testlib in the Tcl_StaticPackage call. The testlib in the package require, package ifneeded and Tcl_PkgProvide also need to all match (as do the occurrences of 1.6, the version number).


Other minor issues

Also, you don't need to use the _ANSI_ARGS_ wrapper macro. That's utterly obsolete, for really ancient and crappy compilers that we don't support any more. Just replace _ANSI_ARGS_((Tcl_Interp *interp)) with (Tcl_Interp *interp). And remember to call Tcl_FindExecutable first to initialise the static parts of the Tcl library. If you don't have argv[0] available to pass into it, use NULL instead; it affects a couple of more obscure introspection systems on some platforms, but you probably don't care about them. However, initialising the library overall is very useful: for example, it lets you make sure that the filesystem's filename encoding scheme is correctly understood! That can be a little important to code…