Constant Platypus

A common challenge in calling C functions from any language other than C or C++ is dealing with constants. In C these are usually implemented using #define pre-processor directives which are lost by the time the code is linked into a dynamic library. (In fact it is lost before the source is even compiled, since it is a pre-processor directive). For example, the libarchive library provides these constants in its header file for dealing with errors:

 * Error codes: Use archive_errno() and archive_error_string()
 * to retrieve details.  Unless specified otherwise, all functions
 * that return 'int' use these codes.
#define ARCHIVE_EOF       1     /* Found end of archive. */
#define ARCHIVE_OK        0     /* Operation was successful. */
#define ARCHIVE_RETRY   (-10)   /* Retry might succeed. */
#define ARCHIVE_WARN    (-20)   /* Partial success. */
/* For example, if write_header "fails", then you can't push data. */
#define ARCHIVE_FAILED  (-25)   /* Current operation cannot complete. */
/* But if write_header is "fatal," then this archive is dead and useless. */
#define ARCHIVE_FATAL   (-30)   /* No more operations are possible. */

(I'll also discuss enumerated types (enums), which are in some ways similar, but in some ways provide additional challenges).

This is especially a challenge for writing FFI bindings in dynamic languages like Perl using FFI::Platypus. If there are only a few constants, and if they are relatively stable (as is the case with libarchive), then it might be easiest to just define equivalent values in Perl space with the same names:

package Archive::Libarchive::FFI;

use strict;
use warnings;
use constant ARCHIVE_EOF    => 1;
use constant ARCHIVE_OK     => 0;
use constant ARCHIVE_RETRY  => -10;
use constant ARCHIVE_WARN   => -20;
use constant ARCHIVE_FAILED => -25;
use constant ARCHIVE_FATAL  => -30;


This starts to break down when there are hundreds of constants, or if they change from version to version, or if they depend on system defined constants (like the maximum length of a file path, or the size of a long int). One way to deal with this would be to bundle some C code with your distribution (See Bundling With Platypus) with functions that provide the name/value mappings:

#include <archive.h>

int const_archive_ok() { return ARCHIVE_OK; }
int const_archive_eof() { return ARCHIVE_EOF; }

This adds a lot of overhead though, for something that will never change. Each constant has to have its own function, and the return value never changes. There will be a lot of repetition in the corresponding .pm file to create the bindings to each constant function. It would be nice if you could define all the constants that are needed in Perl space from C space in one fell swoop. Fortunately the Platypus bundle interface provides a mechanism for doing exactly that!

/* lives in ffi/constant.c */

#include <ffi_platypus_bundle.h>
#include <archive.h>

ffi_pl_bundle_constant(const char *package, ffi_platypus_constant_t *c)
  c->set_sint("ARCHIVE_EOF",    ARCHIVE_EOF);
  c->set_sint("ARCHIVE_OK",     ARCHIVE_OK);
  c->set_sint("ARCHIVE_WARN",   ARCHIVE_WARN);

The API for the ffi_platypus_const_t object lets you define different types of constants. See FFI::Platypus::Constant for more details. (Floating point, string, signed and unsigned integer types are supported). The Perl code that goes along with this doesn't have to do anything special except for to call $ffi->bundle, because Platypus knows to call ffi_pl_bundle_constant if it has been defined. This means that if you need to add or modify constants you only need to do it in one place:

package Archive::Libarchive::FFI {

  use strict;
  use warnings;
  use FFI::Platypus 1.00;
  use base qw( Exporter );

  my $ffi = FFI::Platypus->new( api => 1 );
  $ffi->find_lib( lib => 'archive' );

  # use this trick to export all constants with the ARCHIVE_ prefix:
  our @EXPORT = grep /^ARCHIVE_/, keys %Archive::Libarchive::FFI::

What about enumerated types (enums)? The names for enum values work essentially the same as constants from an FFI perspective. They are lost when you create a dynamic library, so you can use either method for defining the values in your bundled C code or in your .pm file. What type you should use may not be so clear. Enums are usually represented as an integer type of some kind, but the C standard doesn't say exactly how big or if it should be signed. (perhaps obviously enums with negative values should be stored as signed integers, but for enums that happen to have only positive values, it is not as clear).

If you knew that enums were stored as unsigned 32bit integers on your platform, you could use sint32. Your bindings wouldn't be portable to other platforms though, so don't do that!

To save you some work, Platypus probes the C compiler at install time to see what actual integer types are used for "signed" and "unsigned" enums. Thus, if libarchive had chosen an enum instead of #define constants for its error code, its header file might looks something like this:

enum archive_error_code_t {
  ARCHIVE_EOF    = 1,
  ARCHIVE_OK     = 0,
  ARCHIVE_WARN   = -20,

From Perl / Platypus you can define the values the same way as before, but the actual type you should use is senum, since the possible values include negative numbers. If they were all zero or positive, then we would use enum.

use FFI::Platypus 1.00;
my $ffi = FFI::Platypus->new( api => 1 );
$ffi->type( 'senum' => 'archive_error_code_t' );

This type alias can be used for any function that returns an error in this way.

We've learned today that there are a couple of ways to get constants out of C header files into your Perl program using FFI. Ideally there aren't a lot of magic numbers like this in whatever library you are adapting and maybe you can just put them in your .pm file as constants. If not then there is a nifty bundle interface that lets you define constants in Perl space from C space. We also learned that enums are conceptually quite similar to constants, but care needs to be taken about which types you use if you want your program to be portable.

1 Comment

Thank you for your articles. Would be nice to see articles about how to create 'record' (perl objects). Especially how to create struct which refer to different struct.

Leave a comment

About Graham Ollis

user-pic Perl programmer with an interest in Alien and FFI technologies. Primary developer of FFI::Platypus and Alien::Build.