10

dlopen()是用于在运行时动态加载共享库的 C 函数。如果您不熟悉,该模式是这样的:

  • 打电话dlopen("libpath", flag)void *handle图书馆
  • 打电话从图书馆dlsym(handle, "object_name")void *object你想要的东西
  • 做你想做的事object
  • 调用dlclose (handle)卸载库。

在 C++ 中,这是所谓的别名构造函数的完美用例std::shared_ptr。模式变为:

  • 构造一个std::shared_ptr<void> handle将在dlopen("libpath", flag)调用dlclose()其析构函数时调用的
  • std::shared_ptr<void> objecthandleand构造一个dlsym(handle, "object_name")
  • 现在我们可以去object任何我们想去的地方,完全忘记handle;当object's 的析构函数被调用时,无论何时发生,dlclose()都会被自动调用

图案精美,效果很好。不过有一个小问题。上面的模式需要从void*to强制转换whatever_type_object_is*。如果"object_name"引用一个函数(考虑到用例,它在大多数情况下都会这样做),这是未定义的行为。

在 C 中,有一个 hack 可以解决这个问题。从dlopen手册页:

// ...
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");
// ...

这显然工作得很好,在 C 中。但是有没有一种简单的方法可以做到这一点std::shared_ptr

4

3 回答 3

4

上面的模式需要从 void* 转换为whatever_type_object_is*。如果“object_name”指的是一个函数(考虑到用例,它大部分时间都是这样),这是未定义的行为。

好吧,这并不完全正确,至少在 C++ 中它只是有条件地支持。

5.2.10.8说:

有条件地支持将函数指针转换为对象指针类型或反之亦然。这种转换的含义是实现定义的,除非实现支持双向转换,将一种类型的纯右值转换为另一种类型并返回,可能具有不同的 cvqualification,应产生原始指针值。

因此,假设dlsym内部所做的是将函数指针转换为 a void*,我相信如果您只是将它转换回函数指针就可以了。

于 2016-03-16T18:05:39.027 回答
1

像这样的东西?

struct dlib
{
public:
  template<class T>
  std::shared_ptr<T> sym(const char* name) const {
    if (!handle) return {};
    void* sym = dlsym(handle->get(), name);
    if (!sym) return {};
    return {reinterpret_cast<T*>(sym), handle};
  }
  // returns a smart pointer pointing at a function for name:
  template<class Sig>
  std::shared_ptr<Sig*> pfunc(const char* name) const {
    if (!handle) return {};
    void* sym = dlsym(handle->get(), name);
    if (!sym) return {};
    Sig* ret = 0;
    // apparently approved hack to convert void* to function pointer
    // in some silly compilers:
    *reinterpret_cast<void**>(&ret) = sym;
    return {ret, handle};
  }
  // returns a std::function<Sig> for a name:
  template<class Sig>
  std::function<Sig> function(const char* name) const {
    // shared pointer to a function pointer:
    auto pf = pfunc(name);
    if (!pf) return {};
    return [pf=std::move(pf)](auto&&...args)->decltype(auto){
      return (*pf)(decltype(args)(args)...);
    };
  }
  dlib() = default;
  dlib(dlib const&)=default;
  dlib(dlib &&)=default;
  dlib& operator=(dlib const&)=default;
  dlib& operator=(dlib &&)=default;

  dlib(const char* name, int flag) {
    void* h = dlopen(name, flag);
    if (h)
    {
      // set handle to cleanup the dlopen:
      handle=std::shared_ptr<void>(
        h,
        [](void* handle){
          int r = dlclose(handle);
          ASSERT(r==0);
        }
      );
    }
  }
  explicit operator bool() const { return (bool)handle; }
private:
  std::shared_ptr<void> handle;
};

我怀疑是否需要破解。void*正如@sbabbi 所指出的,有条件地支持往返。在使用dlsym返回函数指针的系统上,最好支持它。

于 2016-03-16T18:18:08.173 回答
0

您可以创建一个结构以使您的指针指向函数并处理库:

template<typename T>
struct dlsymbol {
   dlsymbol( const std::string &name, std::shared_ptr<void> handle ) :
      m_handle( std::move( handle ) )
   {
       *(void **)(&m_func) = dlsym( handle.get(), name.c_str );
   }

   std::shared_ptr<void> m_handle;
   T *m_func;
};

auto cosine = std::make_shared<dlsymbol<double(double)>>( "cos", handle );
auto d = cosine->m_func( 1.0 );

我没有编译它,但我认为它足以表明这个想法。

于 2016-03-16T16:36:45.387 回答