• 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。

  • 相关阅读:
    LightOj 1016
    uva 127 "Accordian" Patience 简单模拟
    hdu 1180 诡异的楼梯 BFS + 优先队列
    UVALive 3907 Puzzle AC自动机+DP
    HDU 4001 To Miss Our Children Time DP
    HDU 4000 Fruit Ninja 树状数组
    hdu_1021_Fibonacci Again_201310232237
    hdu_1005_Number Sequence_201310222120
    hdu_1029-Ignatius and the Princess IV_201310180916
    hdu_1020_Encoding_201310172120
  • 原文地址:https://www.cnblogs.com/pbblog/p/3419564.html
Copyright © 2020-2023  润新知