2

I am currently using the dlopen functions for some plugin project.
This function handle returns a void* and then I save all the handle to a map named handles:

void* handle = dlopen(path.c_str(), RTLD_LAZY);  
handles[file] = handle;

My goal is to pass the ownership to the map, I was thinking of a unique_ptr, but not sure if this is even possible.

If it's not possible what other alternatives do I have ?

Adrian
  • 19,440
  • 34
  • 112
  • 219
  • 1
    `void*` by itself has no concept of ownership, the only "ownership" a raw pointer knows about is that you are supposed to delete it, if you dont then the pointer does not own anything – 463035818_is_not_an_ai May 23 '19 at 07:56
  • Possible duplicate of [Why is shared\_ptr legal, while unique\_ptr is ill-formed?](https://stackoverflow.com/questions/39288891/why-is-shared-ptrvoid-legal-while-unique-ptrvoid-is-ill-formed) – Passer By May 23 '19 at 07:56
  • What does ownership exactly mean here? IOW, what cleanup are you supposed to do when you're done with the handle (if any)? – Angew is no longer proud of SO May 23 '19 at 08:05
  • The handle needs to be closed. So I would have want when clearing the map, to close all handles autoamtically (like inside the unique_ptr deleter maybe). Currently I have to call "close" manually and also erase, so there are 2 operations. – Adrian May 23 '19 at 08:07
  • I'm not sure you really "own" the void*. I mean, how could you possibly delete something you don't know the size of. Look to the API for how you properly are meant to clean the handle, then perhaps you can wrap that in something you could use smart pointer semantics with. – Nathan Cooper May 23 '19 at 08:08
  • @NathanCooper: yep, I have to call Close on every handle, so the solution maybe is to encapsulated into a RAII object and use that – Adrian May 23 '19 at 08:09
  • 1
    I'm not really sure, but [custom deleters](https://www.bfilipek.com/2016/04/custom-deleters-for-c-smart-pointers.html) may be a streamlined way of achieving that. Never used them. Someone else can answer. – Nathan Cooper May 23 '19 at 08:16

3 Answers3

10

If I understand correctly you can do something like

Define a close function and an alias for the pointer type:

auto closeFunc = [](void* vp) {
  dlclose(vp);
};
using HandlePtr = std::unique_ptr<void, decltype(closeFunc)>;
std::map<std::string, HandlePtr> handles;

and then create the handles and add to the map:

void* handle = dlopen(path.c_str(), RTLD_LAZY); 
HandlePtr ptr( handle, closeFunc );

handles[file] = std::move( ptr );

Then closeFunc will be called when the unique ptr goes out of scope

The raw pointer can be prevented by combining the two lines above:

HandlePtr handle(dlopen(path.c_str(), RTLD_LAZY), closeFunc );
handles[file] = std::move( handle );

This makes use of the second argument to the std::unique_ptr that specifies the deleter to use.

PS: maps and unique_ptrs don't play well as-is, you might need some emplaces or moves depending on the C++ standard you are using. Or use shared_ptr instead.

CJCombrink
  • 3,738
  • 1
  • 22
  • 38
0

The std::unique_ptr is particularly useful for "wrapping" c-libraries that operate using a resource than needs deleting or closing when finished. Not only does it stop you forgetting when to delete/close the resource it also make using the resource exception safe - because the resource gets properly cleaned up in the event of an exception.

I would probably do something like this:

// closer function to clean up the resource
struct dl_closer{ void operator()(void* dl) const { dlclose(dl); }};

// type alias so you don't forget to include the closer functor
using unique_dl_ptr = std::unique_ptr<void, dl_closer>;

// helper function that ensures you get a valid dl handle
// or an exception.    
unique_dl_ptr make_unique_dl(std::string const& file_name)
{
    auto ptr = unique_dl_ptr(dlopen(file_name.c_str(), RTLD_LAZY));

    if(!ptr)
        throw std::runtime_error(dlerror());

    return ptr;
}

And use it a bit like this:

// open a dl
auto dl_ptr = make_unique_dl("/path/to/my/plugin/library.so");

// now set your symbols using POSIX recommended casting gymnastics
void (*my_function_name)();
if(!(*(void**)&my_function_name = dlsym(dl_ptr.get(), "my_function_name")))
    throw std::runtime_error(dlerror());
Galik
  • 47,303
  • 4
  • 80
  • 117
  • And then write a class to also wrap `dlsym` (so `shared_ptr` seems more appropriate to ensure to not close whereas a function is still active). – Jarod42 May 23 '19 at 08:56
  • @Jarod42 You would **only** use a `std::shared_ptr` when your design makes it impossible to know which of two (or more) dynamic objects (which hold a pointer to the `dl`) will go out of scope first. Usually `std::unique_ptr` is the best choice and that should be used unless you are *forced* to use a `std::shared_ptr` (which should be fairly rare). – Galik May 23 '19 at 09:16
  • Lifetime of `my_function_name` should be shorter than `dl_ptr`. With appropriate `share_ptr` usage, `my_function_name` would extend lifetime of `dl_ptr` and so avoid to use "dangling" `my_function_name`. But indeed, with `unique_ptr`, you might write a wrapper around full `library.so` to guaranty that lifetime. – Jarod42 May 23 '19 at 09:23
  • @Jarod42 It is also not hard with "appropriate" `unique_ptr` usage to do exactly the same thing - ensure your symbols do not outlive your `dl`. Instead of making `my_function_name` hold a shared reference to your `dl` you could make your `dl` class "wrap" all the functions that `dl` gives unique access to. – Galik May 23 '19 at 09:27
  • IMO, users may store the functions, so it seems natural to me that the resource is shared. But indeed, if you don't provide direct access to function (or don't enforce that guaranty) `unique_ptr` is fine too. Mostly depends of the interface we want for the wrapper class. – Jarod42 May 23 '19 at 09:36
0

Even if TheBadger answer's is the best to follow. Remember that there is no concept of ownership in C++, C++ provides classes to simulate this and make a good use of RAII, but you don't really have any guarantees from the language to assure a ressource's ownership, even with unique_ptr, for example :

{
  auto p_i = new int{5};
  auto up_i = std::unique_ptr<int>{p_i};
  *p_i = 6; //nothing in the language prevent something like this
  assert(*up_i == 6); //the unique_ptr ownership is not assured as you can see here
  delete p_i; //still not illegal
} //at run time, RAII mechanism strikes, destroy unique_ptr, try to free an already freed memory, ending up to a core dumped

You even have a get member function in unique_ptr class that gives you the opportunity to mess up your memory as you could with raw pointers.

In C++, there's no mechanism to detect "ownership" violation at compile time, maybe you can find some tools out there to check it but it's not in the standard, but it's only a matter of recommended practices.

Thus, it's more relevant and precise to talk about "managed" memory in C++ instead of "ownership".

Steranoid
  • 64
  • 6
  • 1
    *"Managed memory"* generally refers to garbage collector. We really use the term *"ownership"* in C++ (and yes, we can still do garbage in C++ with misuse). – Jarod42 May 23 '19 at 09:27
  • We use the term, yes, as a way to express the fact that generally speaking, you won't modify the memory managed by a unique_ptr and thus, the "ownership" of the ressource is respected, but this kind of ownership is only a matter of recommended practices, as I said, the language has no mechanism to check at compile time any ownership violations as you have in rust. I think it's really important to remember that. – Steranoid May 23 '19 at 09:37
  • Never own the native pointers. Use handles. There is no sane mechanism that will prevent native pointer double free, use after free and such. `std::unique_ptr` is one-way to go. – Chef Gladiator Aug 05 '21 at 10:00