查看源码 NIF
本节概述如何使用本机实现函数 (NIF) 解决问题示例中的示例问题。
与使用端口驱动程序相比,NIF 是一种更简单、更高效的调用 C 代码的方式。NIF 最适合同步函数,例如示例中的 foo
和 bar
,它们执行一些相对较短的计算,没有副作用并返回结果。
NIF 是一个用 C 而不是 Erlang 实现的函数。对于调用者来说,NIF 看起来像任何其他函数。它们属于一个模块,并且像任何其他 Erlang 函数一样被调用。模块的 NIF 被编译并链接到动态可加载的共享库(UNIX 中为 SO,Windows 中为 DLL)。NIF 库必须由模块的 Erlang 代码在运行时加载。
由于 NIF 库被动态链接到模拟器进程中,因此这是从 Erlang 调用 C 代码的最快方式(与端口驱动程序一起)。调用 NIF 不需要上下文切换。但它也是最不安全的,因为 NIF 中的崩溃也会导致模拟器崩溃。
Erlang 程序
即使模块的所有函数都是 NIF,仍然需要 Erlang 模块,原因有两个:
- NIF 库必须由同一模块中的 Erlang 代码显式加载。
- 模块的所有 NIF 也必须有 Erlang 实现。
通常,这些是抛出异常的最小存根实现。但是它们也可以用作在某些架构上没有本机实现的函数的后备实现。
NIF 库通过调用 erlang:load_nif/2
加载,其中共享库的名称作为参数。第二个参数可以是任何将传递给库并用于初始化的项。
-module(complex6).
-export([foo/1, bar/1]).
-nifs([foo/1, bar/1]).
-on_load(init/0).
init() ->
ok = erlang:load_nif("./complex6_nif", 0).
foo(_X) ->
erlang:nif_error(nif_library_not_loaded).
bar(_Y) ->
erlang:nif_error(nif_library_not_loaded).
这里,指令 on_load
用于在模块加载时自动调用函数 init
。如果 init
返回除 ok
之外的任何值,例如在此示例中加载 NIF 库失败时,模块将被卸载,并且对其中函数的调用将失败。
加载 NIF 库会覆盖存根实现,并导致对 foo
和 bar
的调用被分派到 NIF 实现。
NIF 库代码
模块的 NIF 被编译并链接到共享库中。每个 NIF 都实现为普通的 C 函数。宏 ERL_NIF_INIT
与结构数组一起定义模块中所有 NIF 的名称、arity 和函数指针。必须包含头文件 erl_nif.h
。由于该库是一个共享模块,而不是程序,因此不存在 main 函数。
传递给 NIF 的函数参数出现在数组 argv
中,其中 argc
是数组的长度,因此也是函数的 arity。函数的第 N 个参数可以作为 argv[N-1]
访问。NIF 还接受一个环境参数,该参数充当不透明的句柄,需要将其传递给大多数 API 函数。环境包含有关调用 Erlang 进程的信息。
#include <erl_nif.h>
extern int foo(int x);
extern int bar(int y);
static ERL_NIF_TERM foo_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
int x, ret;
if (!enif_get_int(env, argv[0], &x)) {
return enif_make_badarg(env);
}
ret = foo(x);
return enif_make_int(env, ret);
}
static ERL_NIF_TERM bar_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
int y, ret;
if (!enif_get_int(env, argv[0], &y)) {
return enif_make_badarg(env);
}
ret = bar(y);
return enif_make_int(env, ret);
}
static ErlNifFunc nif_funcs[] = {
{"foo", 1, foo_nif},
{"bar", 1, bar_nif}
};
ERL_NIF_INIT(complex6, nif_funcs, NULL, NULL, NULL, NULL)
这里,ERL_NIF_INIT
具有以下参数:
- 第一个参数必须是 Erlang 模块的名称,作为 C 标识符。它将被宏字符串化。
- 第二个参数是
ErlNifFunc
结构数组,其中包含每个 NIF 的名称、arity 和函数指针。 - 其余参数是指向可用于初始化库的回调函数的指针。在这个简单的示例中它们未使用,因此它们都设置为
NULL
。
函数参数和返回值表示为类型 ERL_NIF_TERM
的值。这里,诸如 enif_get_int
和 enif_make_int
之类的函数用于在 Erlang 项和 C 类型之间进行转换。如果函数参数 argv[0]
不是整数,则 enif_get_int
返回 false,在这种情况下,它通过使用 enif_make_badarg
抛出 badarg
异常来返回。
运行示例
步骤 1. 编译 C 代码
unix> gcc -o complex6_nif.so -fpic -shared complex.c complex6_nif.c
windows> cl -LD -MD -Fe complex6_nif.dll complex.c complex6_nif.c
步骤 2: 启动 Erlang 并编译 Erlang 代码
> erl
Erlang R13B04 (erts-5.7.5) [64-bit] [smp:4:4] [rq:4] [async-threads:0] [kernel-poll:false]
Eshell V5.7.5 (abort with ^G)
1> c(complex6).
{ok,complex6}
步骤 3: 运行示例
3> complex6:foo(3).
4
4> complex6:bar(5).
10
5> complex6:foo("not an integer").
** exception error: bad argument
in function complex6:foo/1
called as comlpex6:foo("not an integer")