查看源码 Erl_Interface
本节概述了如何通过使用端口和 Erl_Interface 解决问题示例中的示例问题。在阅读本节之前,有必要阅读端口中的端口示例。
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 时有两个不同之处
- 由于 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 程序必须能够处理这两个元组。
C 程序
以下示例展示了一个 C 程序如何通过使用 Erlang 外部项格式编码的普通端口与 Erlang 程序通信
/* 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;
}
}
来自端口中erl_comm.c
示例的以下函数 read_cmd()
和 write_cmd()
仍然可以用于从端口读取和写入。
/* 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);
}
运行示例
步骤 1. 编译 C 代码。这将提供头文件 ei.h
的路径,以及库 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
在 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 代码
$ 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}
步骤 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