檢視原始碼 Erl_Interface
本節概述如何使用埠 (port) 和 Erl_Interface 來解決範例問題中的範例問題。在閱讀本節之前,必須先閱讀埠 (Ports)中的埠範例。
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.
與埠 (Ports)中的範例相比,在 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;
}
}
來自埠 (Ports) 中的 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