API TCL-C: utilisation de la fonction Tcl_LinkVar

J’essaie de lier une variable Tcl à une variable C afin de transmettre le pointeur au dernier en cours de création du thread C et de disposer d’une variable partagée de thread TCL-C (je ne pense pas pouvoir utiliser les fonctions de variable partagée de thread TCL natives) . J’ai quelques difficultés à relier les deux variables. Voici comment je fais:

#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" 

La fonction createThreadC crée un thread C, renvoie son ID et tente de créer un lien avec 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; } 

Voici la sortie:

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

La valeur de linkedVar changé, comme le programme C devait le faire, mais elle stocke la mauvaise variable, elle devrait être 1 au lieu de 35. Est-ce que la dissortingbution (char *) &linkedVar est incorrecte?

J’allais dire la même chose que Donal, mais j’ai aussi écrit une démo. Donc, la voici: en gros, votre durée de vie de variable liée doit correspondre à celle d’interprète.

 #include  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; } 

Utilisation (construction aussi en utilisant 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 

Cela montre une façon de nettoyer les choses en utilisant la fonction de nettoyage de commande.

Vous devez faire très attention si vous utilisez plusieurs threads et que des interpréteurs Tcl sont également présents. Une interp Tcl est liée au thread sur lequel elle a été créée. Ainsi, si vous souhaitez que la structure partagée soit transmise à des threads C, vous devez perdre le membre interp. Dans ce cas, l’application gérerait cette durée de vie de la structure. Si votre structure partagée a une durée de vie plus longue que tout interpréteur ayant une commande pour laquelle il s’agit de clientData, tout ira bien et vous n’avez pas besoin de nettoyer l’intérieur de l’interpréteur.

Vous utilisez Tcl_LinkVar presque correctement; votre code d’origine est correct. Mais ce n’est pas ce qui ne va pas!

Le problème est que vous faites le lien entre un interpréteur Tcl (avec une durée de vie assez longue) et une variable sur la stack C avec une durée de vie courte. Après createThreadC_Cmd , le lien pointe vers une stack inutilisée, qui est souvent utilisée pour autre chose immédiatement après. C’est un comportement formellement indéfini, et c’est très mauvais . Ce que vous devez faire, c’est vous assurer que la durée de vie de la variable C est au moins aussi longue que celle de l’interprète.

Le correctif le plus simple consiste à utiliser une variable globale (ou locale static ). Le seul createThreadC_Cmd est que la même variable sera partagée entre tous les appels à createThreadC_Cmd ; parfois ce n’est pas un problème du tout, mais je suppose que ce n’est pas le cas dans votre cas. Vous devez donc allouer de l’espace ailleurs. Le moyen le moins coûteux de le faire, à condition de ne pas s’attendre à ce que les interprètes créés disparaissent, consiste simplement à utiliser malloc pour obtenir un peu de place, puis d’indiquer le lien; vous pouvez alors laisser fuir la mémoire et cesser de vous inquiéter à ce sujet (c’est impur, mais très facile à faire). Si vous voulez nettoyer, vous faites pratiquement la même chose mais enregistrez un crochet d’arrêt approprié qui free la mémoire; Tcl a trois sortes de crochets d’arrêt, en fonction de ce qui se passe réellement:

  1. Les Tcl_CallWhenDeleted arrêt de l’ interpréteur sont créés avec Tcl_CallWhenDeleted
  2. Les points d’ arrêt de thread sont créés avec Tcl_CreateThreadExitHandler
  3. Les crochets d’arrêt de processus / bibliothèque sont créés avec Tcl_CreateExitHandler (vous n’en avez pas besoin pour supprimer de la mémoire, sauf si vous avez besoin d’être ultra-propre; avertissement, il est très difficile d’obtenir la suppression de la mémoire au moment où ils sont appelés) .

Je ne sais pas trop ce qui vous convient. cela dépend de la manière dont vous partagez la variable. (J’espère que vous n’envisagez pas de le partager sous forme de threads; cela ne fonctionnera pas bien, par conception.)