建置 C/C++

🚧

無 Port 編譯器

在 Rebar3 中,需要有 Makefile 或其他指令來建置您的 C/C++ 程式碼,而不是透過 Rebar3 本身。

使用 Makefile 範本

我們將首先建立一個名為 `test_nif` 的新程式庫,然後使用 `test_nif` 專案根目錄下的 `cmake` 範本。

$ rebar3 new lib test_nif
===> Writing test_nif/src/test_nif.erl
===> Writing test_nif/src/test_nif.app.src
===> Writing test_nif/rebar.config
===> Writing test_nif/.gitignore
===> Writing test_nif/LICENSE
===> Writing test_nif/README.md
$ cd test_nif
$ rebar3 new cmake
===> Writing c_src/Makefile

在 `test_nif` 的 `rebar.config` 中,新增 pre_hooks 行,以便在執行 `compile` 時呼叫 `make`。此外,新增 `post_hooks` 項目以清理建置的 C 物件檔。

`rebar3 new cmake` 寫入的 `Makefile` 是一個 GNU Makefile,這表示您需要在系統上安裝 GNU Make。在範例中,我們提供了一個 FreeBSD 作業系統的處理程式,它假設 GNU Make 被稱為 `gmake`。

{pre_hooks,
  [{"(linux|darwin|solaris)", compile, "make -C c_src"},
   {"(freebsd)", compile, "gmake -C c_src"}]}.
{post_hooks,
  [{"(linux|darwin|solaris)", clean, "make -C c_src clean"},
   {"(freebsd)", clean, "gmake -C c_src clean"}]}.

以下是一個 NIF,它有一個函數 `repeat`,它將接收一個 `pid` 和一個要發送到該 `pid` 的 Erlang 項。

#include "erl_nif.h"

static ERL_NIF_TERM
mk_atom(ErlNifEnv* env, const char* atom)
{
    ERL_NIF_TERM ret;

    if(!enif_make_existing_atom(env, atom, &ret, ERL_NIF_LATIN1))
    {
        return enif_make_atom(env, atom);
    }

    return ret;
}

static ERL_NIF_TERM
mk_error(ErlNifEnv* env, const char* mesg)
{
    return enif_make_tuple2(env, mk_atom(env, "error"), mk_atom(env, mesg));
}

static ERL_NIF_TERM
repeat(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
    ErlNifEnv* msg_env;
    ErlNifPid pid;
    ERL_NIF_TERM copy;

    if(argc != 2)
    {
        return enif_make_badarg(env);
    }

    if(!enif_is_pid(env, argv[0]))
    {
        return mk_error(env, "not_a_pid");
    }

    if(!enif_get_local_pid(env, argv[0], &pid))
    {
        return mk_error(env, "not_a_local_pid");
    }

    msg_env = enif_alloc_env();
    if(msg_env == NULL)
    {
        return mk_error(env, "environ_alloc_error");
    }

    copy = enif_make_copy(msg_env, argv[1]);

    if(!enif_send(env, &pid, msg_env, copy))
    {
        enif_free(msg_env);
        return mk_error(env, "error_sending_term");
    }

    enif_free_env(msg_env);
    return mk_atom(env, "ok");
}

static ErlNifFunc nif_funcs[] = {
    {"repeat", 2, repeat}
};

ERL_NIF_INIT(test_nif, nif_funcs, NULL, NULL, NULL, NULL);

修改 `test_nif.erl` 以從 `priv` 載入 `test_nif` 共享程式庫並匯出 `repeat/2`。

-module(test_nif).
-export([repeat/2]).
-on_load(init/0).

-define(APPNAME, test_nif).
-define(LIBNAME, test_nif).

repeat(_, _) ->
    not_loaded(?LINE).

init() ->
    SoName = case code:priv_dir(?APPNAME) of
        {error, bad_name} ->
            case filelib:is_dir(filename:join(["..", priv])) of
                true ->
                    filename:join(["..", priv, ?LIBNAME]);
                _ ->
                    filename:join([priv, ?LIBNAME])
            end;
        Dir ->
            filename:join(Dir, ?LIBNAME)
    end,
    erlang:load_nif(SoName, 0).

not_loaded(Line) ->
    erlang:nif_error({not_loaded, [{module, ?MODULE}, {line, Line}]}).

請注意,`error()` 函數將導致 Dializer 錯誤。建議使用 `erlang:nif_error/1`,它不會導致錯誤。

執行 `rebar3 shell` 並試用 NIF。

$ rebar3 shell
===> Verifying dependencies...
===> Compiling test_nif
Erlang/OTP 17 [erts-6.3] [source] [64-bit] [smp:4:4] [async-threads:0] [kernel-poll:false]

Eshell V6.3  (abort with ^G)
1> test_nif:repeat(self(), hello).
ok
2> receive X -> X end.
hello

參考

上次修改時間:2023 年 9 月 11 日:修正 test_nif 範例中的編譯器警告 (f113813)