【问题标题】:Call golang function from Tcl sript从 Tcl 脚本调用 golang 函数
【发布时间】:2019-05-30 20:13:17
【问题描述】:

我们使用第三方 Tcl 解析库来验证 Tcl 脚本的语法和语义检查。该驱动程序是用 C 语言编写的,并定义了一组实用函数。然后它调用Tcl_CreateObjCommand 以便脚本可以调用这些C 函数。现在我们正在移植主程序,但我找不到这样做的方法。有人知道从 Tcl 脚本调用 golang 函数的方法吗?

static int
create_utility_tcl_cmds(Tcl_Interp* interp)
{
    if (Tcl_CreateObjCommand(interp, "ip_v4_address",
                        ip_address, (ClientData)AF_INET, NULL) == NULL) {
        TCL_CHECKER_TCL_CMD_EVENT(0, "ip_v4_address");
        return -1;
    }

    .....
    return 0;
}

【问题讨论】:

  • 您可以使用 cgo 从 C 代码中调用 Go 函数,Google 搜索 go tcl 得到的第一个结果是:github.com/nsf/gothic
  • 新的主程序将进入,因此选项 1 退出。至于第二个,我会更深入地研究,但我认为它可以与我们的第三方 tcl 库一起使用,因为它已经很老了
  • cgo 无论如何都将成为与非 Go 代码交互的解决方案(即使我链接的库也是一个 cgo 包装器)。如有必要,您可以为您的特定库编写自己的绑定。
  • Tcl_CreateCommand 注册的回调函数(一开始可能更容易处理)接受一个 C 字符串数组,这些字符串是函数的参数字。客户端数据只是一个任意指针 (void*),Tcl 没有解释就传回它,如果不需要它可以为 NULL;在 C++ 绑定中,它用于保存对象的 thunk,该命令是该对象的方法调度程序。

标签: go tcl


【解决方案1】:

假设您已将相关函数设置为导出并构建了项目的 Go 部分,如下所示

Using Go code in an existing C project

[…]

需要注意的重要事项是:

  • 包需要调用main
  • 您需要有一个main 函数,尽管它可以为空。
  • 需要导入包C
  • 您需要特殊的//export cmets 来标记您希望从 C 中调用的函数。

我可以使用以下命令将其编译为 C 可调用静态库:

go build -buildmode=c-archive foo.go

那么剩下要做的核心就是将 C 粘合函数从 Tcl 的 API 编写到您的 Go 代码中。这将涉及类似以下的功能:

static int ip_address_glue(
        ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) {
    // Need an explicit cast; ClientData is really void*
    GoInt address_family = (GoInt) clientData;

    // Check for the right number of arguments
    if (objc != 2) {
        Tcl_WrongNumArgs(interp, 1, objv, "address");
        return TCL_ERROR;
    }

    // Convert the argument to a Go string
    GoString address;
    int len;
    address.p = Tcl_GetStringFromObj(objv[1], &len);
    address.n = len; // This bit is hiding a type mismatch

    // Do the call; I assume your Go function is called ip_address
    ip_address(address_family, address);

    // Assume the Go code doesn't fail, so no need to map the failure back to Tcl

    return TCL_OK;
}

(感谢https://medium.com/learning-the-go-programming-language/calling-go-functions-from-other-languages-4c7d8bcc69bf 为我提供了足够的信息来计算一些类型绑定。)

这就是你在 Tcl 中注册为回调的函数。

Tcl_CreateObjCommand(interp, "ip_v4_address", ip_address_glue, (ClientData)AF_INET, NULL);

理论上,命令注册可能会失败。实际上,只有在删除 Tcl 解释器(或其中的一些关键名称空间)时才会发生这种情况。


如果在 Go 级别将故障编码为枚举,则将故障映射到 Tcl 将是最容易的。可能最容易将成功表示为零。有了它,你会这样做:

GoInt failure_code = ip_address(address_family, address);

switch (failure_code) {
case 0: // Success
    return TCL_OK;
case 1: // First type of failure
    Tcl_SetResult(interp, "failure of type #1", TCL_STATIC);
    return TCL_ERROR;
// ... etc for each expected case ...
default: // Should be unreachable, yes?
    Tcl_SetObjResult(interp, Tcl_ObjPrintf("unexpected failure: %d", failure_code));
    return TCL_ERROR;
}

使用值元组(尤其是成功指示器和“真实”结果值的组合)传回更复杂的返回类型也是可能的,但我没有 Go 开发环境来探究它们是如何实现的'在 C 级别映射。

【讨论】:

  • 是错误的映射使这一切变得不那么顺利; GoInt 实际上与Tcl_WideInt 是同一类型,而GoFloat64 在Tcl API 中被称为普通的旧double;这些都是微不足道的。上面的代码说明了如何处理GoString。但是错误...... Tcl 是一种在概念上使用异常的语言(尽管不是 C++ 意义上的!)而 Go 非常不是(而且我不知道是否普遍遵循错误处理的约定)所以那里存在相当大的阻抗不匹配.
  • 另外,注意 Tcl 关心线程! Tcl 解释器强烈绑定到单个 POSIX 线程(它是多对一的关系;线程可以有多个解释器)在其实现中非常深入。从 Go 调用 Tcl 时,需要尊重使用Tcl_ThreadQueueEvent()从其他地方异步发布事件,这可能会有点复杂……
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-03-17
  • 1970-01-01
  • 1970-01-01
  • 2011-05-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多