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
# 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
# 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