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> object
和handle
dlsym(handle, "object_name")
object
,并完全忘记handle
;当调用object
的析构函数时,无论何时发生,dlclose()
都将被自动调用精美的图案,效果很好。但是有一个小问题。上述模式需要从void*
转换为whatever_type_object_is*
。如果"object_name"
引用了一个函数(考虑到用例,它大部分时间都是这样),这是未定义的行为。
在C中,有一个黑客来解决这个问题。从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
是否有一种简单的方法可以做到这一点?
答案 0 :(得分:4)
上述模式需要从void *转换为whatever_type_object_is *。如果“object_name”引用一个函数(考虑到用例,它大部分时间都是这样),这是未定义的行为。
这不完全正确,至少在C ++中它只是有条件支持。
5.2.10.8 说:
有条件地支持将函数指针转换为对象指针类型,反之亦然。意思 这种转换是实现定义的,除非实现支持转换 两个方向,将一种类型的prvalue转换为另一种类型并返回,可能具有不同的资格, 将产生原始指针值。
因此,假设dlsym
内部正在向void*
投射函数指针,我相信如果你只是将其强制转换为函数指针就可以了。
答案 1 :(得分: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;
};
我怀疑是否需要黑客攻击。正如@sbabbi所指出的那样,void*
的往返是有条件支持的。在使用dlsym
返回函数指针的系统上,最好支持它。
答案 2 :(得分: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 );
我没有编译它,但我认为这足以表明这个想法。