Stories
Slash Boxes
Comments
NOTE: use Perl; is on undef hiatus. You can read content, but you can't post it. More info will be forthcoming forthcomingly.

All the Perl that's Practical to Extract and Report

use Perl Log In

Log In

[ Create a new account ]

schwern (1528)

schwern
  (email not shown publicly)
http://schwern.net/
AOL IM: MichaelSchwern (Add Buddy, Send Message)
Jabber: schwern@gmail.com

Schwern can destroy CPAN at his whim.

Journal of schwern (1528)

Saturday May 10, 2008
12:34 AM

Capturing output from C

[ #36376 ]

I'm working on a contract that's been edumicating me a lot in C and
XS... and mostly making me realize that I didn't charge enough to have
to touch them.

One of the tasks is to write an XS wrapper around a C program so it
can be used as a function call from Perl. They don't want the
overhead of starting it up over and over again. Easy enough. Scoop
out the contents of main() into its own function (as I suspected,
doing an XS wrapper around main() has complications), change all the
exit()s to return()s and write a thin XS wrapper around that.

Now, how do you test it? This program prints to STDOUT and
STDERR... in C. None of the normal Perl output capturing methods
work. Can't tie it. Can't reopen it. Can't redirect it. C is
talking to file descriptors 1 and 2 and that's that. File descriptors
being something Perl generally protects us from behind file handles,
but fortunately you can still get at them.

A little bit of experimentation dug up the obscure ">>&=" open mode
which does the equivalent of fdopen() in C. That being, it opens on
the same file descriptor. Now I can change where the STDOUT and
STDERR file descriptors go, and C will see the change.

Here's what I did.

use File::Temp qw(:seekable);
use Fcntl qw(:seek);
 
# Make self-cleaning temp files each for STDOUT and STDERR
# redirection.
# We need real files so they have real file descriptors.
my $tmp_stdout = File::Temp->new(
    UNLINK => 1,
    TEMPLATE => "test_stdout_XXXXXX"
);
my $tmp_stderr = File::Temp->new(
    UNLINK => 1,
    TEMPLATE => "test_stderr_XXXXXX"
);
 
# Store a copy of STDOUT and STDERR.
open my $save_stdout, ">&", \*STDOUT or die "Can't save STDOUT: $!";
open my $save_stderr, ">&", \*STDERR or die "Can't save STDERR: $!";
 
# Point STDOUT and STDERR at my temp file descriptors.
open STDOUT, ">>&=", $tmp_stdout     or die "Can't dup STDOUT: $!";
open STDERR, ">>&=", $tmp_stderr     or die "Can't dup STDERR: $!";
 
# Run the C function in question.
my $exit = wrapped_c_function($command);
 
# Restore STDOUT and STDERR
open STDOUT, ">&", $save_stdout      or die "Can't restore STDOUT: $!";
open STDERR, ">&", $save_stderr      or die "Can't restore STDERR: $!";
 
# Seek back to the beginning of the temp files
$tmp_stdout->seek(0, SEEK_SET);
$tmp_stderr->seek(0, SEEK_SET);
 
# Read their contents.
my @stdout = <$tmp_stdout>;
my @stderr = <$tmp_stderr>;

Voila. Messy, but easily wrapped up in a module.

Because this technique works at the file descriptor level, it works
for system() and `` without having to get into shell redirection or
IPC::Open3.

The Fine Print: The following comments are owned by whoever posted them. We are not responsible for them in any way.
 Full
 Abbreviated
 Hidden
More | Login | Reply
Loading... please wait.