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?
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.
Usage (building too using msvc 6):
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.
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 tocreateThreadC_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 usemalloc
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 whichfree
s the memory; Tcl has three sorts of shutdown hooks, according to what is actually going away:Tcl_CallWhenDeleted
Tcl_CreateThreadExitHandler
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.)