SourceXR

C/C++ Cross-Reference Tool

Calling C/C++ from Perl

Ever wanted to call some C/C++ subroutine from Perl easily without having to rely on large packages and writing glue code? A Perl module called Inline will help you achieving this very simply.

Another use would be to take advantage of the speed of C to rewrite some time-consuming Perl routines.

There are many ways of calling C/C++ code from Perl. They are, from the lightweight to the full featured (and providing much more languages to talk to):

  • Inline: this is a very simple package that embeds C/C++ code directly in the script file. No interface file is to be written, Inline takes care of everything.
  • DynaLib: This module let you write Perl code that can call functions from shared libraries. Functions to call are described with their C/C++ signature (DynaLib needs to know the types of parameters and return type). It is rather straightforward.
  • XS: This is the base module on which Inline is built. It is like the next package a stub generator.
  • SWIG: Namely Simplified Wrapper and Interface Generator, it is a stub generator for several languages (script or compiled) from interfaces describing code from C and C++.
  • Define a protocol between the Perl script and the C/C++ binary and use IPC. For example, using stdin/stdout to communicate between the two processes. It requires a serialization of data passing between Perl and C/C++.

Depending on your needs, you may want to use the lightweight solution or the larger ones, that allow more control and somewhat more flexibility: interface files let you define exactly what you want be generated. We focus on the simplest solution as the more elaborate ones are overkill for what we want to do.

We'll describe here a very short use of Inline, to get a glimpse of what it can do. Of course many more is available on the Inline CPAN page!

For the purpose of this article, we need to know from a Perl script the number of desktops the user has on his display. This information can be obtained by this command:

xprop -root _NET_NUMBER_OF_DESKTOPS

But we'd rather it directly from a small C program:

Our first implementation was to write the following C program to get the number of desktops from the server:

#include <stdio.h>
#include <X11/Xlib.h>
#include <stdlib.h>

int main ()
{
     char *dsp = getenv ("DISPLAY");
     Display *display = XOpenDisplay (dsp);

     Atom at = XInternAtom (display, "_NET_NUMBER_OF_DESKTOPS", False);
     if (at == None) {
          printf("None\n");
     }
     else {
          Atom atRet = None;
          int fmt = 0;
          unsigned long nitems = 0;
          unsigned long bytes = 0;
          unsigned char *prop = 0;
          int i = XGetWindowProperty (display, DefaultRootWindow (display),
                                      at, 0, 1, False,
                                      AnyPropertyType,
                                      &atRet, &fmt,
                                      &nitems, &bytes, &prop);
          if (i == Success) {
               long *l = (long *)prop;
               printf ("%ld\n", *l);
               XFree (prop);
          }
          else {
               printf ("None\n");
          }
     }

     XCloseDisplay (display);
     return (0);
}

And then we would call this small program from within the Perl script. We have now two files two files to ship, maintain, etc. For what we're trying to do, it's no big deal, but there anyway (and slightly biased for this article of course!).

It's here that Inline comes to play. We just have to copy and paste the C code into the Perl script and that's all. Inline takes care of everything. Our script becomes:

#!/usr/bin/perl

use warnings;
use strict;
use X11::Protocol;

use Inline C => Config => LIBS => '-lX11';
use Inline C => <<'EOC';

#include <stdio.h>
#include <X11/Xlib.h>
#include <stdlib.h>

int getNbOfDesktops(char *dsp) {
    int nb = 0;
    Display *display = XOpenDisplay (dsp);
    Atom at = XInternAtom (display, "_NET_NUMBER_OF_DESKTOPS", False);
    if (at == None) {
        printf("None\n");
    }
    else {
        Atom atRet = None;
        int fmt = 0;
        unsigned long nitems = 0;
        unsigned long bytes = 0;
        unsigned char *prop = 0;
        int i = XGetWindowProperty (display, DefaultRootWindow (display),
                                    at, 0, 1, False,
                                    AnyPropertyType,
                                    &atRet, &fmt,
                                    &nitems, &bytes, &prop);
        if (i == Success) {
            long *l = (long *)prop;
            nb = *l;
            XFree (prop);
        }
        else {
            printf ("None\n");
        }
    }

    XCloseDisplay (display);
    return (nb);
}

EOC

my $nb = getNbOfDesktops ($ENV{'DISPLAY'});

print "$nb\n";

That's it! Inline takes care of the parameters passing for basic types (int, float, char*) and return types. If you need to pass complex types from and to, you will have to delve into Inline documentation page and Perl API. Have a look at the cookbook as well!

If needed, they are many configuration options such as include directory, compilation options (for debugging), linker flags, and so on.

Under the hood, Inline takes care of the compilation of the C code, building a shared library and link it against the required dependencies. It uses internally XS. You will notice it the first time the script is run. A subdirectory named _Inline is created containing the shared library.

Of course, beware of memory leaks and so on, as Perl will not catch them for you!

Inline provides a very convenient way of embedding C code into a Perl script. If you need more advanced features, have a look at XS.

Comments !