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有感 (阅读:4571)
- Erlang match_spec引擎介绍和应用 (阅读:4528)
- php-erlang (阅读:4309)
- gen_tcp调用进程收到{empty_out_q, Port}消息奇怪行为分析 (阅读:3542)
- hibernate使用注意事项 (阅读:3218)
- Erlang如何限制节点对集群的访问之net_kernel:allow (阅读:2971)
- ERLANG OTP源码分析 – gen_server (阅读:2858)
- erlang学习手记 (阅读:2697)
- gen_tcp容易误用的一点解释 (阅读:2636)
- Erlang虚拟机内存使用问题以及监控 (阅读:2419)
扫一扫订阅我的微信号:IT技术博客大学习
- 作者:minzhan 来源: 量子数科院
- 标签: Erlang port_control
- 发布时间:2011-06-02 13:35:19
- [55] IOS安全–浅谈关于IOS加固的几种方法
- [54] 如何拿下简短的域名
- [54] 图书馆的世界纪录
- [54] android 开发入门
- [52] Oracle MTS模式下 进程地址与会话信
- [52] Go Reflect 性能
- [49] 【社会化设计】自我(self)部分――欢迎区
- [48] 读书笔记-壹百度:百度十年千倍的29条法则
- [41] 程序员技术练级攻略
- [35] 视觉调整-设计师 vs. 逻辑