【问题标题】:Value of binary changing after NIF calls ErlangNIF 调用 Erlang 后二进制变化的值
【发布时间】:2014-12-17 19:29:39
【问题描述】:

我打算使用 NIF 来操作我打算在 Erlang 中编写代码的应用程序的二进制文件。 下面给出了 NIF 的 cpp 文件和 erl 文件的 gist 链接。

[Erl Gist 链接]https://gist.github.com/abhijitiitr/3a5bc97184d6dd32f97b

[C++ 要点链接]https://gist.github.com/abhijitiitr/24d2b780f2cdacebfb07

基本上我正在尝试做一个简单的测试。在 NIF 调用之间共享二进制文件,并通过连续的 NIF 调用成功地操作它们。

如果你在 erlang REPL 中测试代码

c(binary_test).
Ref=binary_test:open(<<1>>).
binary_test:increment(Ref,<<3>>).

二进制文件存储了 NIF 调用之间的更改。第三个命令的 REPL 输出是

1
 3
  60
    60
      <<"?">>

我在初始化阶段通过了&lt;&lt;1&gt;&gt;。为什么改成&lt;&lt;60&gt;&gt;?我无法弄清楚这里发生了什么。有人能指出错误吗?

C++编译说明

clang++ -std=c++11 -stdlib=libc++ -undefined dynamic_lookup -O3 -dynamiclib binary_test.cpp -o binary_test.so -I /usr/local/Cellar/erlang/17.0/lib/erlang/erts-6.0/include/ 

在我的 Mac 上。

我还想问一下在 NIF 中操作共享资源的并发进程。这是可能的还是有一个规则是必须在单个 Erlang 进程中访问 NIF。

【问题讨论】:

  • 什么是“二进制”?你的意思是“一个整数”吗?
  • Binary 是原生 Erlang 类型,可以包含任何类型。 >,>,>。从本质上讲,您可以将其视为 ByteString 之类的东西。
  • NIF 和资源可能被多个进程同时使用,但线程安全的责任在于您。

标签: c c++11 erlang erlang-shell erlang-nif


【解决方案1】:

您遇到问题是因为您非法访问内存。在你的BinaryStore 构造函数中,你试图从传递给binary_test:open/1 的参数列表中保存一个二进制文件,但这不起作用,因为一旦NIF 调用完成,这些参数就会被释放。您需要保存参数的副本以供以后使用。为此,首先向您的 BinaryStore 类添加一个新成员:

    ErlNifEnv* term_env;

接下来,修改您的构造函数以分配term_env,然后使用它来复制传入的术语:

    BinaryStore(ERL_NIF_TERM binary)
    {
        term_env = enif_alloc_env();
        binary_term = enif_make_copy(term_env, binary);
    }

这会在term_env 环境中分配binary_term,然后将传入的术语复制到其中。你还需要一个析构函数来释放term_env:

    ~BinaryStore()
    {
        enif_free_env(term_env);
    }

最后,在increment_binary 函数中检查binary_term 时,您需要传递term_env 而不是env:

    nifpp::get_throws(term_env, binary_term, ibin);

完成这些修改后,我通过运行代码得到以下结果:

1> Ref=binary_test:open(<<1>>).
Reading symbols for shared libraries . done
<<>>
2> binary_test:increment(Ref,<<3>>).
1
 3
  1
   1
    <<4>>

(顺便说一句,从 Erlang 仿真器内部打印时,您应该使用 "\r\n" 换行符而不仅仅是 "\n",这样换行符总是返回到最左边的列。)

你还有一个问题,就是你泄漏了分配给new_bin2的内存。

我对学习 NIF 细节的建议是首先避免使用像 nifpp 这样的包,这样你就可以了解 NIF API 以及有关内存所有权、资源分配和释放以及参数转换的所有细节。一旦你理解了它们,使用像nifpp 这样的包就会变得更加容易和更有成效。

【讨论】:

  • 非常感谢您的清晰解释。使用nifpp 的原因基本上是为了编写简洁的代码,nifpp 我认为一旦没有任何指向资源的引用,就会解除分配给new_bin2 的内存。
  • 在第 446 行 niffpp.h 中,我认为资源在分配后被销毁,如果通过 C Api 调用它应该有。如果我错了,请纠正我。
  • 但对于new_bin2,您只需通过enif_alloc_binary 为其分配内存,然后通过memcpy 将内存复制到其中。没有涉及它的nifpp 调用。因此,您需要在退出increment_binary 函数之前对其调用enif_release_binary。或者,完全摆脱 new_bin2 并使用 ibin 代替它,因为不需要复制 ibin 来使用它的值。
  • 谢谢,我明白你的意思。关于ibin,我想我只能访问ibin的值,不能修改。
  • 正确,您无法更改ibin。但是用它来替换 new_bin2 是完全可以的,因为 1)new_bin2 只是 ibin 的副本,2)你使用 new_bin2 只是为了读取,而不是写入。
【解决方案2】:

ERL_NIF_TERMs 必须与 ErlNifEnv 关联,并且传递给您的 nif 函数的 env 仅在该函数调用期间有效。当您将术语存储到 BinaryStore 对象中,然后在另一个 nif 调用中使用它时,您违反了此规则。您的选择:

  1. 为您的二进制存储创建一个新的 ErlNifEnv,并将 nif 调用中的术语复制到这个新的环境中。

  2. 使用 C++ 数据结构(例如 std::vector&lt;unsigned char&gt;)来存储您的二进制数据。我认为这对您的情况会更简单。

【讨论】:

  • 谢谢。明白了理由。
猜你喜欢
  • 2012-07-04
  • 2014-11-22
  • 2016-03-30
  • 2012-06-25
  • 2012-03-20
  • 2014-12-19
  • 2019-01-02
  • 2016-10-06
  • 1970-01-01
相关资源
最近更新 更多