8. NIFs

8 NIFs

本节概述了如何Problem Example使用Native Implemented Functions(NIF)解决示例问题的示例。

作为实验性特征,在Erlang/OTP R13B03中引入了NIF。与使用端口驱动程序相比,它是一种调用C代码的更简单,更高效的方法。NIF最适合于同步函数,比如foobar在这个例子中,它们做了一些相对较短的计算,没有副作用并返回结果。

NIF是一个用C代替Erlang实现的函数。NIF对呼叫者来说显示为任何其他功能。它们属于一个模块,并且像其他任何Erlang函数一样被调用。一个模块的NIF被编译并链接到一个动态可加载的共享库(UNIX中的SO,Windows中的DLL)。NIF库必须在运行时由模块的Erlang代码加载。

由于NIF库被动态链接到仿真器进程中,这是从Erlang调用C代码(与端口驱动程序一起)的最快方式。调用NIF不需要上下文切换。但它也是最不安全的,因为NIF的崩溃也使仿真器失效。

8.1 Erlang程序

即使模块的所有功能都是NIF,仍然需要Erlang模块,原因有两个:

  • NIF库必须在同一模块中由Erlang代码显式加载。

  • 模块的所有NIF也必须具有Erlang实现。

通常情况下,这些是最小的存根实现抛出异常。但是它们也可以用作在某些体系结构中没有本机实现的功能的回退实现。

通过调用加载NIF库erlang:load_nif/2,并以共享库的名称作为参数。第二个参数可以是任何将传递给库并用于初始化的术语:

-module(complex6). -export([foo/1, bar/1]). -on_load(init/0). init() -> ok = erlang:load_nif("./complex6_nif", 0). foo(_X) -> exit(nif_library_not_loaded). bar(_Y) -> exit(nif_library_not_loaded).

这里,该指令on_load用于init在模块加载时自动调用函数。如果init返回其他任何内容ok,例如在本例中加载NIF库失败时,模块被卸载并调用其中的函数,则失败。

加载NIF库将覆盖存根实现,并导致调用foo并将bar其分发到NIF实现。

8.2 NIF库代码

模块的NIF被编译并链接到共享库中。每个NIF都是作为一个普通的C函数实现的。宏ERL_NIF_INIT和一个结构数组定义了模块中所有NIF的名称,参数和函数指针。头文件erl_nif.h必须包含在内。由于库是共享模块,而不是程序,因此不存在主要功能。

传递给NIF的函数参数出现在一个数组中argv,并argc以数组的长度作为函数的参数。该函数的第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的名称,参数和函数指针的结构数组。

  • 其余参数指向可用于初始化库的回调函数。这个简单的例子中没有使用它们,因此它们都被设置为NULL

函数参数和返回值表示为类型的值ERL_NIF_TERM。在这里,函数像enif_get_intenif_make_int用来在Erlang和C-type之间进行转换。如果函数参数argv[0]不是整数,则enif_get_int返回false,在这种情况下,它通过抛出一个badarg-exception来返回enif_make_badarg

8.3运行示例

第一步。编译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")