2010年1月10日日曜日

erlangの非同期ドライバーでのタームの扱い

非同期ドライバーは非同期スレッドで使いたくてやっているわけだが、
erl_interfaceは使えない。

すべて、eiライブラリだけでやりとりする必要がある。よって、ETERMも使えない。
この結論にいたるまでずいぶんと無駄をした。

それで、前回のコードをeiを使用して書いてみた。

まずは、erlang側のコード

注意点や前回との違いは

erl_ddll:load_driver(".", SharedLib) はspawn してから行う
open_portは binary でオープンする(use_stdioがあるとデバックに便利)
データを渡すときは、term_to_binary、受けとるときは、binary_to_termを使う
サンプルは、整数を扱っているのでリストではなくタプルで渡す。
(サンプルの場合、リストで渡すと、C側で-1と255の区別ができない)


start(SharedLib) ->
spawn(?MODULE, init, [SharedLib]).

init(SharedLib) ->
register(complex, self()),
erl_ddll:load_driver(".", SharedLib),
Port = open_port({spawn_driver, SharedLib}, [binary, use_stdio]),
loop(Port).

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.

loop(Port) ->
receive
{call, Caller, Msg} ->
%Port ! {self(), {command, encode(Msg)}},
Port ! {self(), {command, term_to_binary(encode(Msg))}},
receive
{Port, {data, Data}} ->
Caller ! {complex, decode(Data)}
end,
loop(Port);
stop ->decode([Int]) -> IC側のコードnt;
decode(Res) when is_binary(Res)->
binary_to_term(Res).

Port ! {self(), close},
receive
{Port, closed} ->
exit(normal)
end;
{'EXIT', Port, Reason} ->C側のコード
io:format("~p ~n", [Reason]),
exit(port_terminated)
end.

encode({foo, X}) -> {1, X};
encode({bar, Y}) -> {2, Y}.

decode([Int]) -> Int;
decode(Res) when is_binary(Res)->
binary_to_term(Res).


examplebar.cも変更

C側のコード

long bar(int a) {
return a+1;
}

long foo(int b) {
return b*2;
}

ドライバーの方は抜粋。
いろいろ大変だったが、何とか動いた。
ei_decode_XXX を行う度にindexが書き換わる。


...

typedef struct {
char fn;
int arg;
long res;
} our_async_data;

...

static void example_drv_foobar(void * async_data)
{
our_async_data* d = (our_async_data*)async_data;

if (d->fn == 1) {
d->res = foo(d->arg);
} else if (d->fn == 2) {
d->res = bar(d->arg);
}else{
d->res = 0;
}
}

static void example_drv_error(void * async_data){
our_async_data* d = (our_async_data*)async_data;
d->res = 255;
}

static void example_drv_output(ErlDrvData handle, char* buff, int bufflen)
{
example_data* d = (example_data*)handle;
our_async_data* a = (our_async_data*)malloc(sizeof(our_async_data));
int i, ver, size, type, arity;
int val;
long long_val;
unsigned char str[3];
// indexの初期化を忘れないように
i = 0;
if(0 != ei_decode_version(buff, &i, &ver))printf("error\n\r");
// printf("version is %d.\n\r", ver);
ei_get_type(buff, &i, &type, &size);
printf("term type is %d,(%c).\n\r", type,type);
printf("size is %d.\n\r", size);

if( type == ERL_SMALL_TUPLE_EXT ){
ei_decode_tuple_header(buff, &i, &arity);
printf("list size is %d .\n\r", arity);
ei_get_type(buff, &i, &type, &size);
printf("i1 type is %d,(%c).\n\r", type,type);
if( type == ERL_SMALL_INTEGER_EXT){
ei_decode_char(buff, &i, str);
printf("function is %d\n\r",str[0]);
a->fn = str[0];
ei_get_type(buff, &i, &type, &size);
printf("i2 type is %d,(%c).\n\r", type,type);
if( type == ERL_SMALL_INTEGER_EXT){
ei_decode_char(buff, &i, str);
a->arg = (unsigned int)*str;
printf("arg is %d\n\r",a->arg);
driver_async(d->port, NULL, example_drv_foobar, a, free);
}else if( type == ERL_INTEGER_EXT){
ei_decode_long(buff, &i, &val);
a->arg = val;
printf("arg is %d\n\r",val);

driver_async(d->port, NULL, example_drv_foobar, a, free);
}else{
driver_async(d->port, NULL, example_drv_error, a, free);
}
}else{
driver_async(d->port, NULL, example_drv_error, a, free);
}
}

static void ready_async(ErlDrvData handle, ErlDrvThreadData async_data)
{
ei_x_buff x_buff;
ei_x_new_with_version(&x_buff);
example_data* d = (example_data*)handle;
our_async_data* a = (our_async_data*)async_data;

if( a->res <>res >= 0 ){
ei_x_encode_char(&x_buff, a->res);
}else{
ei_x_encode_long(&x_buff, a->res);
}

driver_output(d->port, x_buff.buff, x_buff.index);
/* not thread safe */
// driver_output_term(d->port, bin, sizeof(bin)/sizeof(bin[0]));
ei_x_free(&x_buff);

free(a);
}

...