Search code examples
c++dlopenany

std::any_cast throws a bad any_cast error when converting void* to function pointer


I wrote the code below,

std::unordered_map<std::string_view, std::any> symbols_;
symbols_["foo"] = dlsym(handle_), "foo");

When i use any_cast return (std::any_cast<void(*)()>(symbols_["foo"]))();, the program will throw a error: bad any_cast.

I found the main reason because of the function .

template<typename _Tp>
void* __any_caster(const any* __any)

It would judge the condition as false and then return nullptr.

else if (__any->_M_manager == &any::_Manager<_Up>::_S_manage
#if __cpp_rtti
      || __any->type() == typeid(_Tp)
#endif
      ){
      any::_Arg __arg;
      __any->_M_manager(any::_Op_access, __any, &__arg);
      return __arg._M_obj;
}
return nullptr;

I want to know

1.why __any->_M_manager == &any::_Manager<_Up>::_S_manage and __any->type() == typeid(_Tp) were all false,

2.and how can i fix the problem(continue to use std::any).

Here is a simple demo.

#include <any>

void func() { }

auto main() -> int {
    std::any a = (void*)func;
    std::any_cast<void(*)()>(a)();
    return 1;
}

gcc version 10.1.0 (GCC)


Solution

  • Here you store a void* in the std::any object:

    symbols_["foo"] = dlsym(handle_, "foo");
    

    To instead store a void(*)(), you need to cast the void* that's returned by dlsym:

    symbols_["foo"] = reinterpret_cast<void(*)()>(dlsym(handle_, "foo"));
    

    In this case, you may want to just store the void* and cast when using it instead:

    std::unordered_map<std::string_view, void*> symbols_;
    symbols_["foo"] = dlsym(handle_, "foo");
    //...
    return reinterpret_cast<void(*)()>(symbols_["foo"])();
    

    A third option, if you don't need the runtime lookup in the unordered_map, is to instead store the function pointers in named variables. That makes the usage a little easier. Here's an example:

    A generic class for loading/unloading a shared library:

    class Lib { 
    public:
        explicit Lib(const char* filename, int flags = RTLD_LAZY) :
            lib(dlopen(filename, flags)) 
        {
            if(!lib) throw std::runtime_error(dlerror());
        }
    
        Lib(const Lib&) = delete;
        Lib(Lib&& rhs) = delete;
        Lib& operator=(const Lib&) = delete;
        Lib& operator=(Lib&& rhs) = delete;
        virtual ~Lib() { dlclose(lib); }
    
    private:
        struct cast_proxy { // a class to cast to the proper pointer
            // cast to whatever that is needed:
            template<class Func>
            operator Func () { return reinterpret_cast<Func>(sym); }
            void* sym;
        };
    
    protected:
        cast_proxy sym(const char* symbol) const {
            void* rv = dlsym(lib, symbol);
            if(rv) return {rv};                   // put it in the cast_proxy
            throw std::runtime_error(dlerror());
        }
    
    private:
        void* lib;
    };
    

    A class for loading a specific shared library:

    class YourLib : public Lib {
    public:
        YourLib() : Lib("./libyour_library.so"),
            // load all symbols here:
            foo(sym("foo"))   // the cast proxy will return the correct pointer type
        {}
    
        // Definitions of all the symbols you want to load:
        void(*const foo)();
    };
    

    Then using it will be as simple as this:

    int main() {
        YourLib ml;
        ml.foo();
    }