8 NIFs
本节概述了如何使用本机实现函数 (NIFs) 解决 问题示例 中示例问题的示例。
NIFs 是比使用端口驱动程序更简单、更高效的调用 C 代码的方式。 NIFs 最适合用于同步函数,例如示例中的 foo 和 bar,这些函数执行一些相对较短的计算,没有副作用并返回结果。
NIF 是用 C 而不是 Erlang 实现的函数。 NIF 对调用者来说与其他任何函数一样。 它们属于一个模块,并且像任何其他 Erlang 函数一样被调用。 模块的 NIFs 被编译并链接到一个动态可加载的共享库(在 UNIX 中为 SO,在 Windows 中为 DLL)。 NIF 库必须在运行时由模块的 Erlang 代码加载。
由于 NIF 库是动态链接到模拟器进程中的,因此这是从 Erlang 调用 C 代码的最快方式(与端口驱动程序并列)。 调用 NIFs 不需要任何上下文切换。 但它也是最不安全的,因为 NIF 中的崩溃也会导致模拟器崩溃。
8.1 Erlang 程序
即使模块的所有函数都是 NIFs,仍然需要 Erlang 模块,原因有两个:
- NIF 库必须由同一模块中的 Erlang 代码显式加载。
- 模块的所有 NIFs 也必须具有 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 实现。
8.2 NIF 库代码
模块的 NIFs 被编译并链接到一个共享库中。 每个 NIF 都实现为一个普通的 C 函数。 宏 ERL_NIF_INIT 与结构数组一起定义了模块中所有 NIFs 的名称、元数和函数指针。 必须包含头文件 erl_nif.h。 由于库是一个共享模块,而不是一个程序,因此不应该存在任何主函数。
传递给 NIF 的函数参数出现在数组 argv 中,argc 作为数组的长度,因此是函数的元数。 函数的第 N 个参数可以访问为 argv[N-1]。 NIFs 还接受一个环境参数,该参数充当不透明句柄,需要传递给大多数 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 的名称、元数和函数指针。
- 其余参数是指向回调函数的指针,这些回调函数可用于初始化库。 它们没有在本例中使用,因此它们都被设置为 NULL。
函数参数和返回值表示为类型 ERL_NIF_TERM 的值。 这里,函数如 enif_get_int 和 enif_make_int 用于在 Erlang 项和 C 类型之间进行转换。 如果函数参数 argv[0] 不是整数,enif_get_int 返回 false,在这种情况下,它通过抛出 badarg 异常并使用 enif_make_badarg 返回。
8.3 运行示例
步骤 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")