【翻訳】Port drivers #Erlang

[公式ドキュメント[6 Port drivers]](http://www.erlang.org/doc/tutorial/c_portdriver.html)の翻訳です。

---

# 6 ポートドライバ
ポートドライバ接続を使った[例題](http://www.erlang.org/doc/tutorial/example.html)の解決方法の例
![port_driver](http://www.erlang.org/doc/tutorial/port_driver.gif)
**Figure 6.1**:   ポートドライバ通信

## 6.1  Port Drivers
ポートドライバはErlangプログラムからポートとして利用可能なドライバに接続する。ドライバはシェアードライブラリ（UnixはSOファイル、WindowsはDLLファイル）で、特別なエントリポイントを持つ。Erlangのランタイムはドライバを開始するときとポートへデータを送るときにこれらのエントリポイントを呼び出す。ポートドライバはErlangへデータを送ることもできる。

ポートドライバは仮想的なプロセス（ _emulator process_ ）へ動的に接続するので、ErlangからCのコードを呼び出す方法の中で最も早い。ポートドライバの中の呼び出された関数はコンテキストスイッチを要求しない。しかし、ポートドライバがクラッシュするとエミュレータも落ちるので、最も危険な方法である。

## 6.2 Erlangプログラム
ポートプログラムと同じ方法で、ポートがErlangプロセスと通信する。全ての通信はポートドライバの **繋がったErlangプロセス** を通る。このプロセスが終了するとポートドライバも閉じる。
ポートが作られる前にドライバをロードする必要がある。これは関数 `erl_dll:load_driver/1` で行う。引数として、共有ライブラリの名前が必要である。
ポートはBIF `open_port/2` を使って作られる。第一引数はタプル `{spawn, DriverName}` である。変数 `SharedLib` はポートドライバの名前である。第2引数はオプションのリストで、後のサンプルでは空である。

```erlang
-module(complex5).
-export([start/1, init/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).
```

今、 `complex5:foo/1` と `complex5:bar/1` を実装することができる。それらは両方とも `complex` プロセスにメッセージを送り返信を受け取る。

```erlang
foo(X) ->
    call_port({foo, X}).
bar(Y) ->
    call_port({bar, Y}).

call_port(Msg) ->
    complex ! {call, self(), Msg},
    receive
        {complex, Result} ->
            Result
    end.
```

`complex` プロセスはメッセージをバイト列へエンコードし、ポートへ送り、返信を待ち、返信をデコードし呼び出し元へそれを送り返す。

```erlang
loop(Port) ->
    receive
        {call, Caller, Msg} ->
            Port ! {self(), {command, encode(Msg)}},
            receive
                {Port, {data, Data}} ->
                    Caller ! {complex, decode(Data)}
            end,
            loop(Port)
    end.
```

両方の引数とC関数からの結果の両方が256バイト未満と仮定すると非常に単純なエンコード/デコード方式が採用される。 `foo` はバイトの1で表され、 `bar` はバイトの2で表され、引数と結果も同様に単純なバイトで表される。

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

ポートを停止してポートの障害を検知する機能を含むErlangのプログラムを以下に示す。

```erlang
-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.
```

## 6.3  Cドライバ
Cドライバは共有ライブラリにコンパイルしてリンクされたモジュールである。ドライバ構造を使い、 `erl_driver.h` をインクルードしている。
ドライバ構造はドライバの名前と関数ポインタで構成される。特別なエントリポイントを返し、 `DRIVER_INIT(<driver_name>)` を宣言する。
データを送受信する機能は、関数に結合され、ドライバ構造によって参照されている。ポートへのデータの送信は引数として与えられ、ポートが送り返したデータはCの関数の `driver_output` で送られる。
ドライバは共有モジュールである（プログラムではない）ので、メイン関数はあるべきではない。すべての関数ポインタは、この例で使用されておらず、driver_entry構造内の対応するフィールドはNULLに設定されている。
ドライバ内の全ての関数は
ドライバの全ての機能は、（ `start` から返される）、ハンドルによって持つ。それはただErlangプロセスによって渡される。これは何らかの方法でポートドライバインスタンスを参照しなければならない。
example_drv_startは、ポートインスタンスのためにハンドルを呼び出す唯一の関数なので、これを守る必要がある。これは、このインスタンスのために割り当てられたドライバ定義の構造体を使用し、参照としてポインタを戻すのが普通である。
これは、グローバル変数を使用するのは良くない。ポートドライバは、複数のErlangのプロセスによって生成することができるので、このドライバ構造体が毎回インスタンス化されるべきです。

<font color='red'>TODO: ErlDrvEntryのコメント</font>

```c: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;
    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;
}
```

## 6.4 サンプルの実行
### 1. Compile the C code.
```shell-session
unix> gcc -o exampledrv -fpic -shared complex.c port_driver.c
windows> cl -LD -MD -Fe exampledrv.dll complex.c port_driver.c
```

### 2. Start Erlang and compile the Erlang code.
```erl
> erl
Erlang (BEAM) emulator version 5.1

Eshell V5.1 (abort with ^G)
1> c(complex5).
{ok,complex5}
```

### 3. Run the example.
```erl
2> complex5:start("example_drv").
<0.34.0>
3> complex5:foo(3).
4
4> complex5:bar(5).
10
5> complex5:stop().
stop
```