Capturing output from C

schwern on 2008-05-10T05:34:23

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.


Did you try IO::CaptureOutput?

dagolden on 2008-05-10T10:26:51

IO::CaptureOutput is what you wanted (and more or less reinvented).

-- dagolden

Re:Did you try IO::CaptureOutput?

schwern on 2008-05-12T09:52:48

Thanks, I'll use that instead.