檢視原始碼 如何實作驅動程式
注意
本節撰寫於很久以前。雖然大部分內容仍然有效,因為它解釋了重要的概念,但這是為較舊的驅動程式介面撰寫的,因此範例不再適用。建議讀者同時閱讀
erl_driver
和driver_entry
文件。
簡介
本節描述如何為 Erlang 建構您自己的驅動程式。
Erlang 中的驅動程式是一個以 C 語言撰寫的程式庫,它連結到 Erlang 模擬器並從 Erlang 呼叫。當 C 語言比 Erlang 更適合、用於加速或提供對 Erlang 無法直接存取的 OS 資源的存取時,可以使用驅動程式。
驅動程式可以動態載入,作為共享程式庫(在 Windows 上稱為 DLL),或靜態載入,在編譯和連結時與模擬器連結。此處僅描述動態載入的驅動程式,靜態連結的驅動程式超出本節的範圍。
警告
當驅動程式載入時,它會在模擬器的上下文中執行,共享相同的記憶體和相同的執行緒。這表示驅動程式中的所有操作都必須是非阻塞的,並且驅動程式中的任何崩潰都會導致整個模擬器崩潰。簡而言之,請小心。
範例驅動程式
本節描述一個簡單的驅動程式,用於使用 libpq C 用戶端程式庫存取 postgres 資料庫。使用 Postgres 是因為它是免費且開源的。有關 postgres 的資訊,請參閱 www.postgres.org。
驅動程式是同步的,它使用用戶端程式庫的同步呼叫。這僅為了簡單起見,但不好,因為它會在等待資料庫時停止模擬器。下面將使用異步範例驅動程式來改進此問題。
程式碼很簡單:Erlang 和驅動程式之間的所有通訊都使用 port_control/3
完成,驅動程式使用 rbuf
返回資料。
Erlang 驅動程式僅匯出一個函式:驅動程式進入函式。這是使用巨集 DRIVER_INIT
定義的,它會傳回指向 C struct
的指標,其中包含從模擬器呼叫的進入點。struct
定義模擬器呼叫以呼叫驅動程式的進入點,對於驅動程式未定義和使用的進入點則使用 NULL
指標。
當驅動程式使用 open_port/2
作為埠開啟時,會呼叫 start
進入點。在這裡,我們為使用者資料結構配置記憶體。每次模擬器呼叫我們時,都會傳遞此使用者資料。首先,我們儲存驅動程式控制代碼,因為稍後的呼叫會需要它。我們為 LibPQ 使用的連線控制代碼配置記憶體。我們還透過呼叫 set_port_control_flags
,設定旗標 PORT_CONTROL_FLAG_BINARY
,來設定埠以傳回配置的驅動程式二進位檔。(這是因為我們不知道我們的資料是否適合 control
的結果緩衝區,該緩衝區具有模擬器設定的預設大小 64 位元組。)
當驅動程式載入時,會呼叫進入點 init
。但是,我們不使用它,因為它只執行一次,而且我們希望能夠有多個驅動程式實例。
當埠關閉時,會呼叫 stop
進入點。
當 Erlang 程式碼呼叫 port_control/3
時,會從模擬器呼叫 control
進入點來執行實際工作。我們定義了一組簡單的命令:connect
登入資料庫、disconnect
登出,以及 select
發送 SQL 查詢並取得結果。所有結果都透過 rbuf
返回。 ei
程式庫位於 erl_interface
中,用於以二進位項格式編碼資料。結果以二進位項的形式返回給模擬器,因此在 Erlang 中呼叫 binary_to_term
以將結果轉換為項形式。
該程式碼可在 erts
的 sample
目錄中的 pg_sync.c
中取得。
驅動程式進入點包含模擬器將呼叫的函式。在此範例中,僅提供 start
、stop
和 control
。
/* Driver interface declarations */
static ErlDrvData start(ErlDrvPort port, char *command);
static void stop(ErlDrvData drv_data);
static int control(ErlDrvData drv_data, unsigned int command, char *buf,
int len, char **rbuf, int rlen);
static ErlDrvEntry pq_driver_entry = {
NULL, /* init */
start,
stop,
NULL, /* output */
NULL, /* ready_input */
NULL, /* ready_output */
"pg_sync", /* the name of the driver */
NULL, /* finish */
NULL, /* handle */
control,
NULL, /* timeout */
NULL, /* outputv */
NULL, /* ready_async */
NULL, /* flush */
NULL, /* call */
NULL /* event */
};
我們有一個結構來儲存驅動程式所需的狀態,在此案例中,我們只需要保留資料庫連線
typedef struct our_data_s {
PGconn* conn;
} our_data_t;
我們定義的控制碼如下
/* Keep the following definitions in alignment with the
* defines in erl_pq_sync.erl
*/
#define DRV_CONNECT 'C'
#define DRV_DISCONNECT 'D'
#define DRV_SELECT 'S'
這會傳回驅動程式結構。巨集 DRIVER_INIT
定義唯一匯出的函式。所有其他函式都是靜態的,不會從程式庫匯出。
/* INITIALIZATION AFTER LOADING */
/*
* This is the init function called after this driver has been loaded.
* It must *not* be declared static. Must return the address to
* the driver entry.
*/
DRIVER_INIT(pq_drv)
{
return &pq_driver_entry;
}
在此處完成一些初始化,start
是從 open_port/2
呼叫的。資料將傳遞給 control
和 stop
。
/* DRIVER INTERFACE */
static ErlDrvData start(ErlDrvPort port, char *command)
{
our_data_t* data;
data = (our_data_t*)driver_alloc(sizeof(our_data_t));
data->conn = NULL;
set_port_control_flags(port, PORT_CONTROL_FLAG_BINARY);
return (ErlDrvData)data;
}
我們呼叫 disconnect 以從資料庫登出。(這應該從 Erlang 完成,但以防萬一。)
static int do_disconnect(our_data_t* data, ei_x_buff* x);
static void stop(ErlDrvData drv_data)
{
our_data_t* data = (our_data_t*)drv_data;
do_disconnect(data, NULL);
driver_free(data);
}
我們僅使用二進位格式將資料返回給模擬器;輸入資料是 connect
和 select
的字串參數。傳回的資料由 Erlang 項組成。
函式 get_s
和 ei_x_to_new_binary
是用於縮短程式碼的工具。get_s
會複製字串並以零結尾,因為 postgres 用戶端程式庫需要這樣。ei_x_to_new_binary
接受 ei_x_buff
緩衝區,配置二進位檔,並將資料複製到該處。此二進位檔在 *rbuf
中傳回。(請注意,此二進位檔由模擬器釋放,而不是由我們釋放。)
static char* get_s(const char* buf, int len);
static int do_connect(const char *s, our_data_t* data, ei_x_buff* x);
static int do_select(const char* s, our_data_t* data, ei_x_buff* x);
/* As we are operating in binary mode, the return value from control
* is irrelevant, as long as it is not negative.
*/
static int control(ErlDrvData drv_data, unsigned int command, char *buf,
int len, char **rbuf, int rlen)
{
int r;
ei_x_buff x;
our_data_t* data = (our_data_t*)drv_data;
char* s = get_s(buf, len);
ei_x_new_with_version(&x);
switch (command) {
case DRV_CONNECT: r = do_connect(s, data, &x); break;
case DRV_DISCONNECT: r = do_disconnect(data, &x); break;
case DRV_SELECT: r = do_select(s, data, &x); break;
default: r = -1; break;
}
*rbuf = (char*)ei_x_to_new_binary(&x);
ei_x_free(&x);
driver_free(s);
return r;
}
do_connect
是我們登入資料庫的地方。如果連線成功,我們將連線控制代碼儲存在驅動程式資料中,並傳回 'ok'
。否則,我們會傳回來自 postgres 的錯誤訊息,並在驅動程式資料中儲存 NULL
。
static int do_connect(const char *s, our_data_t* data, ei_x_buff* x)
{
PGconn* conn = PQconnectdb(s);
if (PQstatus(conn) != CONNECTION_OK) {
encode_error(x, conn);
PQfinish(conn);
conn = NULL;
} else {
encode_ok(x);
}
data->conn = conn;
return 0;
}
如果我們已連線(且連線控制代碼不是 NULL
),我們會從資料庫登出。我們需要檢查是否應編碼 'ok'
,因為我們可以從函式 stop
取得此處,而該函式不會將資料傳回給模擬器
static int do_disconnect(our_data_t* data, ei_x_buff* x)
{
if (data->conn == NULL)
return 0;
PQfinish(data->conn);
data->conn = NULL;
if (x != NULL)
encode_ok(x);
return 0;
}
我們執行查詢並編碼結果。編碼在另一個 C 模組 pg_encode.c
中完成,該模組也作為範例程式碼提供。
static int do_select(const char* s, our_data_t* data, ei_x_buff* x)
{
PGresult* res = PQexec(data->conn, s);
encode_result(x, res, data->conn);
PQclear(res);
return 0;
}
在這裡,我們檢查來自 postgres 的結果。如果是資料,我們會將其編碼為具有欄資料的清單清單。來自 postgres 的所有內容都是 C 字串,因此我們使用 ei_x_encode_string
將結果作為字串傳送到 Erlang。(清單的開頭包含欄名稱。)
void encode_result(ei_x_buff* x, PGresult* res, PGconn* conn)
{
int row, n_rows, col, n_cols;
switch (PQresultStatus(res)) {
case PGRES_TUPLES_OK:
n_rows = PQntuples(res);
n_cols = PQnfields(res);
ei_x_encode_tuple_header(x, 2);
encode_ok(x);
ei_x_encode_list_header(x, n_rows+1);
ei_x_encode_list_header(x, n_cols);
for (col = 0; col < n_cols; ++col) {
ei_x_encode_string(x, PQfname(res, col));
}
ei_x_encode_empty_list(x);
for (row = 0; row < n_rows; ++row) {
ei_x_encode_list_header(x, n_cols);
for (col = 0; col < n_cols; ++col) {
ei_x_encode_string(x, PQgetvalue(res, row, col));
}
ei_x_encode_empty_list(x);
}
ei_x_encode_empty_list(x);
break;
case PGRES_COMMAND_OK:
ei_x_encode_tuple_header(x, 2);
encode_ok(x);
ei_x_encode_string(x, PQcmdTuples(res));
break;
default:
encode_error(x, conn);
break;
}
}
編譯和連結範例驅動程式
驅動程式將被編譯並連結到共享程式庫(Windows 上的 DLL)。使用 gcc 時,這是使用連結旗標 -shared
和 -fpic
完成的。由於我們使用 ei
程式庫,我們也應該將其包含在內。ei
有多個版本,針對偵錯或非偵錯以及多執行緒或單執行緒進行編譯。在範例的 makefile 中,obj
目錄用於 ei
程式庫,這表示我們使用非偵錯的單執行緒版本。
在 Erlang 中以埠的形式呼叫驅動程式
在可以從 Erlang 呼叫驅動程式之前,必須先載入並開啟它。載入是使用 erl_ddll
模組完成的(載入動態驅動程式的 erl_ddll
驅動程式實際上本身就是一個驅動程式)。如果載入成功,可以使用 open_port/2
開啟埠。埠名稱必須與共享程式庫的名稱和驅動程式進入結構中的名稱相符。
開啟埠後,即可呼叫驅動程式。在 pg_sync
範例中,我們沒有來自埠的任何資料,只有來自 port_control/3
的傳回值。
以下程式碼是同步 postgres 驅動程式的 Erlang 部分,pg_sync.erl
-module(pg_sync).
-define(DRV_CONNECT, 1).
-define(DRV_DISCONNECT, 2).
-define(DRV_SELECT, 3).
-export([connect/1, disconnect/1, select/2]).
connect(ConnectStr) ->
case erl_ddll:load_driver(".", "pg_sync") of
ok -> ok;
{error, already_loaded} -> ok;
E -> exit({error, E})
end,
Port = open_port({spawn, ?MODULE}, []),
case binary_to_term(port_control(Port, ?DRV_CONNECT, ConnectStr)) of
ok -> {ok, Port};
Error -> Error
end.
disconnect(Port) ->
R = binary_to_term(port_control(Port, ?DRV_DISCONNECT, "")),
port_close(Port),
R.
select(Port, Query) ->
binary_to_term(port_control(Port, ?DRV_SELECT, Query)).
API 很簡單
connect/1
載入驅動程式、開啟它並登入資料庫,如果成功,則傳回 Erlang 埠。select/2
將查詢傳送到驅動程式並傳回結果。disconnect/1
關閉資料庫連線和驅動程式。(但是,它不會卸載它。)
連線字串應為 postgres 的連線字串。
使用 erl_ddll:load_driver/2
載入驅動程式。如果此操作成功,或者如果已載入,則會開啟它。這將呼叫驅動程式中的 start
函式。
我們使用 port_control/3
函式來進行所有對驅動程式的呼叫。驅動程式的結果會立即返回,並透過呼叫 binary_to_term/1
轉換為 term。(我們信任從驅動程式返回的 term 格式正確,否則 binary_to_term/1
的呼叫可能會被包含在 catch
內。)
非同步驅動程式範例
有時資料庫查詢可能需要很長時間才能完成,在我們的 pg_sync
驅動程式中,當驅動程式執行其工作時,模擬器會暫停。這通常是無法接受的,因為沒有其他 Erlang 程序有機會執行任何操作。為了改進我們的 postgres 驅動程式,我們使用 LibPQ 中的非同步呼叫重新實作它。
驅動程式的非同步版本位於範例檔案 pg_async.c
和 pg_asyng.erl
中。
/* Driver interface declarations */
static ErlDrvData start(ErlDrvPort port, char *command);
static void stop(ErlDrvData drv_data);
static int control(ErlDrvData drv_data, unsigned int command, char *buf,
int len, char **rbuf, int rlen);
static void ready_io(ErlDrvData drv_data, ErlDrvEvent event);
static ErlDrvEntry pq_driver_entry = {
NULL, /* init */
start,
stop,
NULL, /* output */
ready_io, /* ready_input */
ready_io, /* ready_output */
"pg_async", /* the name of the driver */
NULL, /* finish */
NULL, /* handle */
control,
NULL, /* timeout */
NULL, /* outputv */
NULL, /* ready_async */
NULL, /* flush */
NULL, /* call */
NULL /* event */
};
typedef struct our_data_t {
PGconn* conn;
ErlDrvPort port;
int socket;
int connecting;
} our_data_t;
相較於 pg_sync.c
,有些地方發生了變化:我們使用 ready_io
作為 ready_input
和 ready_output
的入口點,只有當有資料從 socket 讀取時,才會從模擬器呼叫它。(實際上,socket 是在模擬器內的 select
函式中使用,當 socket 發出訊號,表示有資料要讀取時,就會呼叫 ready_input
入口點。更多相關資訊如下。)
我們的驅動程式資料也擴展了,我們追蹤用於與 postgres 通訊的 socket,以及當我們使用 driver_output
向 port 發送資料時所需的 port。我們有一個旗標 connecting
,用來判斷驅動程式是在等待連線還是等待查詢結果。(這是必要的,因為當連線和有查詢結果時,都會呼叫 ready_io
入口點。)
static int do_connect(const char *s, our_data_t* data)
{
PGconn* conn = PQconnectStart(s);
if (PQstatus(conn) == CONNECTION_BAD) {
ei_x_buff x;
ei_x_new_with_version(&x);
encode_error(&x, conn);
PQfinish(conn);
conn = NULL;
driver_output(data->port, x.buff, x.index);
ei_x_free(&x);
}
PQconnectPoll(conn);
int socket = PQsocket(conn);
data->socket = socket;
driver_select(data->port, (ErlDrvEvent)socket, DO_READ, 1);
driver_select(data->port, (ErlDrvEvent)socket, DO_WRITE, 1);
data->conn = conn;
data->connecting = 1;
return 0;
}
connect
函式的外觀也有些不同。我們使用非同步的 PQconnectStart
函式進行連線。連線開始後,我們使用 PQsocket
取得連線的 socket。這個 socket 與 driver_select
函式一起使用,以等待連線。當 socket 準備好輸入或輸出時,就會呼叫 ready_io
函式。
請注意,只有當這裡發生錯誤時,我們才會返回資料(使用 driver_output
),否則我們會等待連線完成,在這種情況下,會呼叫我們的 ready_io
函式。
static int do_select(const char* s, our_data_t* data)
{
data->connecting = 0;
PGconn* conn = data->conn;
/* if there's an error return it now */
if (PQsendQuery(conn, s) == 0) {
ei_x_buff x;
ei_x_new_with_version(&x);
encode_error(&x, conn);
driver_output(data->port, x.buff, x.index);
ei_x_free(&x);
}
/* else wait for ready_output to get results */
return 0;
}
do_select
函式會啟動 select,如果沒有立即發生的錯誤,則會返回。當呼叫 ready_io
時,會返回結果。
static void ready_io(ErlDrvData drv_data, ErlDrvEvent event)
{
PGresult* res = NULL;
our_data_t* data = (our_data_t*)drv_data;
PGconn* conn = data->conn;
ei_x_buff x;
ei_x_new_with_version(&x);
if (data->connecting) {
ConnStatusType status;
PQconnectPoll(conn);
status = PQstatus(conn);
if (status == CONNECTION_OK)
encode_ok(&x);
else if (status == CONNECTION_BAD)
encode_error(&x, conn);
} else {
PQconsumeInput(conn);
if (PQisBusy(conn))
return;
res = PQgetResult(conn);
encode_result(&x, res, conn);
PQclear(res);
for (;;) {
res = PQgetResult(conn);
if (res == NULL)
break;
PQclear(res);
}
}
if (x.index > 1) {
driver_output(data->port, x.buff, x.index);
if (data->connecting)
driver_select(data->port, (ErlDrvEvent)data->socket, DO_WRITE, 0);
}
ei_x_free(&x);
}
當我們從 postgres 取得的 socket 準備好輸入或輸出時,就會呼叫 ready_io
函式。在這裡,我們先檢查是否正在連線到資料庫。在這種情況下,我們會檢查連線狀態,如果連線成功則返回 OK,如果連線失敗則返回錯誤。如果連線尚未建立,我們就直接返回;會再次呼叫 ready_io
。
如果我們有來自連線的結果,這表示 x
緩衝區中有資料,我們就不再需要在輸出上進行 select(ready_output
),因此我們透過呼叫 driver_select
來移除它。
如果我們沒有進行連線,我們會等待 PQsendQuery
的結果,因此我們取得結果並返回它。編碼是使用與先前範例中相同的函式完成的。
這裡應該新增錯誤處理,例如,檢查 socket 是否仍然開啟,但這只是一個簡單的範例。
非同步驅動程式的 Erlang 部分包含在範例檔案 pg_async.erl
中。
-module(pg_async).
-define(DRV_CONNECT, $C).
-define(DRV_DISCONNECT, $D).
-define(DRV_SELECT, $S).
-export([connect/1, disconnect/1, select/2]).
connect(ConnectStr) ->
case erl_ddll:load_driver(".", "pg_async") of
ok -> ok;
{error, already_loaded} -> ok;
_ -> exit({error, could_not_load_driver})
end,
Port = open_port({spawn, ?MODULE}, [binary]),
port_control(Port, ?DRV_CONNECT, ConnectStr),
case return_port_data(Port) of
ok ->
{ok, Port};
Error ->
Error
end.
disconnect(Port) ->
port_control(Port, ?DRV_DISCONNECT, ""),
R = return_port_data(Port),
port_close(Port),
R.
select(Port, Query) ->
port_control(Port, ?DRV_SELECT, Query),
return_port_data(Port).
return_port_data(Port) ->
receive
{Port, {data, Data}} ->
binary_to_term(Data)
end.
Erlang 程式碼略有不同,因為我們不會從 port_control/3
同步返回結果,而是從訊息佇列中的 driver_output
取得資料。上面的函式 return_port_data
會從 port 接收資料。由於資料是二進制格式,我們使用 binary_to_term/1
將其轉換為 Erlang term。請注意,驅動程式是在二進制模式下開啟的(open_port/2
是使用選項 [binary]
呼叫的)。這表示從驅動程式傳送到模擬器的資料會以二進制形式傳送。如果沒有選項 binary
,它們將會是整數列表。
使用 driver_async 的非同步驅動程式
作為最後一個範例,我們示範如何使用 driver_async
。我們還使用驅動程式 term 介面。驅動程式是以 C++ 編寫的。這使我們可以使用 STL 中的演算法。我們使用 next_permutation
演算法來取得整數列表的下一個排列。對於大型列表(> 100,000 個元素),這需要一些時間,因此我們將其作為非同步任務執行。
驅動程式的非同步 API 很複雜。首先,必須準備工作。在範例中,這是透過 output
完成的。我們可以使用 control
,但我們希望在範例中有些變化。在我們的驅動程式中,我們會配置一個結構,其中包含非同步任務執行工作所需的任何內容。這是在主模擬器執行緒中完成的。然後,從與主模擬器執行緒分離的驅動程式執行緒呼叫非同步函式。請注意,驅動程式函式不是可重入的,因此不能使用。最後,在函式完成後,會從主模擬器執行緒呼叫驅動程式回呼 ready_async
,這就是我們將結果返回 Erlang 的地方。(我們無法從非同步函式內返回結果,因為我們無法呼叫驅動程式函式。)
以下程式碼來自範例檔案 next_perm.cc
。驅動程式入口點看起來與之前相同,但也包含回呼 ready_async
。
static ErlDrvEntry next_perm_driver_entry = {
NULL, /* init */
start,
NULL, /* stop */
output,
NULL, /* ready_input */
NULL, /* ready_output */
"next_perm", /* the name of the driver */
NULL, /* finish */
NULL, /* handle */
NULL, /* control */
NULL, /* timeout */
NULL, /* outputv */
ready_async,
NULL, /* flush */
NULL, /* call */
NULL /* event */
};
output
函式會配置非同步函式的工作區域。由於我們使用 C++,因此我們使用 struct,並將資料塞入其中。我們必須複製原始資料,在我們從 output
函式返回之後,該資料將不再有效,而 do_perm
函式稍後會從另一個執行緒呼叫。我們在這裡不返回任何資料,而是稍後從 ready_async
回呼傳送資料。
async_data
會傳遞給 do_perm
函式。我們不使用 async_free
函式(driver_async
的最後一個引數),它僅在程式以程式設計方式取消任務時使用。
struct our_async_data {
bool prev;
vector<int> data;
our_async_data(ErlDrvPort p, int command, const char* buf, int len);
};
our_async_data::our_async_data(ErlDrvPort p, int command,
const char* buf, int len)
: prev(command == 2),
data((int*)buf, (int*)buf + len / sizeof(int))
{
}
static void do_perm(void* async_data);
static void output(ErlDrvData drv_data, char *buf, int len)
{
if (*buf < 1 || *buf > 2) return;
ErlDrvPort port = reinterpret_cast<ErlDrvPort>(drv_data);
void* async_data = new our_async_data(port, *buf, buf+1, len);
driver_async(port, NULL, do_perm, async_data, do_free);
}
在 do_perm
中,我們執行工作,並對在 output
中配置的結構進行操作。
static void do_perm(void* async_data)
{
our_async_data* d = reinterpret_cast<our_async_data*>(async_data);
if (d->prev)
prev_permutation(d->data.begin(), d->data.end());
else
next_permutation(d->data.begin(), d->data.end());
}
在 ready_async
函式中,會將輸出送回模擬器。我們使用驅動程式 term 格式,而不是 ei
。這是將 Erlang term 直接傳送到驅動程式的唯一方法,而無需 Erlang 程式碼呼叫 binary_to_term/1
。在簡單的範例中,這效果很好,我們不需要使用 ei
來處理二進制 term 格式。
當資料返回時,我們會解除配置我們的資料。
static void ready_async(ErlDrvData drv_data, ErlDrvThreadData async_data)
{
ErlDrvPort port = reinterpret_cast<ErlDrvPort>(drv_data);
our_async_data* d = reinterpret_cast<our_async_data*>(async_data);
int n = d->data.size(), result_n = n*2 + 3;
ErlDrvTermData *result = new ErlDrvTermData[result_n], *rp = result;
for (vector<int>::iterator i = d->data.begin();
i != d->data.end(); ++i) {
*rp++ = ERL_DRV_INT;
*rp++ = *i;
}
*rp++ = ERL_DRV_NIL;
*rp++ = ERL_DRV_LIST;
*rp++ = n+1;
driver_output_term(port, result, result_n);
delete[] result;
delete d;
}
此驅動程式的呼叫方式與 Erlang 中的其他驅動程式相同。但是,由於我們使用 driver_output_term
,因此無需呼叫 binary_to_term/1
。Erlang 程式碼位於範例檔案 next_perm.erl
中。
輸入會變更為整數列表並傳送到驅動程式。
-module(next_perm).
-export([next_perm/1, prev_perm/1, load/0, all_perm/1]).
load() ->
case whereis(next_perm) of
undefined ->
case erl_ddll:load_driver(".", "next_perm") of
ok -> ok;
{error, already_loaded} -> ok;
E -> exit(E)
end,
Port = open_port({spawn, "next_perm"}, []),
register(next_perm, Port);
_ ->
ok
end.
list_to_integer_binaries(L) ->
[<<I:32/integer-native>> || I <- L].
next_perm(L) ->
next_perm(L, 1).
prev_perm(L) ->
next_perm(L, 2).
next_perm(L, Nxt) ->
load(),
B = list_to_integer_binaries(L),
port_control(next_perm, Nxt, B),
receive
Result ->
Result
end.
all_perm(L) ->
New = prev_perm(L),
all_perm(New, L, [New]).
all_perm(L, L, Acc) ->
Acc;
all_perm(L, Orig, Acc) ->
New = prev_perm(L),
all_perm(New, Orig, [New | Acc]).