# nif_call
[![Hex.pm](https://img.shields.io/hexpm/v/nif_call.svg?style=flat&color=blue)](https://hex.pm/packages/nif_call)
Call Erlang/Elixir functions from NIF.
## Usage
### 1. Add `nif_call` as a dependency
Add `nif_call` as a dependency in your `mix.exs` file.
```elixir
defp deps do
[
{:nif_call, "~> 0.1"}
]
end
```
### 2. Get the header file
It's recommended to use the `nif_call`'s mix task to get the bundled header file. Assuming you're currently in the root directory of your project, run the following command:
```bash
mix nif_call.put_header
```
By default, the header file will be put in the `c_src` directory. It may look like this:
```bash
.
├── Makefile
├── c_src
│ ├── demo_nif.cpp
│ └── nif_call.h <-- From this repository
├── lib
│ └── demo
│ ├── application.ex
│ └── demo.ex
├── mix.exs
└── mix.lock
```
You can also change the directory by passing the `--dir` option.
```bash
mix nif_call.put_header --dir lib/nif_call
```
If there's already a `nif_call.h` file in the target directory, you may want to overwrite it by passing the `--overwrite` option.
```bash
mix nif_call.put_header --overwrite
```
### 3. Add runner processes to your supervision tree
In applications that use `nif_call`, you need to add runner processes to the supervision tree. The runner processes are responsible for evaluating the Elixir functions called from NIF. In this demo project, we will add a runner process named `Demo.Runner` by adding the following code to the `lib/demo/application.ex` file.
```elixir
{NifCall.Runner, runner_opts: [nif_module: Demo.NIF, on_evaluated: :nif_call_evaluated], name: Demo.Runner}
```
The `application.ex` file should look like this:
```elixir
defmodule Demo.Application do
@moduledoc false
use Application
@impl true
def start(_type, _args) do
children = [
{NifCall.Runner,
runner_opts: [nif_module: Demo.NIF, on_evaluated: :nif_call_evaluated], name: Demo.Runner}
]
opts = [strategy: :one_for_one, name: Demo.Supervisor]
Supervisor.start_link(children, opts)
end
end
```
`Demo.NIF` is the module that contains your NIF functions. The `on_evaluated` option is the name of the callback function that will be called by `nif_call` to send the evaluated result back to the caller. The default name is `nif_call_evaluated`.
To send the evaluated result back to the caller, `nif_call` needs to inject one NIF function to do that.
```elixir
# lib/demo/nif.ex
defmodule Demo.NIF do
use NifCall.NIF
end
```
If you have changed the name of the callback function for the runner process, you need to specify it in the `on_evaluated` option.
```elixir
# lib/demo/nif.ex
defmodule Demo.NIF do
use NifCall.NIF, on_evaluated: :my_evaluated
end
```
### 4. Prepare C code
In your NIF code, include `nif_call.h` and define the `NIF_CALL_IMPLEMENTATION` macro before including it.
```c
// c_src/demo_nif.cpp
#define NIF_CALL_IMPLEMENTATION
#include "nif_call.h"
```
And remember to initialize nif_call in the `onload` function.
```c
// c_src/demo_nif.cpp
static int on_load(ErlNifEnv *env, void **, ERL_NIF_TERM) {
// initialize nif_call
return nif_call_onload(env);
}
```
Lastly, inject the NIF function:
```c
// c_src/demo_nif.cpp
static ErlNifFunc nif_functions[] = {
// ... your other NIF functions
// inject nif_call functions
// `nif_call_evaluated` is the name of the callback function that will be called by nif_call
NIF_CALL_NIF_FUNC(nif_call_evaluated),
// of course, you can change the name of the callback function
// but remember to change it in the Elixir code as well
// NIF_CALL_NIF_FUNC(my_evaluated),
};
```
### 5. Call Erlang/Elixir functions from NIF
Let's try to implement a simple function that adds 1 to the given value and sends the intermediate result to Elixir for further processing. The result of the Elixir callback function is returned as the final result.
Firstly, implement the `add_one` function in the Elixir code.
```elixir
# lib/demo/demo.ex
defmodule Demo do
@doc """
Add 1 to the `value` in NIF and send the intermediate result to
Elixir for further processing using the `callback` function.
The result of the `callback` function is returned as the final result.
## Examples
iex> Demo.add_one(1, fn result -> result * 2 end)
4
"""
def add_one(value, callback) do
# remember to change the name of the Evaluator module if you have changed it
# and pass both the evaluator and the callback function to the NIF
evaluator = Process.whereis(Demo.Evaluator)
Demo.NIF.add_one(value, evaluator, callback)
end
def add_one(value, callback) do
# Use `NifCall.run/3` to call the NIF function
#
# - The second argument is the callback function that will be called from the NIF
#
# - The third argument is the function that can invoke somes NIF functions,
# this is where you normally call the NIF function
#
# notice that the third argument is a function that takes a `tag` as an argument
# the `tag` is used as a reference to the callback function in your `Demo.Runner` process
NifCall.run(Demo.Runner, callback, fn tag ->
Demo.NIF.add_one(value, tag)
end)
end
end
```
After that, implement the `add_one` function in the NIF C code.
```c
// c_src/demo_nif.cpp
static ERL_NIF_TERM add_one(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
ErlNifSInt64 a;
ERL_NIF_TERM tag = argv[1];
if (!enif_get_int64(env, argv[0], &a)) return enif_make_badarg(env);
ERL_NIF_TERM result_term = enif_make_int64(env, a + 1);
// send the intermediate result to Elixir for further processing
// `make_nif_call` will return the result of the callback function
// which is the final result in this case
NifCallResult result = make_nif_call(env, tag, result_term);
return result.is_ok() ? result.get_value() : enif_make_tuple2(env, enif_make_atom(env, "error"), result.get_err());
}
```
Most importantly, don't forget to add the NIF function to the `nif_functions` array, and **they have to be marked as dirty NIF functions**.
```c
// c_src/demo_nif.cpp
static ErlNifFunc nif_functions[] = {
// ... your other NIF functions
// inject nif_call functions
NIF_CALL_NIF_FUNC(nif_call_evaluated),
// add the NIF function
// NIF functions that calls Elixir functions have to be marked as dirty
// either ERL_NIF_DIRTY_JOB_CPU_BOUND or ERL_NIF_DIRTY_JOB_IO_BOUND
{"add_one", 3, add_one, ERL_NIF_DIRTY_JOB_CPU_BOUND},
};
```
Now, you can call the `add_one` function from Elixir.
```elixir
iex> Demo.add_one(1, fn result -> result * 2 end)
4
```
Congratulations! You have successfully called an Elixir function from NIF.
There's a slightly more complex example in the `example` directory, which shows that you can make multiple calls to Elixir functions from NIF and
use the intermediate results in the next call.