erlang 通过NIF从C++返回自定义数据结构

snz8szmq  于 2022-12-08  发布在  Erlang
关注(0)|答案(1)|浏览(152)

我有一个C函数,我想通过NIF导出。它接受并操作自定义数据结构,std::vectors等。
我对Elixir类型和C
类型之间的转换顺序感到困惑。
我知道enif_make_resource()enif_release_resource()enif_open_resource_type()
返回数据时必须使用它们吗?还是只在解析传入参数时使用?
部分代码:

static int nif_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) {
  ErlNifResourceType* rt = enif_open_resource_type(env, nullptr, "vector_of_my_struct_s1",
    vec1_dtor, ERL_NIF_RT_CREATE, nullptr);

  if (rt == nullptr) {
    return -1;
  }

  assert(vec1_res == nullptr);
  vec1_res = rt;
  return 0;
}

ERL_NIF_INIT(Elixir.MyApp, nif_funcs, nif_load, nullptr, nullptr, nullptr);

和功能:

ERL_NIF_TERM do_cpp_calculations_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
  // [........for now, parsing incoming arguments is skipped...]

  std::vector<MyStructS1> native_res1 = do_cpp_calculations1(....);

  ERL_NIF_TERM term;

  // how to return 'native_res1' ?

  // do I have to I use these functions at all?

  // enif_alloc_resource(...) ?
  // enif_make_resource(..)  ?

  // and how?

  return term;
}
uemypmqf

uemypmqf1#

In the doc and this module from the official repo you have examples on how to do it.
Usually the steps you need are:

  1. Create empty resource
  2. Operate with the resource
    Sometimes you do both in the same function.

Create empty resource:

Example from the doc:

ERL_NIF_TERM term;
    MyStruct* obj = enif_alloc_resource(my_resource_type, sizeof(MyStruct));
    
    /* initialize struct ... */
    
    term = enif_make_resource(env, obj);
    
    if (keep_a_reference_of_our_own) {
        /* store 'obj' in static variable, private data or other resource object */
    }
    else {
        enif_release_resource(obj);
        /* resource now only owned by "Erlang" */
    }
    return term;

I'd recommend releasing the resource immediately and relying on the GC for the destructor, which should free the vector memory (you should make std::vector use enif_alloc to manage the memory), so in the end you may have something along the lines of:

static ERL_NIF_TERM create(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
    {
        void* pointer_to_resource_memory = enif_alloc_resource(vec1_res, sizeof(ResourceStruct));
        // TODO Initialize the resource memory
        ERL_NIF_TERM ret = enif_make_resource(env, pointer_to_resource_memory);
        enif_release_resource(pointer_to_resource_memory);
        return ret;
    
    }

Operate with the resource

In order to work with it, you only need to extract the pointer from the resource:

static ERL_NIF_TERM do_stuff(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
    void* pointer_to_resource_memory = NULL;
    if (!enif_get_resource(env, argv[0], vec1_res, &pointer_to_resource_memory) {
        return enif_make_badarg(env);
    }
    // TODO do stuff with the resource memory

    // TODO make a term and return it, no need to create a new resource
    return enif_make_int(env, 0);
}

Keep in mind that you don't need to create a new resource nor return it again if you don't want to, you're modifying the memory pointed by it.

Wrapping a std:vector in a resource

You may have noticed that in the snippets above I used only 'resource' and not 'vector', you have a choice there (my C++ is a bit rusty, though, so take the following with a grain of salt):
You can have the resource hold a pointer to the vector (safest):

typedef struct {
   std::vector<MyStructS1>* vector; 
} ResourceStruct;

void* pointer_to_resource_memory = enif_alloc_resource(vec1_res, sizeof(ResourceStruct));
pointer_to_resource_memory->vector = new std::vector(...) // std::vector constructor is called here
// TODO 'new' should use enif_alloc(), destroy, enif_free()

or you can have the resource be the vector (I'm not sure if this syntax is allowed, but you get the idea):

void* pointer_to_vector_object_memory = enif_alloc_resource(vec1_res, sizeof(std::vector<MyStructS1>));
// TODO: Somehow initialize a std::vector in the given memory

相关问题