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

So I've been toying around with wrapping some C functions that simply take arguments and print via stdout. The input was fairly straightforward. Capturing the output however, has been difficult to say the least.

Regardless of the PerlIO layer, I can't seem to capture data written to FILE streams from XS.

#!/usr/bin/perl use strict; use warnings; use Inline C => <<'C_HUNK'; void c_output() { fprintf(stdout, "###c_output"); } void c_output_newline() { fprintf(stdout, "###c_output_newline\n"); } C_HUNK sub test_global { my $out_var = ''; my $prev_select = select; open my $prev_stdout, '>&', \*STDOUT or die "Error: $!"; close STDOUT; open STDOUT, '>', \$out_var or die "Error: $!"; select $out_var; $|=1; select $prev_select; outp_test(); open STDOUT, '>&', \$prev_stdout or die "Error: $!"; return $out_var; } sub outp_test { # Test with newline (autoflush) warn "\tprinting [perl_output_newline]\n"; print STDOUT "***perl_output_newline\n"; warn "\tcalling [c_output_newline]\n"; c_output_newline(); # Test without newline warn "\tprinting [perl_output]\n"; print STDOUT "***perl_output"; warn "\tcalling [c_output]\n"; c_output(); } warn "===============dry run============\n"; outp_test(); print "\n"; warn "=============test_global==========\n"; my $buffer = test_global(); $buffer =~ s/\n/\\n/g; warn "test_global buffer:\n($buffer)\n"; print "\n";

When I set PERLIO to perlio (ala default), I get the following output:

===============dry run============ printing [perl_output_newline] ***perl_output_newline calling [c_output_newline] ###c_output_newline printing [perl_output] calling [c_output] ***perl_output###c_output =============test_global========== printing [perl_output_newline] calling [c_output_newline] printing [perl_output] calling [c_output] test_global buffer: (***perl_output_newline\n***perl_output)

The dry run is logically outputting the non-flushed Perl and C output as soon as there is a flush. The redirection however, never returns any of the C output.

Setting PERLIO to stdio gives the following:

===============dry run============ printing [perl_output_newline] ***perl_output_newline calling [c_output_newline] ###c_output_newline printing [perl_output] calling [c_output] ***perl_output =============test_global========== printing [perl_output_newline] calling [c_output_newline] printing [perl_output] calling [c_output] test_global buffer: (***perl_output_newline\n***perl_output) ###c_output

The dry run only outputs the flushed C output, and not the non-flushed. The redirection seems to be caching the non-flushed C output, and releasing it only after everything else is done.

So... I'm hoping to avoid forking, but I'm thinking I might not have any other option. Oh, tested this on 5.10.1 and 5.8.8 with the same behavior.

Any ideas?

Replies are listed 'Best First'.
Re: Redirecting XS stdout
by ikegami (Patriarch) on Feb 21, 2010 at 01:01 UTC
    You shouldn't be using C IO lib, you should be using Perl's. (Well, I think so at least. I have no experience in this area.)

    Update: Yup, that does the trick:

    void c_output() { PerlIO_printf(PerlIO_stdout(), "###c_output"); } void c_output_newline() { PerlIO_printf(PerlIO_stdout(), "###c_output_newline\n"); }
    >set PERLIO=stdio >perl a.pl ===============dry run============ printing [perl_output_newline] ***perl_output_newline calling [c_output_newline] ###c_output_newline printing [perl_output] ***perl_output calling [c_output] ###c_output =============test_global========== printing [perl_output_newline] calling [c_output_newline] printing [perl_output] calling [c_output] test_global buffer: (***perl_output_newline\n###c_output_newline\n***perl_output###c_outpu +t) >set PERLIO=perlio >perl a.pl ===============dry run============ printing [perl_output_newline] ***perl_output_newline calling [c_output_newline] ###c_output_newline printing [perl_output] calling [c_output] ***perl_output###c_output =============test_global========== printing [perl_output_newline] calling [c_output_newline] printing [perl_output] calling [c_output] test_global buffer: (***perl_output_newline\n###c_output_newline\n***perl_output###c_outpu +t)
      perlclib has a nice list of Internal replacements for standard C library functions

      ++ You know what, that's just the key I needed!

      I'm trying to wrap some C with as little modification as possible (staying current with the upstream). I didn't want to replace any IO calls directly, but after *seeing* your PerlIO call it dawned on me to simply override the stdio calls with PerlIO.

      #define fprintf(fh,...) PerlIO_printf(fh,__VA_ARGS__); #define stdout PerlIO_stdout() #define stderr PerlIO_stderr()
      Thanks!
        Exactly. You'd think something like this already exists. Maybe it does. (Google code search?)