douqian9729
douqian9729
2019-05-30 20:13
浏览 179

从Tcl脚本调用golang函数

We use a third party Tcl parsing library to validation Tcl script for both syntax and semantic checking. The driver was written in C and defined a set of utility functions. Then it calls Tcl_CreateObjCommand so the script could call these C functions. Now we are in the process of porting the main program to go and I could not find a way to do this. Anyone know a way to call golang functions from Tcl script?

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;
}
  • 点赞
  • 写回答
  • 关注问题
  • 收藏
  • 邀请回答

1条回答 默认 最新

  • dongluedeng1524
    dongluedeng1524 2019-06-01 06:59

    Assuming you've set the relevant functions as exported and built the Go parts of your project as in

    Using Go code in an existing C project

    […]

    The important things to note are:

    • The package needs to be called main
    • You need to have a main function, although it can be empty.
    • You need to import the package C
    • You need special //export comments to mark the functions you want callable from C.

    I can compile it as a C callable static library with the following command:

    go build -buildmode=c-archive foo.go
    

    Then the core of what remains to be done is to write the C glue function from Tcl's API to your Go code. That will involve a function something like:

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

    (Credit to https://medium.com/learning-the-go-programming-language/calling-go-functions-from-other-languages-4c7d8bcc69bf for providing enough information for me to work out some of the type bindings.)

    That's then the function that you register with Tcl as the callback.

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

    Theoretically, a command registration can fail. Practically, that only happens when the Tcl interpreter (or a few critical namespaces within it) is being deleted.


    Mapping a failure into Tcl is going to be easiest if it is encoded at the Go level as an enumeration. Probably easiest to represent success as zero. With that, you'd then do:

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

    Passing back more complex return types with tuples of values (especially a combination of a success indicator and a “real” result value) should also be possible, but I've not got a Go development environment in order to probe how they're mapped at the C level.

    点赞 评论

相关推荐