std::shared_ptr 和 dlopen(),避免未定义的行为
std::shared_ptr and dlopen(), avoiding undefined behavior
dlopen()
是一个 C 函数,用于在运行时动态加载共享库。如果你不熟悉,模式是这样的:
- 致电
dlopen("libpath", flag)
去图书馆 void *handle
- 致电
dlsym(handle, "object_name")
以 void *object
从图书馆获得您想要的东西
- 用
object
做你想做的事
- 调用
dlclose (handle)
卸载库。
在 C++ 中,这是 std::shared_ptr
的所谓 别名构造函数 的完美用例。模式变为:
- 从
dlopen("libpath", flag)
构造一个 std::shared_ptr<void> handle
,当它的析构函数被调用时将调用 dlclose()
- 从
handle
和 dlsym(handle, "object_name")
构造一个 std::shared_ptr<void> object
- 现在我们可以随心所欲地传递
object
,而完全忘记handle
;当 object
的析构函数被调用时,无论何时,dlclose()
都会被自动调用
精美的图案,效果很好。不过有一个小问题。上面的模式需要从 void*
到 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
来做到这一点?
您可以创建一个结构,使您的指针指向函数并指向库:
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 );
我没有编译,但我觉得足以说明思路了
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.
这并不完全正确,至少在 C++ 中它只是有条件地支持。
5.2.10.8 说:
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.
所以假设 dlsym
内部所做的是将函数指针转换为 void*
,我相信如果你将它转换回函数指针就没问题。
是这样的吗?
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;
};
我怀疑是否需要 hack。正如@sbabbi 指出的那样,有条件地支持到 void*
的往返。在使用 dlsym
到 return 函数指针的系统上,最好支持它。
dlopen()
是一个 C 函数,用于在运行时动态加载共享库。如果你不熟悉,模式是这样的:
- 致电
dlopen("libpath", flag)
去图书馆void *handle
- 致电
dlsym(handle, "object_name")
以void *object
从图书馆获得您想要的东西 - 用
object
做你想做的事
- 调用
dlclose (handle)
卸载库。
在 C++ 中,这是 std::shared_ptr
的所谓 别名构造函数 的完美用例。模式变为:
- 从
dlopen("libpath", flag)
构造一个std::shared_ptr<void> handle
,当它的析构函数被调用时将调用dlclose()
- 从
handle
和dlsym(handle, "object_name")
构造一个 - 现在我们可以随心所欲地传递
object
,而完全忘记handle
;当object
的析构函数被调用时,无论何时,dlclose()
都会被自动调用
std::shared_ptr<void> object
精美的图案,效果很好。不过有一个小问题。上面的模式需要从 void*
到 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
来做到这一点?
您可以创建一个结构,使您的指针指向函数并指向库:
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 );
我没有编译,但我觉得足以说明思路了
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.
这并不完全正确,至少在 C++ 中它只是有条件地支持。
5.2.10.8 说:
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.
所以假设 dlsym
内部所做的是将函数指针转换为 void*
,我相信如果你将它转换回函数指针就没问题。
是这样的吗?
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;
};
我怀疑是否需要 hack。正如@sbabbi 指出的那样,有条件地支持到 void*
的往返。在使用 dlsym
到 return 函数指针的系统上,最好支持它。