August 2017 Archives

Perl6 - NativeCall, using the native trait correctly

I saw a lot of confusion with what should be passed to the native trait when working on binding a C function from a library using NativeCall.

One important thing to remember is Perl 6 trait are solved at compile time. This is important because every value passed to a trait take their compile time value.

use NativeCall;
my $foo = "a";
sub foo is native($foo) {*}

This will fail to compile since native does not take Any has a valid type. Even if $foo can be checked as being a Str, its compile time type is Any. Easy to check with a BEGIN block.

<Skarsnik> m: use NativeCall; my $foo = "a"; say $foo.^name
<evalable6> Skarsnik, rakudo-moar 79604a880: OUTPUT: «Str»
<Skarsnik> m: use NativeCall; my $foo = "a"; BEGIN {say $foo.^name};
<evalable6> Skarsnik, rakudo-moar 79604a880: OUTPUT: «Any»

It's actually explained in NativeCall documentation

as the example state. Don't do something like constant LIBMYSQL = %*ENV<P6LIB_MYSQLCLIENT> || 'mysqlclient'; It's not a big deal when testing in a single file, but it's really bad when it's in a module since this will keep the value when the module is precompiled.

Native trait magic

As the documentation says, the native trait is nice with dealing with platform specific way to find the library file name to use. Most of the time you can just do a constant LIB = ('foo', v1); and give this constant to each native part of a sub declaration. (note that a full library path will never be changed)

Now you probably wonder the proper way for solving the %*ENV case. You will need to use the fact that the native trait allows you to specify a routine to run. And that where things get bad, it will stop doing its magic for you.

If I were to write

LIB = sub {
  %*ENV<MYLIB> || ('foo', v1)

It will not work. NativeCall will search for a file ('foo', v1) or just fail not having any idea what to do with a List/Seq.

I saw a lot of people solving this issue by partially rewriting the routine that does all this work (and forgetting about the need for an ABI version in most case). Luckily this routine is actually usable if you export the right symbol from NativeCall.

sub FindMyLib {
   use NativeCall :TEST; #We don't need to export for the whole code
   %*ENV<MYLIB> || guess_library_name(('foo', v1))
constant LIB = &FindMyLib;

Note the double ( since it expect a List when you want to pass a version.

This is actually IHMO a bad workaround around the fact that NativeCall uses a trait that has its value solved at compile time when sometimes we could only have this information at run time.

Last thoughts

SPECIFY AN ABI VERSION (and use App::GPTrixie to generate your C binding. )

About Sylvain Colinet

user-pic I blog about Perl 6.