HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

Typesafety with dlsym function loading

Submitted by: @import:stackexchange-codereview··
0
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:

#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 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.