Значение бинарного изменения после того, как NIF вызывает Erlang

Я намерен манипулировать двоичными файлами с помощью NIF для приложения, которое я планирую кодировать на Erlang. Основные ссылки на файлы cpp и erl для NIF приведены ниже.

[Ссылка на Erl Gist] https://gist.github.com/abhijitiitr/3a5bc97184d6dd32f97b

[C++ Gist Link] 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
      <<"?">>

Я прошел <<1>> на этапе инициализации. Почему он изменился на <<60>>? Я не могу понять, что здесь происходит. Может ли кто-нибудь указать на ошибку?

Инструкции по компиляции С++

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. Возможно ли это или существует правило, согласно которому доступ к NIF должен осуществляться в одном процессе Erlang.


person abips    schedule 22.10.2014    source источник
comment
Что такое бинарник? Вы имеете в виду просто целое число?   -  person unwind    schedule 22.10.2014
comment
Binary — это собственный тип Erlang, который может содержать любой тип, например. ‹‹1››,‹‹lorem ipsum››,‹‹[1,2,3]››. По сути, вы можете думать об этом как о ByteString.   -  person abips    schedule 22.10.2014
comment
NIF и ресурсы могут использоваться многими процессами одновременно, но ответственность за безопасность потоков лежит на вас.   -  person goertzenator    schedule 22.10.2014


Ответы (2)


У вас возникают проблемы, потому что вы незаконно обращаетесь к памяти. В вашем конструкторе 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);
    }

Это выделяет binary_term в среде term_env, а затем копирует в нее входящий термин. Вам также понадобится деструктор, чтобы освободить term_env:

    ~BinaryStore()
    {
        enif_free_env(term_env);
    }

И, наконец, вам нужно передать term_env вместо env при проверке binary_term в функции increment_binary:

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

(Кстати, вы должны использовать окончания строки "\r\n", а не просто "\n" при печати из эмулятора Erlang, чтобы новые строки всегда возвращались в крайний левый столбец.)

У вас все еще есть одна оставшаяся проблема, которая заключается в утечке памяти, выделенной для new_bin2.

Мой совет для изучения деталей NIF: сначала избегайте использования таких пакетов, как nifpp, чтобы вы могли изучить NIF API и все подробности, касающиеся владения памятью, выделения и освобождения ресурсов, а также преобразования аргументов. Как только вы их поймете, использование таких пакетов, как nifpp, станет намного проще и эффективнее.

person Steve Vinoski    schedule 22.10.2014
comment
Большое спасибо за доходчивое объяснение. Причина использования nifpp заключалась в том, чтобы написать краткий код, а nifpp, я думаю, освобождает память, выделенную для new_bin2, когда не осталось ссылок, указывающих на ресурс. - person abips; 22.10.2014
comment
В строке 446 niffpp.h я думаю, что ресурс уничтожен после выделение, как и должно быть, если оно вызывается через C Api. Пожалуйста, поправьте меня, если я ошибаюсь. - person abips; 22.10.2014
comment
Но для new_bin2 вы просто выделяете для него память через enif_alloc_binary, а затем копируете в него память с помощью memcpy. Нет nifpp вызовов с его участием. Поэтому вам нужно вызвать enif_release_binary перед выходом из функции increment_binary. В качестве альтернативы можно вообще избавиться от new_bin2 и просто использовать вместо него ibin, так как нет необходимости копировать ibin только для того, чтобы использовать его значение. - person Steve Vinoski; 22.10.2014
comment
Спасибо, я понял вашу точку зрения. Что касается ibin, я думаю, что могу получить доступ только к значению ibin и не могу его изменить. - person abips; 22.10.2014
comment
Правильно, вы не можете изменить ibin. Но использовать его для замены new_bin2 совершенно нормально, поскольку 1) new_bin2 — это просто копия ibin и 2) вы используете new_bin2 только для чтения, а не для записи. - person Steve Vinoski; 22.10.2014

ERL_NIF_TERMs должен быть связан с ErlNifEnv, а env, который передается вашим функциям nif, действителен только на время вызова этой функции. Вы нарушаете это правило, когда сохраняете термин в объекте BinaryStore, а затем используете его из другого вызова nif. Ваши варианты:

  1. Создайте новый ErlNifEnv для вашего хранилища бинарных файлов и скопируйте термины из вызова nif в этот новый env.

  2. Используйте структуры данных C++ (например, std::vector<unsigned char>) для хранения двоичных данных. Я думаю, что это будет проще для вашей ситуации.

person goertzenator    schedule 22.10.2014
comment
Спасибо. Получил обоснование. - person abips; 22.10.2014