Skip to content

Instantly share code, notes, and snippets.

@tadzik
Created December 21, 2011 21:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tadzik/1507821 to your computer and use it in GitHub Desktop.
Save tadzik/1507821 to your computer and use it in GitHub Desktop.
Post draft
=head1 Native libraries
Last year flussence++ wrote a nice post about writing XMMS bindings for
Perl 6 using the Native Call Interface. It has improved a bit since then,
(at least NCI, I don't know about XMMS), so let's show it off a bit.
Previously, we were carefully writing all the C subs we needed to use
and then usually writing some Perl 6 class which wrapped it in a nice,
familiar interface. That doesn't change much, except that now a class
is not really an interface for some C-level data structure. Thanks to
the new metamodel we can now make our class to actually I<be> a C-level
data structure, at least under the hood. Consider a class representing
a connection to Music Player Deamon:
class Connection is repr('CPointer') {
sub mpd_connection_new(Str $host, Int $port)
returns Connection
is native('libmpdclient.so') {}
sub mpd_connection_free()
is native('libmpdclient.so') {}
method new(Str $host, Int $port) {
self.bless(mpd_connection_new($host, $port))
}
method DESTROY {
mpd_connection_free(self)
}
}
The first line does not neceserilly look familiar. The C<is repr> trait
tells the compiler that the internal representation of the class
C<Connection> is a C pointer. It still is a fully functional Perl 6 type,
which we can use in method signatures or wherever (as seen in the lines
below).
We then declare some native fuctions we're going to use. It's quite
convenient to put them inside the class body, so they don't pollute the
namespace and don't confuse the user. What we are really exposing here
is the C<new> method, which uses C<bless> to set the object's internal
representation to what C<mpd_connection_new> has returned. From now on
our object is a Perl 6 level object, while under the hood being a mere
C pointer. In method C<DESTROY> we just pass C<self> to another native
function, C<mpd_connection_free>, without the need to unbox it or
whatever. The C<NativeCall> module will just extract its internal
representation and pass it around. Ain't that neat?
Let's see some bigger example. We'll use C<taglib> library to extract
the metadata about some music files lying around. Let's see the C<Tag>
class first:
class Tag is repr('CPointer') {
sub taglib_tag_title(Tag) returns Str is native('libtag_c.so') {}
sub taglib_tag_artist(Tag) returns Str is native('libtag_c.so') {}
sub taglib_tag_album(Tag) returns Str is native('libtag_c.so') {}
sub taglib_tag_genre(Tag) returns Str is native('libtag_c.so') {}
sub taglib_tag_year(Tag) returns Int is native('libtag_c.so') {}
sub taglib_tag_track(Tag) returns Int is native('libtag_c.so') {}
sub taglib_tag_free_strings(Tag) is native('libtag_c.so') {}
method title { taglib_tag_title(self) }
method artist { taglib_tag_artist(self) }
method album { taglib_tag_album(self) }
method genre { taglib_tag_genre(self) }
method year { taglib_tag_year(self) }
method track { taglib_tag_track(self) }
method free { taglib_tag_free_strings(self) }
}
That one is pretty boring: plenty of native functions, and plenty of
methods being exactly the same things. You may have noticed the lack of
C<new>: how are we going to get an object and read our precious tags?
In C<taglib>, the actual C<Tag> object is obtained from a C<Tag_File>
object first. Why didn't we implement it first? Well, it's going to
have a method returning the C<Tag> object shown above, so it was
convenient to declare it first.
class TagFile is repr('CPointer') {
sub taglib_file_new(Str) returns TagFile is native('libtag_c.so') {}
sub taglib_file_free(TagFile) is native('libtag_c.so') {}
sub taglib_file_tag(TagFile) returns Tag is native('libtag_c.so') {}
sub taglib_file_is_valid(TagFile) returns Int
is native('libtag_c.so') {}
method new(Str $filename) {
unless $filename.IO.e {
die "File '$filename' not found"
}
my $self = self.bless(taglib_file_new($filename));
unless taglib_file_is_valid($self) {
taglib_file_free(self);
die "'$filename' is invalid"
}
return $self;
}
method tag { taglib_file_tag(self) }
method free { taglib_file_free(self) }
}
Note how we use native functions in C<new> to check for exceptional
situations and react appropriately Perl 6 way. Now we only have to write
a simple MAIN before we can test it on our favourite music files.
sub MAIN($filename) {
my $file = TagFile.new($filename);
my $tag = $file.tag;
say 'Artist: ', $tag.artist;
say 'Title: ', $tag.title;
say 'Album: ', $tag.album;
say 'Year: ', $tag.year;
$tag.free;
$file.free;
}
Live demo! Everyone loves live demos.
$ perl6 taginfo.pl some-track.mp3
Artist: Diablo Swing Orchestra
Title: Balrog Boogie
Album: The Butcher's Ballroom
Year: 2009
Works like a charm. I promise I'll wrap it up in some nice C<Audio::Tag>
module and release it on Github shortly.
Of course there's more to do with NativeCall than just passing raw
pointers around. You could, for example, declare it as a
C<repr('CStruct')> and access the C<struct> field directly, as you would
in the good, old C. This is only partly implemented as for now though,
but that shouldn't stop you from experimenting and seeing what you can do
before Christmas. Happy hacking!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment