Search documentation of Erlang System Documentation

# Erl_Interface

View Source

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` and `binary_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

← Previous Page Ports
Next Page → Port Drivers