Unifex — integrate native code with Elixir without a hassle

Bartosz Błaszków
Membrane Framework
Published in
3 min readFeb 28, 2019

While developing Membrane Framework, which is our solution for multimedia streaming written in Elixir, we often have to rely on native code to integrate with existing libraries or when we need a native performance in crucial bits of code.

The Erlang VM, on which the Elixir code is executed, allows you to create NIFs (Native Implemented Functions) — functions implemented in C that can be used just like any other function defined in Elixir or Erlang module. With the v0.2.0 release of Membrane Core, we introduced Unifex, a tool that makes NIF creation easier.

Unifex job is to generate an interface between a native C code and Elixir. Based on a file with .spec.exs extension, it creates the C boilerplate. All you have to do is to provide the definition (and destructor) of a struct storing state and implement the functions using generated helpers.

Here’s an example of .spec.exs file:

The above spec defined that we would like to implement two functions for Example.Native module called foo and init; we’d also like to send a message {:example_msg, number} from the native code. Below you can find the native part of the implementation:

What are the benefits of using Unifex?

Argument parsing

Please take a look at the init function from the example.c above — it gets an int as an argument. How could that be possible with Elixir (and Erlang) using dynamic typing? Well, normally when implementing a NIF, your function would receive an array of generic Erlang terms (ERL_NIF_TERM type) that you would parse to a proper type (and if that fails, return an error). Unifex handles that for you. And saves you from mistakes like confusing the order of arguments and using incorrect indices. Here’s the comparison of code with and without Unifex:

Automatically generated helpers for returned values and messages sent — one simple call to rule them all!

Thanks to the helpers generated by our tool, a conversion the other way around (from native types to terms) and sending messages become as easy as can be. Below you can find an example contrasting plain NIF and Unifex. Both snippets send the {:example_msg, number} message and return either {:error, :send_failed} or {:ok, number}. Just imagine how complicated it would become if we wanted to return a more complex result, e.g., nested tuples. Another example shows the same can be said about message sending.

Separation from erl_nif library

Unifex creates an abstraction layer above erl_nif library. The NIFs written with Unifex does not directly use that library. Thanks to that, if in the future we would like to run the native code in a separate process (e.g., using Erlang port mechanism instead of NIFs), we could add support for it in Unifex while keeping the API (more or less) the same.

That’s not the only benefit of having an abstraction layer on top of erl_nif. Let’s consider a call that sends a message:

enif_send(NULL, target, env, term);

As a matter of fact, both 1st and 3rd arguments are ErlNifEnvs. Without getting into details — if you’re making a call from a created thread (i.e., outside of Erlang VM scheduler thread executing the NIF), you should swap the NULL and env in that call. Unfortunately, it’s quite easy to forget which variant should be used for each case. Unifex wraps that call and uses more explicit UNIFEX_NO_FLAGS and UNIFEX_SEND_THREADED flags to determine the proper NULL and env order.

If you’re interested in using our tool and would like to see it in action, be sure to check out the simple tutorial that will, step by step, guide you to writing your first Unifex-based NIF. Also, the docs might be quite helpful, and the source code is available on GitHub. Of course, since this is an initial release of our tool, it may still be a bit rough around the edges (e.g., some native types are not supported yet), but it has already proven its worth in Membrane elements, such as MAD Decoder or FFmpeg-based H264 Encoder/Decoder.

--

--

Bartosz Błaszków
Membrane Framework

Software engineer at Software Mansion. Part of the Membrane Core development team.