run4flat has asked for the wisdom of the Perl Monks concerning the following question:

Hello monks -

I am building a GUI REPL with Prima. One major part of this is to capture the output of print, printf, warn, etc. Not a trivial problem, but solvable. A simple solution is given on Stack Overflow and serves as the basis for my current work. Basically, you create a tied file handle and either select() it or assign STDOUT (and STDERR) to it. Simple. However, all is not well. I have a REPL, remember, and it's not hard to generate an Inline block that uses C's printf. Unfortunately, the output of this printf statement does not get captured!

I was recently directed to Capture::Tiny and thought, "This is exactly what I need!" Almost. For long running XS functions, I like to print output to the screen using something like

... printf("On step %d\n", i); ...

I would *really* like to have the output screen update with each round of that printout, but Capture::Tiny won't work with tied file handles. It unties them and directs the results to the tie only at the end of the "captured" code. No matter how I implement that, I won't get the output until after the XS function returns.

Not to be dismayed, I dug into Capture::Tiny to see if I could extract the goodies, and that is where I'm stuck. It looks like the way to override STDOUT, in both Perl and underlying XS functions, is to call

open *NEWHANDLE, ">&STDOUT",

but do you notice the catch? The open command creates the new file handle for me. But unless I'm mistaken, that's what the tie operation is supposed to do! They both want to create the new file handle for me, which means I either get the tying behavior or I get the full STDOUT overriding, but not both.

At this stage, I'm beginning to believe that the only real way to keep the REPL responsive and have it print every single output is to have it fork a separate Perl process (an eval daemon, so to speak) and capture the output of the process. This gets pretty crazy, though, because I'm actually targeting PDL, and I have a plotting library that I would like to have open new tabs when you create new plots. Neither PDL nor Prima are Perl threadsafe, so they'd have to be their own processes and I'd probably have to communicate over sockets. Since neither are threadsafe, I don't know how to share data between the two processes, which means that for good plotting, they would both need a copy of the data... You can see that this is turning the whole thing into a much larger project. It might not be a bad direction to eventually take, but it's not where I want to go at the moment.

If I can't universally capture XS output without creating an eval daemon, I have one last hope, which is to direct my users to use a special C function to print their output. I assumed that PerlIO_stdoutf would have been that function, but it turns out not to be the case. The only remaining idea I have is to suggest the creation of a C function that explicitly calls Perl's print by explicitly calling Perl code from within C.

But surely there's a better solution?

Replies are listed 'Best First'.
Re: Capturing XS printf output with a tied filehandle (lots)
by tye (Sage) on Feb 17, 2012 at 03:48 UTC

    Provide two C functions, say() and sayf(), and have them forward the strings on to your hook. No need to force say() to use Perl's built-in print so it can find STDOUT tied and call the tie code to eventually get the data to your code.

    Also, I'm not sure why you think the process boundaries need to be divided up the way you envision. I'd just have one very simple separate process that just does the screen updates. Keep everything else together like you already have it.

    I would have thought that the "GUI" part of your creation would have already forced threads much more than this little "redirect output" thing.

    Most GUIs have a widget or such that will update with text when it becomes available. So imagine just redirecting STDOUT to the 'in' side of a pipe and telling such a widget to display whatever text comes from the 'out' side of the pipe.

    That might not work by itself because a pipe will only buffer up a fairly small amount before trying to write more will block waiting for some of the buffered data to be read. And your widget might not get access to the CPU until the 'write' finishes. That is, deadlock.

    But a responsive GUI app needs to deal with this type of thing anyway so just the "pipe to yourself" might work. But if it doesn't, all you need to add is a buffering bit pump process.

    Or, simplest of all, just direct STDOUT to a temporary file and have the widget monitor the file for new text.

    - tye        

      Yeah, providing functions say() and sayf() for Inline and/or XS users are probably the simplest. Not entirely trivial, but it shouldn't be much more difficult than instructing the user to retrieve a function pointer from a Perl stash and then call it with their args.

      As for your ideas about using threads, yes, that would certainly make life a lot easier. However, Prima is not (yet?) threadsafe. I really like Prima's internals, though, and I may help Dmitry work on it if he's interested.

      Thanks!

Re: Capturing XS printf output with a tied filehandle
by Anonymous Monk on Feb 17, 2012 at 00:14 UTC
      That link is useful but does not address the OP's (admittedly very specific) use case and issue. The previous discussion explains how to capture XS output in general, but not with a tied file handle.
        And?