in reply to Overloading print()

Hi Rob,

from reading the rest of this thread, I figure your real problem is to capture the stdout of an XS routine.

The key thing to note here is that, within the C routine, stdout corresponds to fileno 1. So, in your Perl code, you'll need to arrange for things to read the output written to exactly that filehandle, e.g. by setting up a pipe to it.

This a bit more involved than you might think, but the following should do the trick (to keep the example concise, I'm using Inline here, but it should equally work with regular XS code):

#!/usr/bin/perl use Inline C => <<'EOC'; void foo() { printf("something being sent to stdout\n"); fflush(stdout); } EOC my $output; { # save original STDOUT open my $saved_stdout, ">&STDOUT"; # create a pipe, which we'll use to read from our own STDOUT local(*RH, *WH); pipe RH, WH; # connect the writing side of the pipe to STDOUT, with # STDOUT being (and remaining) fileno 1 (!) open STDOUT, ">&WH" or die "open: $!"; # debug: verify that fileno really is 1 printf STDERR "fileno(STDOUT): %d\n", fileno(STDOUT); # call the C/XS function whose stdout we want to capture foo(); # close WH to avoid buffering issues (pipes are buffered) close WH; # read output (one line) $output = <RH>; close RH; # restore original STDOUT open STDOUT, ">&", $saved_stdout or die "open: $!"; } print "output of foo(): $output";

prints:

fileno(STDOUT): 1 output of foo(): something being sent to stdout

The crucial thing is that STDOUT has to correspond to fileno 1, so localising STDOUT will not work here (local *STDOUT will result in a new fileno...). For the same reason, you can't use "open-to-a-string", because in that case the filehandle will be a Perl internal one (fileno -1), which the C side knows nothing about...

With the above code, the open STDOUT, ">&WH" is doing a dup2 system call (among other things), which (in this particular case) will first close STDOUT and then immediately reassign that same fileno, which is 1. BTW, this works only if you haven't messed with Perl's STDOUT before (by default, it is fileno 1).

To get a deeper understanding of the mechanics under the hood, you might want to play around with tracing system calls (i.e. strace and friends).  HTH.

Replies are listed 'Best First'.
Re^2: Overloading print() (full pipe)
by tye (Sage) on Sep 08, 2007 at 02:07 UTC

    I believe that this approach will "hang" ("deadlock", "lock up") if the XS routine tries to output more than one "system buffer" full of data. Exceeding somewhere on the order of 4KB of data to the pipe will cause the XS code to hang, waiting for someone to read from the pipe, draining it to make room for the extra data.

    - tye        

Re^2: Overloading print()
by syphilis (Archbishop) on Sep 06, 2007 at 04:59 UTC
    almut++ ... a truly edifying piece of code which does exactly what I was after.

    However, looking at the hoops it jumps through, I start to wonder about its portability and reliability. (Could it potentially create more problems for me than it solves ?)

    And I couldn't work out how to restore the original stdout on perl 5.6 (where ">&" is not understood) - though that's not really a major consideration.

    So ... I'm not yet sure whether I'll implement that approach, but thanks for going to the trouble of presenting such a well written and useful demo.

    Math::MPFR already overloads "". The version on CPAN doesn't render inf, nan, and -0 as it should, though that's taken care of in the next release. Apart from that, the only difference (I'm aware of) between the overloaded "" and mpfr_out_str is that mpfr_out_str doesn't strip trailing zeros, whereas perl *does* strip them. (That is, whereas mpfr_out_str might render a number as  1.23450000e1, the overloaded "" will return 1.2345e1.) I don't see any need to be concerned about that discrepancy. The appeal of having the overloaded "" wrap mpfr_out_str is simply that I can then be certain that the outputs will match ... without even having to consider the many-and-varied "what if" scenarios.

    Cheers,
    Rob