ElixirでNIFを書く方法

ごく最近、私はロボット工学の世界に飛び込み、RasPiに基づいて自分のロボットをプログラムすることにしました。 このために、Erlang VMのバイトコードにコンパイルする比較的新しいプログラミング言語であるElixirを使用しました。 GPIOの連絡先をすぐに管理できませんでした。 それから、すべての問題を解決しているように見えるライブラリを見つけました。 ただし、ポートとして記述されているため、各関数の呼び出しに時間がかかりすぎて、ロボットの正しい動作に影響を与えました。



少し考えた後、私はまだライブラリをNIFとして書き直すことにしました。 私はこれに関する多くの情報を見つけられなかったので、エリクサーでNIFを書いた経験をあなたと共有することにしました。 例として、作成したものを使用します。



それで、 そもそも 、Cにあるライブラリpiggioを見つけました。これには必要なすべての機能がありました。 次に、次のコマンドを使用して新しいプロジェクトを作成しました。



mix new ex_pigpio
      
      





ミックスプログラムによって自動的に作成された標準フォルダーに、以下を追加しました。



次のステップは、C自体でNIFコードを作成することでした。 最初に、Erlang VMからNIF関数ヘッダーをインポートする必要があります。



 #include <erl_nif.h>
      
      





次に、このNIFがElixirにエクスポートする関数を正確に記述する必要があります。 例として、私の場合:



 static ErlNifFunc funcs[] = { { "set_mode", 2, set_mode }, // ... { "get_pwm_range", 1, get_pwm_range } };
      
      





funcs []は、3つの要素の構造を含む配列です。 最初の要素はElixirの関数の名前です。 2番目は、関数によって受け入れられるパラメーターの数です。 3番目はCの関数自体へのポインターです。 この配列の名前には意味がなく、何でもかまいません。



さらに、NIFはマクロERL_NIF_INITを使用して登録する必要があります。 私にとってはこのように見えます:



 ERL_NIF_INIT(Elixir.ExPigpio, funcs, &load, &reload, &upgrade, &unload)
      
      





このマクロのパラメーターは次のとおりです。



  1. プレフィックス「Elixir。」が付いたElixirのモジュールの名前。 私の場合、モジュールの名前はExPigpioです。 モジュールの名前はコンパイル中に変更され、プレフィックス「Elixir」を使用するため、プレフィックスが必要です。
  2. NIF関数を記述する配列
  3. ライブラリのロード、再ロード、更新、およびアンロード時に呼び出される関数へのポインター。 これらの関数はオプションのコールバックです。 これらのコールバックが必要ない場合は、代わりにNULLを指定できます。




NIF関数の例として、get_pwm_range関数の実装を示したいと思います。



 static ERL_NIF_TERM get_pwm_range(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { ex_pigpio_priv* priv; priv = enif_priv_data(env); unsigned gpio; if (!enif_get_uint(env, argv[0], &gpio)) { return enif_make_badarg(env); } int value = gpioGetPWMrange(gpio); switch(value) { case PI_BAD_USER_GPIO: return enif_make_tuple2(env, priv->atom_error, priv->atom_bad_user_gpio); default: return enif_make_tuple2(env, priv->atom_ok, enif_make_int(env, value)); } }
      
      





すべてのNIF関数は、上記のパラメーターを正確に受け取り、ERL_NIF_TERM型の結果を返す必要があります。 詳細はすべてwww.erlang.org/doc/man/erl_nif.htmlで確認できます。



これで、Cのコードの準備ができました。 現在、Elixirでモジュールを作成しています。 その主なタスクは、ライブラリをCにロードし、NIFに実装されている機能を記述することです。



 defmodule ExPigpio do @on_load :init def init do path = Application.app_dir(:ex_pigpio, "priv/ex_pigpio") |> String.to_char_list :ok = :erlang.load_nif(path, 0) end def set_mode(_gpio, _mode) do exit(:nif_not_loaded) end # ... end
      
      





@on_load:initに注意してください。 これにより、モジュールのロード時にinit関数呼び出しが登録されます。 init関数は、privフォルダー内でex_pigpio.soライブラリを見つけます。 サフィックス「.so」は必要ありません。 自動的に追加されます。 最後に、関数呼び出し:erlang.load_nifがライブラリをロードします。



NIFからElixirまでの各関数に対して、同じ名前とパラメーター数の関数を作成します。 この関数は、NIFのロードに失敗した場合に呼び出されます。 通常、このElixirモジュールで説明されている関数は、nif_not_loadedパラメーターを使用してexitを呼び出すだけです。 それでも、最終機能の代替実装に使用できます。



最後のステップは、プロジェクトをコンパイルすることです。 これを行うには、Makefileを作成し、mix.exsに必要な変更を加える必要があります。



Makefileの例:



 MIX = mix CFLAGS = -O3 -Wall ERLANG_PATH = $(shell erl -eval 'io:format("~s", [lists:concat([code:root_dir(), "/erts-", erlang:system_info(version), "/include"])])' -s init stop -noshell) CFLAGS += -I$(ERLANG_PATH) ifeq ($(wildcard deps/pigpio),) PIGPIO_PATH = ../pigpio else PIGPIO_PATH = deps/pigpio endif CFLAGS += -I$(PIGPIO_PATH) -fPIC LDFLAGS = -lpthread -lrt .PHONY: all ex_pigpio clean all: ex_pigpio ex_pigpio: $(MIX) compile priv/ex_pigpio.so: src/ex_pigpio.c $(MAKE) CFLAGS="-DEMBEDDED_IN_VM" -B -C $(PIGPIO_PATH) libpigpio.a $(CC) $(CFLAGS) -shared $(LDFLAGS) -o $@ src/ex_pigpio.c $(PIGPIO_PATH)/libpigpio.a clean: $(MIX) clean $(MAKE) -C $(PIGPIO_PATH) clean $(RM) priv/ex_pigpio.so
      
      





このMakefileについて特別なことはありません。 LDFLAGSおよびフラグ「-DEMBEDDED_IN_VM」は、すべてのNIFに必要なわけではなく、このプロジェクトに固有のものです。 それどころか、変数ERLANG_PATHはすべてのNIFに必要なものです。



これで、mix.exsに最新の変更を加えることができます。

 defmodule Mix.Tasks.Compile.Pigpio do @shortdoc "Compiles Pigpio" def run(_) do {result, _error_code} = System.cmd("make", ["priv/ex_pigpio.so"], stderr_to_stdout: true) Mix.shell.info result :ok end end defmodule ExPigpio.Mixfile do use Mix.Project def project do [app: :ex_pigpio, version: "0.0.1", elixir: "~> 1.0", build_embedded: Mix.env == :prod, start_permanent: Mix.env == :prod, compilers: [:pigpio, :elixir, :app], deps: deps] end # ... end
      
      





ex.pigpio.soライブラリをコンパイルするのに役立つMix.Tasks.Compile.Pigpioモジュールを作成しています。 runコマンドを実装します。この関数は、パラメーター「priv / ex_pigpio.so」を指定してmakeコマンドを呼び出します。 以下のプロジェクト関数のキーワードで、「compilers」要素を追加し、最初の場所で、標準の要素の前にモジュールを指定します。 ご覧のとおり、モジュールのフルネームの代わりに、最後の部分のみを反映するatom:pigpioを指定しました。



コンパイルするには、次のコマンドを指定します:



 mix compile
      
      





これで、NIFの準備が整いました! 完全なソースコードはこちらです: github.com/briksoftware/ex_pigpio



All Articles