Native libraries, native objects

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!

7 thoughts on “Native libraries, native objects

    1. 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 function void* mpd_connection_new(char*, int). No additional header file needed.

  1. 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)

    1. 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!

  2. 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

    1. 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…

Leave a reply to heir Cancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.