非常教程

Erlang 20参考手册

指南:教程 | Guide: Tutorial

5. Erl_界面 | 5. Erl_Interface

本节概述了如何Problem Example通过使用端口和Erl_Interface 来解决示例问题的示例。Ports在阅读本节之前,需要阅读端口示例。

5.1 Erlang程序

下面的例子显示了一个Erlang程序通过一个带有自制编码的普通端口与C程序进行通信:

-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.

在C端使用Erl_Interface与Ports使用只有普通端口的示例相比,有两点区别:

  • 由于Erl_Interface使用Erlang外部术语格式,因此必须将端口设置为使用二进制文件。
  • 而不是发明一种编码/解码方案,将使用term_to_binary/1binary_to_term/1BIF。

即:

open_port({spawn, ExtPrg}, [{packet, 2}])

改为:

open_port({spawn, ExtPrg}, [{packet, 2}, binary])

以及:

Port ! {self(), {command, encode(Msg)}},
receive
  {Port, {data, Data}} ->
    Caller ! {complex, decode(Data)}
end

改为:

Port ! {self(), {command, term_to_binary(Msg)}},
receive
  {Port, {data, Data}} ->
    Caller ! {complex, binary_to_term(Data)}
end

由此产生的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.

请注意,调用complex2:foo/1complex2:bar/1生成元组{foo,X}或将{bar,Y}其发送给complex进程,该进程将它们编码为二进制文件并将其发送到端口。这意味着C程序必须能够处理这两个元组。

5.2 C程序

下面的例子显示了一个C程序通过一个带有自制编码的普通端口与Erlang程序进行通信:

/* 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);
  }
}
      

Ports仅使用普通端口的C程序相比,while必须重写-loop。来自端口的消息采用Erlang外部术语格式。它们必须转换成一个ETERM结构,这是一个类似于Erlang术语的C结构。调用的结果foo()bar()必须在被发送回端口之前转换为Erlang外部术语格式。但在调用任何其他Erl_Interface函数之前,必须启动内存处理:

erl_init(NULL, 0);

下面的函数,read_cmd()以及write_cmd()erl_comm.c示例中Ports仍然可以用于读取和写入端口:

/* 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);
}

函数erl_decode()erl_marshal转换成二进制文件的ETERM结构:

int main() {
  ETERM *tuplep;

  while (read_cmd(buf) > 0) {
    tuplep = erl_decode(buf);

这里tuplep指向一个ETERM表示具有两个元素的元组的结构; 函数名称(原子)和参数(整数)。使用函数erl_element()from erl_eterm,可以提取这些元素,但也必须声明为指向ETERM结构的指针:

fnp = erl_element(1, tuplep);
argp = erl_element(2, tuplep);

ERL_ATOM_PTRERL_INT_VALUEfrom erl_eterm可以用来获得原子和整数的实际值。原子值被表示为一个字符串。通过将此值与字符串“foo”和“bar”进行比较,可以决定调用哪个函数:

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));
}

现在ETERM可以使用函数erl_mk_int()from 来构造表示整数结果的结构erl_etermerl_format()模块中的功能erl_format也可以使用:

intp = erl_mk_int(res);

将所得的ETERM结构被转换成使用该函数的外部的Erlang术语格式erl_encode()erl_marshal和发送到使用Erlangwrite_cmd()

erl_encode(intp, buf);
write_cmd(buf, erl_eterm_len(intp));

最后,由ETERM必须释放创建函数:

erl_free_compound(tuplep);
erl_free_term(fnp);
erl_free_term(argp);
erl_free_term(intp);

生成的C程序如下:

/* 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运行示例

第1步。编译C代码。这提供了路径包含文件erl_interface.hei.h,并且还向图书馆erl_interfaceei

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

Erlang/OTP R5B和OTP的更高版本中,includelib目录位于下OTPROOT/lib/erl_interface-VSN,其中OTPROOT是OTP安装的根目录(/usr/local/otp在最近的例子),并VSN是Erl_interface应用程序的版本(3.2.1在最近的例子) 。

在R4B和更早版本的OTP中,includelib位于下OTPROOT/usr

第2步:启动Erlang并编译Erlang代码:

unix> erl
Erlang (BEAM) emulator version 4.9.1.2

Eshell V4.9.1.2 (abort with ^G)
1> c(complex2).
{ok,complex2}

第3步。运行示例:

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
Erlang 20

Erlang 是一种通用的面向并发的编程语言,可应付大规模开发活动的程序设计语言和运行环境。

主页 https://www.erlang.org/
源码 https://github.com/erlang/otp
版本 20
发布版本 20.1