5. Erl_界面 | 5. Erl_Interface
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/1
和binary_to_term/1
BIF。
即:
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/1
并complex2: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_PTR
和ERL_INT_VALUE
from 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_eterm
。erl_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.h
和ei.h
,并且还向图书馆erl_interface
和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
Erlang/OTP R5B和OTP的更高版本中,include
和lib
目录位于下OTPROOT/lib/erl_interface-VSN
,其中OTPROOT
是OTP安装的根目录(/usr/local/otp
在最近的例子),并VSN
是Erl_interface应用程序的版本(3.2.1在最近的例子) 。
在R4B和更早版本的OTP中,include
并lib
位于下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