# Erl_Interface This section outlines an example of how to solve the example problem in [Problem Example](example.md) by using a port and Erl_Interface. It is necessary to read the port example in [Ports](c_port.md) before reading this section. ## Erlang Program The following example shows an Erlang program communicating with a C program over a plain port with home made encoding: ```erlang -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](c_port.md), 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`](`term_to_binary/1`) and [`binary_to_term/1`](`binary_to_term/1`) BIFs are to be used. That is: ```erlang open_port({spawn, ExtPrg}, [{packet, 2}]) ``` is replaced with: ```erlang open_port({spawn, ExtPrg}, [{packet, 2}, binary]) ``` And: ```erlang Port ! {self(), {command, encode(Msg)}}, receive {Port, {data, Data}} -> Caller ! {complex, decode(Data)} end ``` is replaced with: ```erlang 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: ```erlang -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. ## C Program The following example shows a C program communicating with an Erlang program over a plain port with the Erlang external term format encoding: ```c /* ei.c */ #include "ei.h" #include #include #include typedef unsigned char byte; int read_cmd(byte *buf); int write_cmd(byte *buf, int len); int foo(int x); int bar(int y); static void fail(int place) { fprintf(stderr, "Something went wrong %d\n", place); exit(1); } int main() { byte buf[100]; int index = 0; int version = 0; int arity = 0; char atom[128]; long in = 0; int res = 0; ei_x_buff res_buf; ei_init(); while (read_cmd(buf) > 0) { if (ei_decode_version(buf, &index, &version) != 0) fail(1); if (ei_decode_tuple_header(buf, &index, &arity) != 0) fail(2); if (arity != 2) fail(3); if (ei_decode_atom(buf, &index, atom) != 0) fail(4); if (ei_decode_long(buf, &index, &in) != 0) fail(5); if (strncmp(atom, "foo", 3) == 0) { res = foo((int)in); } else if (strncmp(atom, "bar", 3) == 0) { res = bar((int)in); } if (ei_x_new_with_version(&res_buf) != 0) fail(6); if (ei_x_encode_long(&res_buf, res) != 0) fail(7); write_cmd(res_buf.buff, res_buf.index); if (ei_x_free(&res_buf) != 0) fail(8); index = 0; } } ``` The following functions, `read_cmd()` and `write_cmd()`, from the `erl_comm.c` example in [Ports](c_port.md) can still be used for reading from and writing to the port: ```c /* erl_comm.c */ #include #include typedef unsigned char byte; int 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> 8) & 0xff; write_exact(&li, 1); li = len & 0xff; write_exact(&li, 1); return write_exact(buf, len); } ``` ## Running the Example _Step 1._ Compile the C code. This provides the paths to the include file `ei.h`, and also to the library `ei`: ```text $ 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 -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: ```erlang $ erl Erlang/OTP 26 [erts-14.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns] Eshell V14.2 (press Ctrl+G to abort, type help(). for help) 1> c(complex2). {ok,complex2} ``` _Step 3._ Run the example: ```erlang 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 ```