5 Erl_Interface
This section outlines an example of how to solve the example problem in Problem Example
by using a port and Erl_Interface. It is necessary to read the port example in Ports
before reading this section.
5.1 Erlang Program
The following example shows an Erlang program communicating with a C program over a plain port with home made encoding:
-module(complex1). -export([start/1, stop/0, init/1]). -export([foo/1, bar/1]). start(ExtPrg) -> spawn(?MODULE, init, [ExtPrg]). stop() -> complex ! stop. foo(X) -> call_port({foo, X}). bar(Y) -> call_port({bar, Y}). call_port(Msg) -> complex ! {call, self(), Msg}, receive {complex, Result} -> Result end. init(ExtPrg) -> register(complex, self()), process_flag(trap_exit, true), Port = open_port({spawn, ExtPrg}, [{packet, 2}]), loop(Port). loop(Port) -> receive {call, Caller, Msg} -> Port ! {self(), {command, encode(Msg)}}, receive {Port, {data, Data}} -> Caller ! {complex, decode(Data)} end, loop(Port); stop -> Port ! {self(), close}, receive {Port, closed} -> exit(normal) end; {'EXIT', Port, Reason} -> exit(port_terminated) end. encode({foo, X}) -> [1, X]; encode({bar, Y}) -> [2, Y]. decode([Int]) -> Int.
There are two differences when using Erl_Interface on the C side compared to the example in Ports
, using only the plain port:
- As Erl_Interface operates on the Erlang external term format, the port must be set to use binaries.
- Instead of inventing an encoding/decoding scheme, the
term_to_binary/1
andbinary_to_term/1
BIFs are to be used.
That is:
open_port({spawn, ExtPrg}, [{packet, 2}])
is replaced with:
open_port({spawn, ExtPrg}, [{packet, 2}, binary])
And:
Port ! {self(), {command, encode(Msg)}}, receive {Port, {data, Data}} -> Caller ! {complex, decode(Data)} end
is replaced with:
Port ! {self(), {command, term_to_binary(Msg)}}, receive {Port, {data, Data}} -> Caller ! {complex, binary_to_term(Data)} end
The resulting Erlang program is as follows:
-module(complex2). -export([start/1, stop/0, init/1]). -export([foo/1, bar/1]). start(ExtPrg) -> spawn(?MODULE, init, [ExtPrg]). stop() -> complex ! stop. foo(X) -> call_port({foo, X}). bar(Y) -> call_port({bar, Y}). call_port(Msg) -> complex ! {call, self(), Msg}, receive {complex, Result} -> Result end. init(ExtPrg) -> register(complex, self()), process_flag(trap_exit, true), Port = open_port({spawn, ExtPrg}, [{packet, 2}, binary]), loop(Port). loop(Port) -> receive {call, Caller, Msg} -> Port ! {self(), {command, term_to_binary(Msg)}}, receive {Port, {data, Data}} -> Caller ! {complex, binary_to_term(Data)} end, loop(Port); stop -> Port ! {self(), close}, receive {Port, closed} -> exit(normal) end; {'EXIT', Port, Reason} -> exit(port_terminated) end.
Notice that calling complex2:foo/1
and complex2:bar/1
results in the tuple {foo,X}
or {bar,Y}
being sent to the complex
process, which codes them as binaries and sends them to the port. This means that the C program must be able to handle these two tuples.
5.2 C Program
The following example shows a C program communicating with an Erlang program over a plain port with home made encoding:
/* port.c */ typedef unsigned char byte; int main() { int fn, arg, res; byte buf[100]; while (read_cmd(buf) > 0) { fn = buf[0]; arg = buf[1]; if (fn == 1) { res = foo(arg); } else if (fn == 2) { res = bar(arg); } buf[0] = res; write_cmd(buf, 1); } }
Compared to the C program in Ports
, using only the plain port, the while
-loop must be rewritten. Messages coming from the port is on the Erlang external term format. They must be converted into an ETERM
struct, which is a C struct similar to an Erlang term. The result of calling foo()
or bar()
must be converted to the Erlang external term format before being sent back to the port. But before calling any other Erl_Interface function, the memory handling must be initiated:
erl_init(NULL, 0);
The following functions, read_cmd()
and write_cmd()
, from the erl_comm.c
example in Ports
can still be used for reading from and writing to the port:
/* erl_comm.c */ typedef unsigned char byte; read_cmd(byte *buf) { int len; if (read_exact(buf, 2) != 2) return(-1); len = (buf[0] << 8) | buf[1]; return read_exact(buf, len); } write_cmd(byte *buf, int len) { byte li; li = (len >> 8) & 0xff; write_exact(&li, 1); li = len & 0xff; write_exact(&li, 1); return write_exact(buf, len); } read_exact(byte *buf, int len) { int i, got=0; do { if ((i = read(0, buf+got, len-got)) <= 0) return(i); got += i; } while (got<len); return(len); } write_exact(byte *buf, int len) { int i, wrote = 0; do { if ((i = write(1, buf+wrote, len-wrote)) <= 0) return (i); wrote += i; } while (wrote<len); return (len); }
The function erl_decode()
from erl_marshal
converts the binary into an ETERM
struct:
int main() { ETERM *tuplep; while (read_cmd(buf) > 0) { tuplep = erl_decode(buf);
Here, tuplep
points to an ETERM
struct representing a tuple with two elements; the function name (atom) and the argument (integer). Using the function erl_element()
from erl_eterm
, these elements can be extracted, but they must also be declared as pointers to an ETERM
struct:
fnp = erl_element(1, tuplep); argp = erl_element(2, tuplep);
The macros ERL_ATOM_PTR
and ERL_INT_VALUE
from erl_eterm
can be used to obtain the actual values of the atom and the integer. The atom value is represented as a string. By comparing this value with the strings "foo" and "bar", it can be decided which function to call:
if (strncmp(ERL_ATOM_PTR(fnp), "foo", 3) == 0) { res = foo(ERL_INT_VALUE(argp)); } else if (strncmp(ERL_ATOM_PTR(fnp), "bar", 3) == 0) { res = bar(ERL_INT_VALUE(argp)); }
Now an ETERM
struct that represents the integer result can be constructed using the function erl_mk_int()
from erl_eterm
. The function erl_format()
from the module erl_format
can also be used:
intp = erl_mk_int(res);
The resulting ETERM
struct is converted into the Erlang external term format using the function erl_encode()
from erl_marshal
and sent to Erlang using write_cmd()
:
erl_encode(intp, buf); write_cmd(buf, erl_eterm_len(intp));
Finally, the memory allocated by the ETERM
creating functions must be freed:
erl_free_compound(tuplep); erl_free_term(fnp); erl_free_term(argp); erl_free_term(intp);
The resulting C program is as follows:
/* ei.c */ #include "erl_interface.h" #include "ei.h" typedef unsigned char byte; int main() { ETERM *tuplep, *intp; ETERM *fnp, *argp; int res; byte buf[100]; long allocated, freed; erl_init(NULL, 0); while (read_cmd(buf) > 0) { tuplep = erl_decode(buf); fnp = erl_element(1, tuplep); argp = erl_element(2, tuplep); if (strncmp(ERL_ATOM_PTR(fnp), "foo", 3) == 0) { res = foo(ERL_INT_VALUE(argp)); } else if (strncmp(ERL_ATOM_PTR(fnp), "bar", 3) == 0) { res = bar(ERL_INT_VALUE(argp)); } intp = erl_mk_int(res); erl_encode(intp, buf); write_cmd(buf, erl_term_len(intp)); erl_free_compound(tuplep); erl_free_term(fnp); erl_free_term(argp); erl_free_term(intp); } }
5.3 Running the Example
Step 1. Compile the C code. This provides the paths to the include files erl_interface.h
and ei.h
, and also to the libraries erl_interface
and ei
:
unix> gcc -o extprg -I/usr/local/otp/lib/erl_interface-3.9.2/include \\ -L/usr/local/otp/lib/erl_interface-3.9.2/lib \\ complex.c erl_comm.c ei.c -lerl_interface -lei -lpthread
In Erlang/OTP R5B and later versions of OTP, the include
and lib
directories are situated under OTPROOT/lib/erl_interface-VSN
, where OTPROOT
is the root directory of the OTP installation (/usr/local/otp
in the recent example) and VSN
is the version of the Erl_interface application (3.2.1 in the recent example).
In R4B and earlier versions of OTP, include
and lib
are situated under OTPROOT/usr
.
Step 2. Start Erlang and compile the Erlang code:
unix> erl Erlang (BEAM) emulator version 4.9.1.2 Eshell V4.9.1.2 (abort with ^G) 1> c(complex2). {ok,complex2}
Step 3. Run the example:
2> complex2:start("./extprg"). <0.34.0> 3> complex2:foo(3). 4 4> complex2:bar(5). 10 5> complex2:bar(352). 704 6> complex2:stop(). stop
© 2010–2020 Ericsson AB
Licensed under the Apache License, Version 2.0.