1

我正在尝试将 Tcl 变量链接到 C 变量,以便在 C 线程创建期间将指针传递给最新的并具有 TCL-C 线程共享变量(我认为我不能使用本机 TCL 线程共享变量函数) . 我很难将这两个变量联系起来。这是我的做法:

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

createThreadC函数创建一个 C 线程,返回其 ID 并尝试使用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;  
}

这是输出:

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

值改变了,正如 C 程序所必须的linkedVar那样,但它存储了错误的变量,应该是 1 而不是 35。是(char *) &linkedVar错误的转换吗?

4

2 回答 2

3

Tcl_LinkVar 几乎正确地使用了;您的原始代码类型正确。但这不是错的!

问题是您在 Tcl 解释器(具有相当长的生命周期)和 C 堆栈上具有较短生命周期的变量之间进行链接。之后createThreadC_Cmd,链接指向未使用的堆栈,并且通常在之后立即用于其他内容。这是形式上未定义的行为,而且非常糟糕。您需要做的是确保 C 变量的生命周期至少与解释器的生命周期一样长。

最简单的解决方法是使用全局(或static局部)变量。唯一的缺点是相同的变量将在所有对createThreadC_Cmd;的调用之间共享。有时这根本不是问题,但我怀疑你的情况并非如此。因此,您需要在其他地方分配一些空间。如果您不希望您创建的口译员消失,那么最便宜的方法就是使用malloc获得一点空间,然后将链接指向该空间;然后你可以泄漏内存并停止担心它(它不干净,但很容易做到)。如果你想清理,你几乎做同样的事情,但注册一个适当的关闭挂钩作为free内存;根据实际消失的情况,Tcl 有三种关闭钩子:

  1. 解释器关闭钩子是用Tcl_CallWhenDeleted
  2. 线程关闭钩子是用Tcl_CreateThreadExitHandler
  3. 进程/库关闭挂钩是使用创建的Tcl_CreateExitHandler(除非您需要超级干净,否则您不需要它来删除内存;警告,在调用这些挂钩时很难立即删除内存)。

我不太确定哪个适合您;这取决于您共享变量的范围。(我希望您不打算跨线程共享它;按照设计,这不会很好地工作。)

于 2012-09-07T10:50:47.747 回答
1

我打算和Donal说同样的话,但也写了一个演示。所以在这里 - 基本上你的链接变量生命周期应该与解释器生命周期相匹配。

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

用法(也使用 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

这显示了一种通过利用命令清理功能进行清理的方法。

如果您使用多个线程并且周围还有 Tcl 解释器,则必须非常小心。Tcl interp 与创建它的线程相关联。因此,如果您希望共享结构在 C 线程之间传递,您应该松开 interp 成员。在那种情况下,我会让应用程序处理这个结构生命周期。如果您的 Shared 结构的生命周期比任何解释器的生命周期都长,该解释器的命令是 clientData,那么一切都会好起来的,您不需要在解释器中进行清理。

于 2012-09-07T10:55:23.120 回答