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

Time:10-14

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)

CodePudding user response:

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();
}

CodePudding user response:

std::any_cast will only cast back to the type that was stored in the std::any. As dlsym returns void*, that is what is stored in the std::any.

You need a separate cast to void(*)() either before storing in std::any or after the std::any_cast:

std::unordered_map<std::string_view, std::any> symbols_;
symbols_["foo"] = reinterpret_cast<void(*)()>(dlsym(handle_), "foo"));
return (std::any_cast<void(*)()>(symbols_["foo"]))();
  • Related