TCL-C API: Tcl_LinkVar function use

2019-07-31 06:27发布

I am trying to link a Tcl variable to a C variable in order to pass the pointer to the latest during C thread creation and have a TCL-C thread shared variable (I don't think I can use native TCL Thread Shared Variable functions). I have some difficulties to link both variables. Here is how I do:

#Tcl code, calling the C function:
set linkedVar 98
puts "linkedVar: $linkedVar"
load [file join [pwd] libCextension[info sharedlibextension]]
set threadId [createThreadC]
puts "Created thread n° $threadId"
puts "linkedVar: $linkedVar"

The createThreadC function creates a C thread, return its ID and tries to create a link with linkedVar.

// C function called by Tcl
static int
createThreadC_Cmd(
    ClientData cdata,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *const objv[])
{
    int linkedVar=2;
    Tcl_LinkVar(interp, "linkedVar", (char *) &linkedVar, TCL_LINK_INT);
    linkedVar=1;
    ...
    # Thread creation, return Tcl object with thread ID
    ...
    return TCL_OK;  
}

Here is the output:

linkedVar: 98
Created thread n° -1227199680
linkedVar: 35

The linkedVar value changed, as C program had to, but it stores the wrong variable, should be 1 instead of 35. Is it the (char *) &linkedVar cast which is wrong?

标签: c tcl
2条回答
一纸荒年 Trace。
2楼-- · 2019-07-31 07:07

I was going to say the same stuff as Donal, but also wrote a demo. So here it is - basically your linked variable lifetime should match the interpreter lifetime.

#include <tcl.h>

typedef struct Shared {
    Tcl_Interp *interp;
    int id;
} Shared;

static int
UpdateCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
{
    Shared *sharedPtr = (Shared *)clientData;
    if (objc != 1) {
        Tcl_WrongNumArgs(interp, 1, objv, "");
        return TCL_ERROR;
    }
    ++sharedPtr->id;
    return TCL_OK;
}
static void
DeleteProc(ClientData clientData)
{
    Shared *sharedPtr = (Shared *)clientData;
    Tcl_UnlinkVar(sharedPtr->interp, "shared_id");
    Tcl_Release(sharedPtr->interp);
    Tcl_Free(clientData);
}

int DLLEXPORT
Testlink_Init(Tcl_Interp *interp)
{
    Shared *sharedPtr;
    if (Tcl_InitStubs(interp, "8.4", 0) == NULL) {
        return TCL_ERROR;
    }
    sharedPtr = (Shared *)Tcl_Alloc(sizeof(Shared));
    sharedPtr->interp = interp;
    sharedPtr->id = 0;
    Tcl_Preserve(sharedPtr->interp);
    Tcl_LinkVar(interp, "shared_id", (char *)&sharedPtr->id, TCL_LINK_INT);
    Tcl_CreateObjCommand(interp, "update_shared", UpdateCmd, sharedPtr, DeleteProc);
    Tcl_PkgProvide(interp, "testlink", "1.0");
    return TCL_OK;
}

Usage (building too using msvc 6):

C:\src>cl -nologo -Od -MD -I\opt\tcl\include -DUSE_TCL_STUBS -c tcl_link.c
tcl_link.c

C:\src>link -dll -debug -out:tcl_link.dll tcl_link.obj \opt\tcl\lib\tclstub85.lib
Microsoft (R) Incremental Linker Version 6.00.8447
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.

C:\src>tclsh
% load tcl_link.dll testlink
% set shared_id
0
% update_shared
% set shared_id
1

This shows one way to clean things up by leveraging the command cleanup function.

You must be very careful if you use multiple threads and also have Tcl interpreters around. A Tcl interp is tied to the thread it was created on. So if you want the Shared structure to be passed among C threads you should loose the interp member. In that case I'd have this structure lifetime being handled by the application. Provided your Shared structure has a lifetime that is longer than any interpreter that has a command for which this is the clientData then everything will be ok and you don't need to cleanup within the interpreter.

查看更多
叼着烟拽天下
3楼-- · 2019-07-31 07:10

You are using Tcl_LinkVar almost correctly; your original code is type-correct. But that's not what's wrong!

The problem is that you're linking between a Tcl interpreter (with a fairly long lifetime) and a variable on the C stack with a short lifetime. After createThreadC_Cmd, the link points at unused stack, and that often gets used for something else immediately after. This is formally undefined behavior, and it is very bad. What you need to do is to make sure that the lifetime of the C variable is at least as long as that of the interpreter.

The simplest fix is to use a global (or static local) variable. The only down-side of that is that the same variable will be shared between all calls to createThreadC_Cmd; sometimes that's not a problem at all, but I suspect that's not so in your case. So instead you need to allocate some space elsewhere. The cheapest way of doing that, provided you're not expecting the interpreters you create to go away, is to just use malloc to get a little room and then point the link into that; you can then leak the memory and stop worrying about it (it's unclean, but very easy to do). If you want to clean up, you do virtually the same thing but register an appropriate shutdown hook which frees the memory; Tcl has three sorts of shutdown hooks, according to what is actually going away:

  1. Interpreter shutdown hooks are created with Tcl_CallWhenDeleted
  2. Thread shutdown hooks are created with Tcl_CreateThreadExitHandler
  3. Process/library shutdown hooks are created with Tcl_CreateExitHandler (you don't need this for deleting memory unless you've got a need to be uber-clean; warning, it's very hard to get memory deletion right at the point when these are called).

I'm not quite sure which is appropriate for you; it depends on how widely you are sharing the variable. (I hope you're not planning to share it across threads; that won't work well, by design.)

查看更多
登录 后发表回答