View Source 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.
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.
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:
/* ei.c */
#include "ei.h"
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
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 can still be used for reading from and writing to
the port:
/* erl_comm.c */
#include <stdio.h>
#include <unistd.h>
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<len);
return(len);
}
int 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);
}
int 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);
}
int 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);
}
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
:
$ 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:
$ 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:
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