Erlang下与其他程序和语言的通信机制(2)

  前面聊了普通端口,今天聊下链入式驱动端口,以及NIFs。

  • 链入式驱动端口

  

  如上图所示,链入式驱动端口与Erlang虚拟机存在于同一个OS进程中。

  在Erlang这边与普通端口类似,所有与链入式驱动端口的通信,也是通过一个Erlang的连接进程进行的,终止这个进程将同时终止端口。在端口创建之前,驱动需要先载入运行时(驱动以动态库方式提供),可以通过erl_dll:load_driver/1函数。然后就与普通端口一样,使用open_port/2函数打开。  

-module(complex5).
-export([start/1, stop/0, init/1]).
-export([foo/1, bar/1]).

start(SharedLib) ->
    case erl_ddll:load_driver(".", SharedLib) of  %%  载入驱动
      ok -> ok;
      {error, already_loaded} -> ok;
      _ -> exit({error, could_not_load_driver})
    end,
    spawn(?MODULE, init, [SharedLib]).

init(SharedLib) ->
    register(complex, self()),
    Port = open_port({spawn, SharedLib}, []),
    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)}},
        receive
        {Port, {data, Data}} ->
            Caller ! {complex, decode(Data)}
        end,
        loop(Port);
    stop ->
        Port ! {self(), close},
        receive
        {Port, closed} ->
            exit(normal)
        end;
    {'EXIT', Port, Reason} ->
        io:format("~p ~n", [Reason]),
        exit(port_terminated)
    end.

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

decode([Int]) -> Int.

 

  在C这边,首先包含erl_driver.h,需要使用里面的driver structure将C函数暴露给端口,这些函数用于接收和发送数据。端口发送过来的数据以函数参数的形式提供,而从C发送数据回端口则需要使用driver_output函数。端口由于可能被多个Erlang进程打开,所以最好不要使用全局变量,不然函数不可重入。应当使用driver_alloc,driver_free来分配每个端口独有的数据。最后我们需要把模块编译成动态库。

/* port_driver.c */

#include <stdio.h>
#include "erl_driver.h"

typedef struct {
    ErlDrvPort port;
} example_data;

static ErlDrvData example_drv_start(ErlDrvPort port, char *buff)
{
    example_data* d = (example_data*)driver_alloc(sizeof(example_data));
    d->port = port;  //start 回调,保存端口在独立的数据中,以后的回调可以访问
    return (ErlDrvData)d;
}

static void example_drv_stop(ErlDrvData handle)
{
    driver_free((char*)handle);
}

static void example_drv_output(ErlDrvData handle, char *buff, 
                   ErlDrvSizeT bufflen)
{
    example_data* d = (example_data*)handle;
    char fn = buff[0], arg = buff[1], res;
    if (fn == 1) {
      res = foo(arg);
    } else if (fn == 2) {
      res = bar(arg);
    }
    driver_output(d->port, &res, 1);//输出结果
}

ErlDrvEntry example_driver_entry = {  //注册端口回调
    NULL,            /* F_PTR init, called when driver is loaded */
    example_drv_start,        /* L_PTR start, called when port is opened */
    example_drv_stop,        /* F_PTR stop, called when port is closed */
    example_drv_output,        /* F_PTR output, called when erlang has sent */
    NULL,            /* F_PTR ready_input, called when input descriptor ready */
    NULL,            /* F_PTR ready_output, called when output descriptor ready */
    "example_drv",        /* char *driver_name, the argument to open_port */
    NULL,            /* F_PTR finish, called when unloaded */
    NULL,                       /* void *handle, Reserved by VM */
    NULL,            /* F_PTR control, port_command callback */
    NULL,            /* F_PTR timeout, reserved */
    NULL,            /* F_PTR outputv, reserved */
    NULL,                       /* F_PTR ready_async, only for async drivers */
    NULL,                       /* F_PTR flush, called when port is about 
                   to be closed, but there is data in driver 
                   queue */
    NULL,                       /* F_PTR call, much like control, sync call
                   to driver */
    NULL,                       /* F_PTR event, called when an event selected 
                   by driver_event() occurs. */
    ERL_DRV_EXTENDED_MARKER,    /* int extended marker, Should always be 
                   set to indicate driver versioning */
    ERL_DRV_EXTENDED_MAJOR_VERSION, /* int major_version, should always be 
                       set to this value */
    ERL_DRV_EXTENDED_MINOR_VERSION, /* int minor_version, should always be 
                       set to this value */
    0,                          /* int driver_flags, see documentation */
    NULL,                       /* void *handle2, reserved for VM use */
    NULL,                       /* F_PTR process_exit, called when a 
                   monitored process dies */
    NULL                        /* F_PTR stop_select, called to close an 
                   event object */
};

DRIVER_INIT(example_drv) /* must match name in driver_entry */
{
    return &example_driver_entry;  //初始化端口回调
}
  • NIFs

  NIFs是一种更简单,高效的通信方法,特别适合一些同步调用的函数,这些函数只是简单的计算并返回结果,并且没有副作用。它也需要编译成动态库,加载入运行时中。在Erlang这边,我们也需要一个Erlang模块来引用它,首先通过这个模块加载NIFs库(erlang:load_nif/2),然后在Erlang这边实现NIFs里同名的函数作为stub implementations,这些函数实现很简单,只是抛出异常。当NIFs中没有这个函数时,这些函数将作为替代被调用。

-module(complex6).
-export([foo/1, bar/1]).
-on_load(init/0).  %%在模块被加载时调用init/0函数,如果过init不是返回的ok,模块将会加载失败

init() ->
    ok = erlang:load_nif("./complex6_nif", 0).

foo(_X) ->
    exit(nif_library_not_loaded).
bar(_Y) ->
    exit(nif_library_not_loaded).

  在C这边,我们通过ERL_NIF_INIT宏将函数暴露出去。函数的参数在C这边通过一个ERL_NIF_TERM类型的数组表示。第Nth参数为数组里Nth-1元素。ErlNifEnv指针里包含了许多Erlang进程的信息,我们通过它调用enif_xx函数与Erlang通信。

#include "erl_nif.h"

extern int foo(int x);
extern int bar(int y);

static ERL_NIF_TERM foo_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
    int x, ret;
    if (!enif_get_int(env, argv[0], &x)) {
    return enif_make_badarg(env);
    }
    ret = foo(x);
    return enif_make_int(env, ret);
}

static ERL_NIF_TERM bar_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
    int y, ret;
    if (!enif_get_int(env, argv[0], &y)) {
    return enif_make_badarg(env);
    }
    ret = bar(y);
    return enif_make_int(env, ret);
}

static ErlNifFunc nif_funcs[] = {
    {"foo", 1, foo_nif},
    {"bar", 1, bar_nif}
};

ERL_NIF_INIT(complex6, nif_funcs, NULL, NULL, NULL, NULL)

   链入式驱动端口,和NIFs就聊到这,后面再写下C Nodes。

posted @ 2013-11-12 15:16  PbLite  阅读(1600)  评论(2编辑  收藏  举报