Perl 6 NativeCall: Look, Ma! I'm a C Programmer!
A while back, I wanted to write a post talking about how Perl 6 lets you use C libraries without writing any C code. It was cool and clickbaity, but I quickly realized two things: (a) the statement isn't always true; and (b) I'm too ignorant to talk about it without sounding like a moron.
And so has started my path to re-learn C (I barely ever used it and it was over a decade ago) and to learn Perl 6's NativeCall in great detail. Oh, and I'll blog about my journey in a series of bite-sized posts. Let's begin!
Use C Libraries Without Writing Any C Code!
NativeCall
is one of the
standard modules included with Perl 6 that provides interface to C libraries.
No compilers or -dev
versions of the libraries are needed! And this, of
course, means you can use C libraries without writing any C code!
The is native()
trait is applied to a sub with an empty body and signature
that matches the prototype of the C function you wish this sub to call.
Magic! Right?
As I've already hinted, things get complex fast, and in some circumstances not writing any C might be unfeasible or maybe even impossible. In such situations, you'd simply create a wrapper library. But let's look at some code already!
The Standard C Library
If no argument is given to is native
trait, it will look in the Standard
C Library. Programmers coming from Perl 5 often notice there's no fork()
in Perl 6. The
reason for that is that, unlike in Perl 5, it's almost never needed, but
thanks to NativeCall, it is actually "there":
use NativeCall;
sub fork() returns int32 is native {};
given fork() {
when 0 { say "I'm a kid!"; };
when * > 0 { say "I'm a parent. The kid is at $_"; };
default { die "Failed :("; };
}
sleep .5;
say 'Hello, World!';
# OUTPUT:
# I'm a parent. The kid is at 13099
# I'm a kid!
# Hello, World!
# Hello, World!
On the first line, we use
the NativeCall module to bring in its
functionality. The second line is where all the magic happens. We declare
a sub with an empty body and name it the same as its named in the C library. The sub is sporting is native
trait which tells the
compiler we want a C library function, and since the name of the library
isn't there, we want the Standard Library.
For a successfull call, we need to match the prototype of the C function.
Looking at man 2 fork
, we see it's pid_t fork(void)
. So our sub
doesn't take any arguments, but it returns one. If you dig around, you'll
find pid_t
can be represented as a C int
and if we look up an int
in the handy table mapping C and Perl 6 types, you'll notice we can use
int32
Perl 6 native type, which is what we specified in the returns
trait.
And that is it! The rest of our code uses fork()
as if it were a Perl 6
sub. It will be looked up in the library on the first call and cached for any subsequent look ups.
Basic Use of Libraries
For our learning pleasure, I'll be using
libcdio library that lets you
mess around with CDs and CD-ROMs (anybody still got those?). On Debian,
I'll just need libcdio13
package. Notice is it not the -dev
version and
on my box it was actually already installed.
I'm going to
create a Perl 6 program called troll.p6
that opens and closes the CD tray:
use NativeCall;
sub cdio_eject_media_drive(Str) is native('cdio', v13) {};
sub cdio_close_tray(Str, int32) is native('cdio', v13) {};
say "Gimme a CD!";
cdio_eject_media_drive Str;
sleep .5;
say "Ha! Too slow!";
cdio_close_tray Str, 0;
The cdio_eject_media_drive
and cdio_close_tray
functions are provided
by the library. We declare them and apply is native
trait. This time, we
give the trait two arguments: the library name and its version.
Notice how
the name lacks any lib
prefixes or .so
suffixes. Those are not needed, as
NativeCall figures out what those should be automatically, based on the
operating system the code is running on.
The version is optional, but it's not recommended that you omit it, since then you never know whether the version that's loaded is compatible with your code. In future posts, I'll explore how to make naming/versioning more flexible.
The one thing to look at are the C function prototypes for these two subs:
driver_return_code_t cdio_eject_media_drive (const char *psz_drive)
driver_return_code_t cdio_close_tray (const char *psz_drive, driver_id_t *p_driver_id)
That looks mighty fancy and it seems like we haven't reproduced them exactly in our
Perl 6 code. I'm cheetsy-doodling here a bit: while Str
is correct for const char *
, I looked up what int
value will work for p_driver_id
so I don't
have to mess with structs or enums, for now. I'm also ignoring return types
which may be a bad idea and makes my code less predictable and perhaps less
portable as well. When making calls to subs, I used the type object
Str
for the strings. That translates to a NULL
in C.
I'll leave more detailed coverage of passing arguments around for future articles. Right now, there's a more serious issue that needs fixing. The names!
Renaming Functions
One thing that sucks about C functions is they're often named with snake_case, which is an eyesore in Perl 6 with it's shiny kebob-case. Luckily, the fix is just a trait away:
use NativeCall;
sub open-tray(Str) is native('cdio', v13)
is symbol('cdio_eject_media_drive') {};
sub close-tray(Str, int32) is native('cdio', v13)
is symbol('cdio_close_tray') {};
say "Gimme a CD!";
open-tray Str;
sleep .5;
say "Ha! Too slow!";
close-tray Str, 0;
The usage is simple: name your sub with whatever you want its name to be, then
use is symbol
trait and use the C function's name as its argument. And that's
it! With just a couple of lines of code, we're making a call into a C library
and we're using purty sub names to do it!
Conclusion
Today we've seen a glimpse of the power Perl 6 provides when it comes to C libraries. It lets you get pretty far without needing a C compiler.
In future posts in the series, we'll learn more about passing data around as well as various helpers that do the heavy lifting.
See you next time!
thanks, useful post. --jnap
I wonder how is it on the Perl 5 side. If you can use Perl 6 from Perl 5 with compatibility module or Inline, then hypothetically this should work on Perl 5 too, right?