0
votes

My C++ applicaton embeds TCL (8.6.7) and needs to get data via Windows named pipes (dll) and TCP socket (TCL script) at the same time. I want to use TCL threads to achieve the simultaneous data acquisition via the pipe and socket. One thread (threadPipe) via TCL C api would manage data reading from the named pipe and another thread via TCL script (threadTclSocket) would manage the socket. The main thread (the C++ application) via TCL script initiates the data acquisition and it waits (non-blocking) for the worker threads using vwait forever in its TCL script.

My problem:

  • How to do I get the TCL C thread (threadPipe) and the thread via TCL script (threadTclSocket) to both set the variable: forever of the main application when they are done so that the main thread can exit its event loop?
  • I have read about Tcl_ThreadQueueEvent and Tcl_ThreadAlert, but I don't understand how they could be used to set variable forever in the main thread.

Any suggestions would be appreciated.

2
You could use the twapi extension to use named pipes in Tcl. - Johannes Kuhn
@JohannesKuhn thanks, the named pipes is already implemented as dll. - yaro
The usual idiom is something like while {$waitcount < 2} {vwait waitcount} and increasing the variable waitcount when the other threads are finished. - Johannes Kuhn
Thanks @JohannesKuhn. My current difficulty is how do I get the TCL C thread call to set the variable waitcount and return control to the main TCL interpreter. It seems to hang after set I the variable in the TCL_EventProc. - yaro
After each thread is done, it should use Tcl_ThreadQueueEvent to queue an event in the main thread that uses Tcl_Eval with incr ::waitcount. - Johannes Kuhn

2 Answers

2
votes

You should probably use the twapi named pipe implementation. I suspect your named pipe reading code is not presenting this as a channel that supports the tcl fileevent asynchronous API. If you had such support, as is found in the twapi version, then you don't need the additional thread at all. You simpley use fileevent and have some tcl procedure called when data arrives on the channel.

If you persist with the existing code then you use Tcl_ThreadQueueEvent to post an event to the tcl notifier so that the interpreter thread will then call a function when it processes that event. This shifts control to the interpreter thread. Here is an example from something I wrote previously:

static void MailslotSignalledProc(void *clientData)
{
    MailslotData *slotPtr = (MailslotData *)clientData;
    MailslotEvent *evPtr = NULL;
    DWORD cbRead, cbSize = 0, cQueued = 0;
    BOOL br = FALSE;

    Tcl_GetLongFromObj(NULL, slotPtr->sizeObj, &cbSize);
    GetOverlappedResult(slotPtr->handle, &slotPtr->ov, &cbRead,  FALSE);
    do
    {
        evPtr = (MailslotEvent *)ckalloc(sizeof(MailslotEvent));
        evPtr->header.proc = EventProc;
        evPtr->header.nextPtr = NULL;
        evPtr->interp = slotPtr->pkgPtr->interp;
        Tcl_Preserve(evPtr->interp);
        evPtr->slotPtr = slotPtr;
        evPtr->messageObj = Tcl_NewByteArrayObj(slotPtr->message, cbRead);
        Tcl_IncrRefCount(evPtr->messageObj);
        Tcl_ThreadQueueEvent(slotPtr->wait.tid, (Tcl_Event *)evPtr, TCL_QUEUE_TAIL);

        /*
         * Schedule another read on the mailslot. This may return
         * immediately if data is already available. We also rate-limit
         * by enforcing a wait after 64 immediate messages.
         */
        br = ReadFile(slotPtr->handle, slotPtr->message, cbSize, &cbRead, &slotPtr->ov);
        ++cQueued;
    } while (br && cQueued < 64);

    Tcl_ThreadAlert(slotPtr->wait.tid);
    return;
}

Of note is we have a structure that 'inherits' from Tcl_Event and allows us to put our own data in the event. We can post a number of events and use Tcl_ThreadAlert to wake up the target once we are done. The target function (EventProc in this case) will be passed this structure but will be running on the interpreter thread, so we have to mind the Tcl_Obj reference counting.

In your case it sounds like you may just wish to set the forever variable in your event procecdure.

If its useful you can see the whole file.

However, are you sure you shouldn't just use fileevent on a properly written named pipe channel?

0
votes

Thanks for all your suggestions, I have solved my problem.

Setting the forever variable of vwait forever with the TCL script is quite easy, one only needs to use forever as the result of thread::send and let the main TCL thread wait on it. For the TCL C thread setting forever immediately in the TCL_EventProc hangs the main TCL interpreter. To solve this I delayed the setting of forever until the event loop is idle using after like so:

after idle {set forever thread}