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.
To run the examples below you need a NativeCall module installed. Then add use NativeCall;
at the top of the file.
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 be a C-level data structure, at least under the hood. Consider a class representing a connection to Music Player Daemon:
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 necesarilly look familiar. The is repr
trait tells the compiler that the internal representation of the class 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 new
method, which uses bless
to set the object’s internal representation to what 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 DESTROY
we just pass self
to another native function, mpd_connection_free
, without the need to unbox it or whatever. The 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 taglib
library to extract the metadata about some music files lying around. Let’s see the 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 new
: how are we going to get an object and read our precious tags? In taglib
, the actual Tag
object is obtained from a Tag_File
object first. Why didn’t we implement it first? Well, it’s going to have a method returning the 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 new
to check for exceptional situations and react in an 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 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 repr('CStruct')
and access the struct
field directly, as you would in 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!
How do Perl know the “true” datatype from the .so file? Does it need a .h file or something?
The data types are all in the signature of the Perl 6 subroutine. For example
sub mpd_connection_new(Str $host, Int $port) returns Connection
boils down to a C functionvoid* mpd_connection_new(char*, int)
. No additional header file needed.Trying to run the first code snippet using a current rakudo from git, I get:
===SORRY!===
No applicable candidates found to dispatch to for ‘trait_mod:’. Available candidates are:
:(Attribute $attr, Any $rw)
:(Attribute $attr, Any $readonly)
:(Attribute $attr, Any $box_target)
:(Routine $r, Any $rw)
:(Routine $r, Any $default)
:(Routine $r, Any $info, Any $inlinable)
:(Parameter $param, Any $readonly)
:(Parameter $param, Any $rw)
:(Parameter $param, Any $copy)
:(Parameter $param, Any $required)
:(Routine $r, Any $export)
:(Routine $r, Any $hidden_from_backtrace)
:(Mu $type, Any $rw)
:(Mu $type, Any $size, Any $nativesize)
:(Mu $type, Any $export)
:(Mu $docee, Any $doc, Any $docs)
:(Mu $docee, Any $doc, Any $docs)
:(Mu $child, Mu $parent)
Yeah, I forgot to mention you need to import the NativeCall module to do that (it’ll probably live in core Rakudo one day). I’ll update the post to mention that, thanks!
These examples could be written much more concisely, by creating
our own custom ‘is’ modifier:
class Tag is repr(‘CPointer’) {
my sub trait_mod:(Routine $r, :$tag!) {
trait_mod:($r, :native(‘libtag_c’));
trait_mod:($r, :symbol(“taglib_tag_” ~ $r.name));
};
method title() returns Str is tag {}
method artist() returns Str is tag {}
method album() returns Str is tag {}
method genre() returns Str is tag {}
method year() returns Int is tag {}
method track() returns Int is tag {}
method free_strings() is tag {}
method free { .free_strings }
}
class TagFile is repr(‘CPointer’) {
my sub trait_mod:(Routine $r, :$tagfile!) {
trait_mod:($r, :native(‘libtag_c’));
trait_mod:($r, :symbol(“taglib_file_” ~ $r.name));
};
sub new(Str $filename) returns TagFile is tagfile {};
method free() is tagfile {};
method tag() returns Tag is tagfile {};
method is_valid() returns Int is tagfile {};
method new(Str $filename) {
unless $filename.IO.e {
fail “File ‘$filename’ not found”
}
my $self = new($filename);
unless $self {
fail “Either the type cannot be determined, or ‘$filename’ could not be opened”;
}
unless $self.is_valid {
$self.free;
fail “‘$filename’ is invalid”;
}
return $self;
}
}
A few things to note: First, NativeCall will tack on .so or .dll for you,
depending on your operating system, so I’ve removed that. Second, NativeCall
is perfectly capable of binding class methods to C functions, so except for
TagFile.new, we do not need to have both a sub, and a method wrapper around it.
Third, we don’t need to call .bless in 99.99% of perl6 code, including this code.
Fourth, check that taglib_file_new doesn’t return a NULL pointer.
Lastly, it’s more perl6ish to ‘fail’ than to ‘die’ in code like this.
Oh, and perl6 can create bindings to C++ classes, including the original
libtag library; we don’t *need* to use the C binding. However, making a
change like that is beyond the scope of this comment.
PS: I haven’t tested these changes, since I’m on Windows not Linux and don’t
have libtag on my machine.
PPS: The header file of note for libtag can be found at:
https://github.com/taglib/taglib/blob/master/bindings/c/tag_c.h
For what it’s worth, this post is nearly six years old now!
Gonna have to check out your thoughts here, as I have been a faithful Audio::Taglib::Simple user for years…
(Well, and also in general C++ bindings might be very useful for my $work.)