patterncppMinor
Typesafety with dlsym function loading
Viewed 0 times
withfunctiontypesafetyloadingdlsym
Problem
Aim:
Use C++0x features to make function interposition safer. The problem is that it's easy to make a typo when wrapping and interposing on functions.
Prototype Implementation:
Example usage:
Questions:
Use C++0x features to make function interposition safer. The problem is that it's easy to make a typo when wrapping and interposing on functions.
Prototype Implementation:
#define MAKE_WRAPPER(x) static const wrapper x(#x)
namespace {
template
struct wrapper;
template
struct wrapper {
typedef Ret(*real_func)(Args...);
real_func f;
wrapper(const std::string& sym) : f(NULL) {
dlerror();
void *ptr = dlsym(RTLD_NEXT, sym.c_str());
f = (real_func)ptr;
if (NULL == f) {
std::cerr << "dlsym(): " << dlerror() << std::endl;
}
std::cout << "constructing wrapper: " << this << " for: " << sym << "(at " << ptr << ")" << std::endl;
}
Ret operator()(const Args&... args) const {
return f(args...);
}
};
}Example usage:
extern "C" {
void glXSwapBuffers(Display *dpy, GLXDrawable drawable) {
MAKE_WRAPPER(glXSwapBuffers);
// Do stuff here before making the call to the wrapper
glXSwapBuffers(dpy, drawable);
}
}Questions:
- Is it worth the effort?
- How can it be improved?
Solution
[ I'm assuming your example is meant to
-
It's worth it if you have a use for it I guess.
-
Printing to
Posix recommends the following way to cast when dealing with function types with
The wrapping will cost two moves or one copy + one move per argument passed by value and one forward for reference types. It is transparent to the client; he or she won't need to use
By comparison using
Perhaps the macro should take the name of the variable as an argument; but that may not be necessary depending on your planned usage.
Minor pet peeve: meaningful names. I would have called the constructor parameter
return x(dpy, drawable); rather than call itself recursively. ]-
It's worth it if you have a use for it I guess.
-
Printing to
std::cerr is not error handling, it's error logging. Pick a real error handling strategy and stick to it. Similarly is printing to std::cout necessary? I also believe that the fewer library calls there are in the constructor, the fewer risks of your wrapper calling itself recursively during static initialization of x, which will be disastrous.Posix recommends the following way to cast when dealing with function types with
dlsym:*(void**)&f = dlsym(RTLD_NEXT, sym.c_str());operator() can use perfect forwarding:Ret
operator()(Args... args)
{
f(std::forward(args));
}The wrapping will cost two moves or one copy + one move per argument passed by value and one forward for reference types. It is transparent to the client; he or she won't need to use
std::move unless the original function would already have him required to. Notice that the first copy or move of a by-value argument must be paid no matter what; the real opportunity cost of the wrapper is the (potential) move that comes after that, to pass the argument to the real function.By comparison using
Args const&... breaks if the wrapped function is void foo(T&&);, due to reference collapsing rules the signature will be void operator()(T&);Perhaps the macro should take the name of the variable as an argument; but that may not be necessary depending on your planned usage.
Minor pet peeve: meaningful names. I would have called the constructor parameter
symbol. The time it takes to type three more characters each time is simply negligible compared to the time it takes to write and maintain code. function_type instead of real_func possibly, too.Code Snippets
*(void**)&f = dlsym(RTLD_NEXT, sym.c_str());Ret
operator()(Args... args)
{
f(std::forward<Args>(args));
}Context
StackExchange Code Review Q#4006, answer score: 4
Revisions (0)
No revisions yet.