Actually getting started with Portable Native Client

A minimal crash course, by
2014-02-27

This post was originally a featured article on the front page. I thought the technology looked fun and promising, and I wanted to keep up with it. It is clear, however, that the winner in this space is WASM, and PNaCl is just a failed experiment.

While blog posts can go out of date and be kept for posterity, this is not what I want the fetaured articles on my front page to be like, so I relegated this text to the blog on 2019-09-09.


We will develop a minimal PNaCl module with a focus on exposing all the details of the development process, leaving as little as possible to opaque scripts and prewritten solutions. This should give you a solid foundation on which you can build a full understanding of PNaCl.

There are several APIs you can use to create a pnacl module. In an effort to actually get started, we will start out with ppapi_simple, which introduces the least amount of unfamiliar abstractions.

Start by downloading nacl_sdk.zip and unzipping it in a directory where it can stay. We will need to refer into this directory. nacl_sdk is merely a tool for downloading the real sdk, so you need to ask it to do so:

curl -O 'https://storage.googleapis.com/nativeclient-mirror/nacl/nacl_sdk/nacl_sdk.zip'

unzip nacl_sdk.zip

cd nacl_sdk
./naclsdk update # Downloads the real SDK. This takes a while

# We can see that naclsdk installed pepper_nn, and we need to know about this directory:
PEPPER=$(pwd)/pepper_* # Keep this path for later :)

You might consult the official documentation of the naclsdk tool to see how you can use it for staying up to date and so on, if that interests you.

Now, let's start our own project. You need source code:

#include <ppapi_simple/ps_main.h>

// The entry point function can have any name:
int ppapi_simple_main(int argc, char* argv[]) {
    return 0;
}

// ... but we need to tell ppapi_simple about it:
PPAPI_SIMPLE_REGISTER_MAIN(ppapi_simple_main)

Compile this with pnacl-clang++:

# First, you need to fill in this stuff:
PEPPER=Path to the SDK. It should end with nacl_sdk/pepper_n, for some number n
PLATFORM=The platform bit of the directories named $PEPPER/toolchain/${PLATFORM}_pnacl
BINDIR="bin" or "bin64", matching $PEPPER/toolchain/${PLATFORM}_pnacl/$BINDIR
BUILD_VARIANT="Release" or "Debug", used to select variants of the ppapi libraries

# Locate the stuff we need inside nacl_sdk: TOOLCHAIN="${PEPPER}/toolchain/${PLATFORM}_pnacl/${BINDIR}"

CXX="$TOOLCHAIN/pnacl-clang++" LINK="$CXX" FINALIZE="$TOOLCHAIN/pnacl-finalize" # For other tools, like ar and ranlib, you must also # use the pnacl-prefixed ones in this directory

PPAPI_INCLUDE="$PEPPER/include" PPAPI_LIBDIR="$PEPPER/lib/pnacl/$BUILD_VARIANT"

# And now we can compile: $CXX -c -o main.o -pthread -I "$PPAPI_INCLUDE" main.cpp

# Link: LIBS="-lppapi_simple -lppapi -lppapi_cpp -lnacl_io" $LINK -o minimal_unstripped.bc -pthread -L "$PPAPI_LIBDIR" main.o $LIBS

# Finalize. This step doesn't have an analogy in regular build procedures. # The --help text says "This tool prepares a PNaCl bitcode application for ABI stability." $FINALIZE -o minimal.pexe minimal_unstripped.bc

The pnacl module can not be loaded directly with an <embed> tag. Instead, it must be indirectly loaded via a manifest file:

{
    "program": {
        "portable": {
            "pnacl-translate": {
                "url": "minimal.pexe"
            }
        }
    }
}
<!DOCTYPE html>
<html>
    <head><title>Actually pnacl</title></head>
    <body>
        <embed
            width="200" height="200"
            src="minimal.nmf"
            type="application/x-pnacl"
        ></embed>
    </body>
</html>

Web browsers will not accept the <embed> on a file:-URL, so you need to serve this via HTTP, for example by using python -m SimpleHTTPServer.

You can now actually load this page in Chrome and have the code be compiled to machine code (this can take several seconds) and executed. When it is finished, the message NativeClient: NaCl module crashed will appear in the debug console. This is as expected, because Google doesn't really want PNaCl modules to terminate at all, so they confusingly call this situation a crash.

Portable Native Client requires compilation to machine code before the module can be executed. They call this process "translation". Chrome caches the translated modules rather aggressively, so if it seems that your new changes don't take effect, you can start the HTTP server on a different port to bypass the cache, for example with python -m SimpleHTTPServer 8001.

Hopefully, this is enough to let you set up a proper build environment for developing PNaCl modules or even integrating it into your existing build systems. Keep on reading if you want to actually do stuff with this module.

Basic interaction with the browser

Let's build up some simple interaction with the browser environment. There is a message passing mechanism with the basic Pepper API that we could use directly. However, we will leverage the wrapping they have done for us in ppapi_simple, which lets us pipe standard output from the module into this message passing functionality. The ps_stdout and ps_tty_prefix attributes on the <embed> tag are required for this to work.

We will also pass arguments into the module. In general, the PNaCl module can read all the attributes of the <embed> tag. Additionally, pepper_simple reads the ones called argn and passes them to our main-function as regular command line arguments.

<!DOCTYPE html>
<html>
    <head><title>Actually pnacl</title></head>
    <body>
        <embed
            id="pnacl"
            width="200" height="200"
            src="minimal.nmf"
            type="application/x-pnacl"
            ps_stdout="dev/tty"
            ps_tty_prefix=""
            arg0="arrrr"
        ></embed>
        <pre id="output"></pre>
        <script>
            var pnacl = document.getElementById("pnacl");

            function log_handler(tag) {
                return function () {
                    var args = [tag].concat(Array.prototype.slice.call(arguments));
                    console.log.apply(console, args);
                }
            }

            // Dump data for Progress Events:
            pnacl.addEventListener('loadstart', log_handler("loadstart"));
            pnacl.addEventListener('progress', log_handler("progress"));
            pnacl.addEventListener('load', log_handler("load"));
            pnacl.addEventListener('error', log_handler("error"));
            pnacl.addEventListener('abort', log_handler("abort"));
            pnacl.addEventListener('loadend', log_handler("loadend"));

            // The 'error' and 'abort' events get a descriptive error message:
            function error_handler() {
                console.error("Error occurred:", pnacl.lastError);
            }
            pnacl.addEventListener('error', error_handler);
            pnacl.addEventListener('abort', error_handler);

            // Handle Native Client-specific events:
            pnacl.addEventListener('message', function (ev) {
                var output = document.getElementById("output");
                output.textContent += ev.data;
            });
            pnacl.addEventListener('crash', function () {
                console.log("Exit code:", pnacl.exitStatus);
            });
        </script>
    </body>
</html>

And make the module do slightly more interesting stuff:

#include <iostream>
#include <unistd.h>
#include <ppapi_simple/ps_main.h>

int ppapi_simple_main(int argc, char* argv[]) {
    for (int i=0; i<argc; ++i) {
        std::cout << "Argument " << i << ": " << argv[i] << std::endl;
    }

    std::cerr << "Standard error output appears in the debug console\n";

    // Since PNaCl modules aren't supposed to terminate, we need to give
    // Pepper some breathing room to convey all the messages:
    sleep(3);

    return 0;
}

PPAPI_SIMPLE_REGISTER_MAIN(ppapi_simple_main)

Compile, link and finalize as before, then reload the page in your browser. Be sure to have the debug console open. Hopefully, this should be enough to get you started with PNaCl modules that actually do something.

Further reading

Why don't you try and do something with the 2D graphics API? Hot tip: You can get an InstanceHandle in your pepper_simple module like this: pp::InstanceHandle(PSInstance::GetInstance())

Get deeper into the core of the Pepper API by peeling away pepper_simple and implementing your stuff directly on the C++ (or C) API. Read the source code of pepper_simple and transform your own module to work directly with ppapi/cpp or have a go at the official tutorial, which doesn't use ppapi_simple.

Want to load a resource over HTTP in your module? iurlstream will let you load URLs in a backwards compatible blocking fashion, see iurlstream.hpp, iurlstream.cpp, urlbuf.hpp and urlbuf.cpp.

Happy hacking!

, 2014