1

I want to operate on a file from C using the tcl C API. From within tcl, this is what I would do:

% set file [open "my_file"]
file3
% myfunc::load $file

where myfunc::load is from a C extension:

#include <tcl/tcl.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>

#define NS "myfunc"

static int
Load_Cmd(ClientData cdata, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) {
    if (objc != 2) {
        Tcl_SetObjResult(interp,
                         Tcl_NewStringObj("wrong # args: should be \" " NS "::open file\"",
                                          -1));
        return TCL_ERROR;
    }

    Tcl_Obj *o = objv[1];
    char *chan_name = Tcl_GetString(o);

    Tcl_Channel chan = Tcl_GetChannel(interp, chan_name, NULL);
    if (chan == NULL) {
        return TCL_ERROR;
    }

    const Tcl_ChannelType * type = Tcl_GetChannelType(chan);

    if (!strcmp(type->typeName, "file")) {
        return TCL_ERROR;
    }

    return TCL_OK;
}

int DLLEXPORT
Myfunc_Init(Tcl_Interp *interp) {
    Tcl_InitStubs(interp, TCL_VERSION, 0);
    Tcl_CreateNamespace(interp, NS, NULL, NULL);

    Tcl_CreateObjCommand(interp, NS "::load", Load_Cmd, NULL, NULL);
    Tcl_PkgProvide(interp, "myfunc", "1.0");
    return TCL_OK;
}

Is there a way to get the FILE pointer associated with the TCL channel?

I have tried the following:

FILE *data =  Tcl_GetChannelInstanceData(chan);

FILE *fp = malloc(sizeof(FILE));
Tcl_GetChannelHandle(chan, TCL_READABLE, (ClientData *) fp);

FILE *fp = malloc(sizeof(FILE));
Tcl_GetOpenFile(interp, chan_name, /*forWriting=*/ 0, /*checkUsage=*/1, (ClientData *) fp);

but none of these seem to work and end in a Segmentation fault, i.e.

% myfunc::load $file
Segmentation fault (core dumped)
Lukas
  • 13
  • 3

2 Answers2

3

Tcl does not use C stdio on any platform; stdio is not very good at handling asynchronous I/O. Instead, Tcl does direct system calls wherever it can; on some platforms, this lets it avoid some really nasty bugs.

On all POSIX platforms (including both Linux and macOS) the underlying handle is actually always a file descriptor, which is of type int. On Windows, it's potentially one of many things; often a HANDLE of some kind, sometimes something more complex.

int fd;

if (Tcl_GetChannelHandle(chan, TCL_READABLE, (ClientData *) &fd) != TCL_OK) {
     // Some sort of failure...
     return TCL_ERROR;
}

On POSIX platforms only, you also can do:

FILE *file;

if (Tcl_GetOpenFile(interp, channelName, 0, 1, (ClientData *) &file) != TCL_OK) {
     // Some sort of failure...
     return TCL_ERROR;
}

This has a somewhat odd type signature; the final argument should be a FILE **, but isn't to avoid binding lots of stdio directly into Tcl's API. Tcl still doesn't internally use FILE * for channels, but in this case will wrap the file descriptor into one for you (with fdopen()).

Donal Fellows
  • 133,037
  • 18
  • 149
  • 215
0

I'm assuming that's three different efforts to get the file pointer. I only have information on the last one, and I think this is a case of misreading the man page for the function Tcl_GetOpenFile

What you need to do is:

FILE *fp = 0;
Tcl_GetOpenFile(interp, chan_name, 0, 1, (ClientData *) &fp);

… and don't forget to check the return code from the function. If you read the source for this function, then you can see the implementation:

The last parameter to the function: void **filePtr

which is assigned as: *filePtr = f;, where f is the result from an fdopen.

i.e. it's just assigning the FILE * to the content of what was passed in.

Anya Shenanigans
  • 91,618
  • 3
  • 107
  • 122