檢視原始碼 NIFs
本節概述如何使用原生實作函式(NIFs)解決範例問題中的範例問題。
與使用埠驅動程式相比,NIFs 是呼叫 C 程式碼更簡單且更有效率的方式。NIFs 最適合用於同步函式,例如範例中的 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 識別符。它將由巨集字串化。
- 第二個參數是包含每個 NIF 的名稱、arity 和函式指標的
ErlNifFunc
結構陣列。 - 其餘參數是指向可用於初始化程式庫的回呼函式的指標。在此簡單範例中未使用它們,因此它們都設定為
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")