Erlang linkin driver用port_control方式时的一些经验分享
最近由于需要Erlang与C交互,采用了linkin driver的方式。利用port_control以及driver_entry中的control回调,调用C函数。在传递复杂的数据结构,序列化和反序列化数据时遇到了一些问题,与大家分享一下。先简单介绍一下eralng driver。
首先,Erlang与外部程序交互的方式主要有两种:
另外,还有一种NIF方式,暂时没有考虑。这里我采用了linkin driver的方式。
Erlang调用driver也有两种方式:
这里选用port_control的方式。
为了方便encode和decode比较复杂的数据结构,采用ei这个库来序列化和反序列化参数。
首先,open一个port, 采用binary方式:
case erl_ddll:load_driver(".", SharedLib) of
ok -> ok;
{error, alread_loaded} -> ok;
Ret ->
error_logger:error_msg("Could not load driver ~p~n", [Ret]),
exit({error, could_not_load_driver})
end,
Port = open_port({spawn, SharedLib}, [binary]).
打开port之后,就能直接用port_control来调用driver里面的函数了
Resp=erlang:port_control(Port, Cmd, term_to_binary(Msg)),
io:format("recv ctl data ~p~n",[binary_to_term(Resp)]).
Port即先前打开的Port, Cmd是一个int,你可以对你的函数编号,1代表调用sum,2代表调用twice。。。最后Msg即参数,注意需要传入binary(或者字符串),返回结果Resp也是binary(或者字符串)。
对应C程序这里,需要定义一个回调函数port_ctl, 并把它添加到driver_entry里面注册,如下:
ErlDrvEntry example_driver_entry = {
NULL,
example_drv_start, //driver启动时回调函数
example_drv_stop, //driver停止时回调函数
NULL,
NULL,
NULL,
"example1_drv", //driver名称
NULL,
NULL,
port_ctl, //在这里注册
NULL,
NULL,
NULL,
NULL,
NULL,
NULL
};
DRIVER_INIT(example1_drv)
{
return &example_driver_entry;
}
static ErlDrvData example_drv_start(ErlDrvPort port, char* buff)
{
example_data * d = (example_data*) driver_alloc(sizeof(example_data));
d->port = port;
set_port_control_flags(port, PORT_CONTROL_FLAG_BINARY);
return (ErlDrvData)d;
}
static void example_drv_stop(ErlDrvData handle)
{
driver_free((char*) handle);
}
Erlang调用port_control 会使得driver调用刚刚注册的port_ctl回调函数。
下面是最重要的port_ctl函数. cmd即port_control的Cmd, buf和len为接受到的函数参数数据的buffer和长度, rbuf是返回值buffer。 这里假设port_control传入Msg的参数是将一个tuple {a,b}序列化后的结果:
static int port_ctl(ErlDrvData handle, unsigned int cmd, char* buf, int len,
char** rbuf, int rsize)
{
ei_x_buff x;
if(1 == cmd) { //1号命令,调用 sum(a,b)
char *p;
int i;
int arity = 0;
int version = 0;
long a, b;
int index = 0;
int res = 0;
ei_decode_version(buf, &index, &version); //必须有
ei_decode_tuple_header(buf, &index, &arity); //decode出tuple头,
//得到tuple的长度arity,这里为2
ei_decode_long(buf, &index, &a); //tuple第一个元素
ei_decode_long(buf, &index, &b); //tuple第二个元素
res = sum(a, b); //这里调用目标函数,这里是一个简单的求和
//encode返回数据阶段
ei_x_new_with_version(&x);
ei_x_encode_long(&x, res);
if(x.index > rsize){
ErlDrvBinary *ptr = driver_alloc_binary(x.index);
if(NULL == ptr)
erl_exit(1,"Out of virtual memory in malloc (%s)", __FILE__);
memcpy(ptr->orig_bytes, x.buff, x.index);
*rbuf = (char *)ptr;
} else
memcpy(*rbuf, x.buff, x.index);
ei_x_free(&x);
return x.index;
} else
if(2 == cmd) {...... }
return -1; //返回<0会导致elang:port_control抛出badarg异常
}
注意,ei_decode_version(buf, &index, &version);必须有,我在开始没有这一行时,总是无法decode成功。
后来经过阅读erlang源代码发现,传入的数据是这样序列化的:
序列化的数据都会有生成个magic number作为version, 放在第一个字节。比如一个tuple {1,2}, 假设他的version为131, tuple类型用’i\'表示, 1,2属于ERL_SMALL_INTEGER_EXT,用‘a’表示该类型,则序列化后如下:
131 i 2 a 1 a 2
对应的131为version, i代表是一个tuple,2代表长度为2, 后面是tuple的内容,第一个a代表是一个小整数,1是这个整数的值;接着第二个a代表第二个元素为小整数,2代表这个整数值为2.
所以,必须先把version 131 decode出来,然后调用ei_decode_tuple接着解析131之后的数据才能正常。 这里,ei_decode_xxx函数中 index这个参数随着decode的调用,会指向下一个需要decode的位置。
另外,example_drv_start回调函数中set_port_control_flags(port, PORT_CONTROL_FLAG_BINARY);也必须有。
如果没有这一行, port_control返回值Reply不会是binary,而是一个list,[131, $i, 2, $a, 1, $a, 2], 这个list无法用binary_to_term还原,而且自己处理也比较麻烦。
相反,如果设置后,则会返回一个binary 《131,$i, 2, $a, 1, $a, 2》,这个格式可以用binary_to_term转化为term。相应的,如果设置PORT_CONTROL_FLAG_BINARY, 当返回缓冲区大小不够时,一定要用driver_alloc_binary来为*rbuf分配缓冲区。
如果大家觉得binary与term转化比较麻烦,可以考虑用erlang:port_call函数, c程序不变,只是erlang程序不需要term_to_binary以及binary_to_term.
欢迎大家指出本文的不足,以及与我交流。
建议继续学习:
- whatsapp深度使用Erlang有感 (阅读:5286)
- Erlang match_spec引擎介绍和应用 (阅读:5202)
- php-erlang (阅读:4886)
- gen_tcp调用进程收到{empty_out_q, Port}消息奇怪行为分析 (阅读:3944)
- hibernate使用注意事项 (阅读:3845)
- Erlang如何限制节点对集群的访问之net_kernel:allow (阅读:3593)
- ERLANG OTP源码分析 – gen_server (阅读:3261)
- erlang学习手记 (阅读:3240)
- gen_tcp容易误用的一点解释 (阅读:3092)
- Erlang虚拟机内存使用问题以及监控 (阅读:2964)
扫一扫订阅我的微信号:IT技术博客大学习
- 作者:minzhan 来源: 量子数科院
- 标签: Erlang port_control
- 发布时间:2011-06-02 13:35:19
-
[928] WordPress插件开发 -- 在插件使用 -
[134] 解决 nginx 反向代理网页首尾出现神秘字 -
[52] 整理了一份招PHP高级工程师的面试题 -
[52] 如何保证一个程序在单台服务器上只有唯一实例( -
[51] 用 Jquery 模拟 select -
[50] 海量小文件存储 -
[50] Innodb分表太多或者表分区太多,会导致内 -
[50] 全站换域名时利用nginx和javascri -
[49] CloudSMS:免费匿名的云短信 -
[47] jQuery性能优化指南
