Search code examples
c++11shared-librariesshared-ptrundefined-behaviordlsym

std::shared_ptr and dlopen(), avoiding undefined behavior


dlopen() is a C function used for dynamically loading shared libraries at runtime. The pattern, in case you're not familiar, is thus:

  • Call dlopen("libpath", flag) to get a void *handle to the library
  • Call dlsym(handle, "object_name") to get a void *object to the thing you want from the library
  • Do what you want with object
  • Call dlclose (handle) to unload the library.

This is, in C++, a perfect use-case for the so-called aliasing constructor of std::shared_ptr. The pattern becomes:

  • Construct a std::shared_ptr<void> handle from dlopen("libpath", flag) that will call dlclose() when its destructor is called
  • Construct a std::shared_ptr<void> object from handle and dlsym(handle, "object_name")
  • Now we can pass object wherever we want, and completely forget about handle; when object's destructor is called, whenever that happens to be, dlclose() will be called automagically

Brilliant pattern, and it works beautifully. One small problem, though. The pattern above requires a cast from void* to whatever_type_object_is*. If "object_name" refers to a function (which most of the time it does, considering the use-case), this is undefined behavior.

In C, there is a hack to get around this. From the dlopen man page:

// ...
void *handle;    
double (*cosine)(double);
// ...
handle = dlopen("libm.so", RTLD_LAZY);
// ...

/* Writing: cosine = double (*)(double)) dlsym(handle, "cos");
   would seem more natural, but the C99 standard leaves
   casting from "void *" to a function pointer undefined.
   The assignment used below is the POSIX.1-2003 (Technical
   Corrigendum 1) workaround; see the Rationale for the
   POSIX specification of dlsym(). */

*(void **) (&cosine) = dlsym(handle, "cos");
// ...

which obviously works just fine, in C. But is there an easy way to do this with std::shared_ptr?


Solution

  • The pattern above requires a cast from void* to whatever_type_object_is*. If "object_name" refers to a function (which most of the time it does, considering the use-case), this is undefined behavior.

    Well this is not entirely true, at least in C++ it is just conditionally-supported.

    5.2.10.8 says:

    Converting a function pointer to an object pointer type or vice versa is conditionally-supported. The meaning of such a conversion is implementation-defined, except that if an implementation supports conversions in both directions, converting a prvalue of one type to the other type and back, possibly with different cvqualification, shall yield the original pointer value.

    So assuming that what dlsym does internally is casting a function pointer to a void*, I believe that you are ok if you just cast it back to a function pointer.