l'essentiel est invisible pour les yeux

Tuesday, July 01, 2008

Manipulate Erlang binary term format in C/C++ with ei library

Erlang includes ei library for handling Erlang binary term format. This entry introduces how to use this library.

1. Pass the one string to C driver from Erlang.
It's pretty simple case, and pass one string argument to C driver from Erlang. It should be passed port_control/3 or erlang:port_control/3 after all arguments should be encoded as binary. The "index" variable are used for pointing position in the buffer.

% echo.erl


-define(DRV_ECHO, 1).

echo(Str) ->
control(?DRV_ECHO, Str).

control(Cmd, Data) ->
[{port, Port}| _] = ets:lookup(echo_table, port),
Bin = term_to_binary(Data), % XXX: All arguments will be encoded as binary term format.
Res = port_control(Port, Cmd, Bin),
binary_to_term(Res).

% echo_drv.c

#define DRV_ECHO 1

static int control(ErlDrvData drv_data, unsigned int command, char *buf,
int len, char **buf, int rlen) {
int index, type, size, arity ,ver;
char *arg1;

switch(command) {
case DRV_ECHO:
// It must call ei_decode_version at the beginning when argument is Erlang binary term format.

// All ei_xxx functions return 0 if it will be success, else return -1
ei_decode_version(buf, &index, &ver); // Value of 'index' will become 1.

// Get size of string in order to allocate buffer for argument
ei_get_type(buf, &index, &type, &size); // value of 'type' equal ERL_STRING_EXT

// Allocate memory for buffer
arg1 = new char[size + 1]; // Extra bite is required for NULL string.
/* do something */
}
}


Type of Erlang term
It can obtain type of Erlang term with result of ei_get_type function. All value of types are defined in ei.h.

#define ERL_SMALL_INTEGER_EXT 'a'
#define ERL_INTEGER_EXT 'b'
#define ERL_FLOAT_EXT 'c'
#define ERL_ATOM_EXT 'd'
#define ERL_REFERENCE_EXT 'e'
#define ERL_NEW_REFERENCE_EXT 'r'
#define ERL_PORT_EXT 'f'
#define ERL_PID_EXT 'g'
#define ERL_SMALL_TUPLE_EXT 'h'
#define ERL_LARGE_TUPLE_EXT 'i'
#define ERL_NIL_EXT 'j'
#define ERL_STRING_EXT 'k'
#define ERL_LIST_EXT 'l'
#define ERL_BINARY_EXT 'm'
#define ERL_SMALL_BIG_EXT 'n'
#define ERL_LARGE_BIG_EXT 'o'
#define ERL_NEW_FUN_EXT 'p'
#define ERL_FUN_EXT 'u'

#define ERL_NEW_CACHE 'N' /* c nodes don't know these two */
#define ERL_CACHED_ATOM 'C'


2. Pass the List
It should call ei_decode_list_header function when parse the list term. It must be called when parse the list term.

% echo_list.erl

-define(DRV_ECHO_LIST, 1).

echo(List) when is_list(List) ->
control(?DRV_ECHO_LIST, List).

control(Cmd, Data) ->
[{port, Port}| _] = ets:lookup(echo_list_table, port),
Bin = term_to_binary(Data), % XXX: All arguments will be encoded as binary term format.
Res = port_control(Port, Cmd, Str),
binary_to_term(Res).


% echo_drv.c

#define DRV_ECHO 1

static int control(ErlDrvData drv_data, unsigned int command, char *buf,
int len, char **buf, int rlen) {
int index, type, size, arity ,ver;
char *arg1;

switch(command) {
case DRV_ECHO_LIST:
// It must call ei_decode_version at the beginning when argument is Erlang binary term format.

// All ei_xxx functions return 0 if it will be success, else return -1
ei_decode_version(buf, &index, &ver); // Value of 'index' will become 1.

// Get size of string in order to allocate buffer for argument
ei_get_type(buf, &index, &type, &size);

// Check the type
if(type == ERL_LIST_EXT) {
// It must be called at the begginning when hanle the list term.
ei_decode_list_header(buf, &index, &arity);

printf("number of elements: %d\n", arity);

// Check the type of first element in the list
ei_get_type(buf, &index, &type, &size);

swithc(type) {
case ERL_SMALL_INTEGER_EXT:
case ERL_INTEGER_EXT:
// do something
break;
case ERL_STRING_EXT:
// do something
break;
}
}
}
}