2016-03-16 34 views
8

dlopen() è una funzione C utilizzata per caricare dinamicamente le librerie condivise in fase di esecuzione. Il modello, nel caso in cui non hai familiarità, è dunque:std :: shared_ptr e dlopen(), evitando comportamenti non definiti

  • chiamata dlopen("libpath", flag) per ottenere un void *handle alla biblioteca
  • chiamata dlsym(handle, "object_name") per ottenere un void *object alla cosa che si vuole dalla libreria
  • Do cosa vuoi con object
  • Chiama dlclose (handle) per scaricare la libreria.

Questo è, in C++, un caso d'uso perfetto per la cosiddetta aliasing costruttore di std::shared_ptr. Il modello diventa:

  • costruire un std::shared_ptr<void> handle da dlopen("libpath", flag) che chiamerà dlclose() quando il suo distruttore viene chiamato
  • costruire un std::shared_ptr<void> object da handle e dlsym(handle, "object_name")
  • Ora possiamo passare object dove vogliamo, e completamente dimenticare handle; quando distruttore object s' è detto, ogni volta che sembra essere, dlclose() saranno chiamati automagically

brillante modello, e funziona meravigliosamente. Un piccolo problema, però. Il modello sopra richiede un cast da void* a whatever_type_object_is*. Se "object_name" si riferisce a una funzione (che la maggior parte delle volte fa, considerando il caso d'uso), questo è un comportamento non definito.

In C, c'è un trucco per aggirare questo. Dalla pagina dlopen man:

// ... 
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"); 
// ... 

che funziona, ovviamente, più che bene, in C. Ma c'è un modo semplice per fare questo con std::shared_ptr?

+0

perché hai bisogno di 'std :: shared_ptr' nel poointer, restituito da dlsym? – Slava

+1

@Slava: per garantire la durata (non chiamare 'dlclose' quando c'è quel puntatore). – Jarod42

+0

Il costruttore aliasing di 'std :: shared_ptr' consente a due' std :: shared_ptr's di condividere la stessa "condizione di chiusura" (il mio termine, non ufficiale) senza indicare lo stesso oggetto o lo stesso tipo. L'uso di 'std :: shared_ptr' per il valore restituito da' dlsym() 'mi procura questo vantaggio: la durata della libreria è legata alla durata di' object'. – Arandur

risposta

4

Il modello precedente richiede un cast da void * a qualunque_type_object_is *. Se "object_name" si riferisce a una funzione (che la maggior parte delle volte fa, considerando il caso d'uso), questo è un comportamento indefinito.

Beh, questo non è completamente vero, almeno in C++ è supportato solo condizionatamente.

5.2.10.8 dice:

Conversione di un puntatore a funzione a un tipo di puntatore oggetto o viceversa è condizionatamente supportato.Il significato di tale conversione è definito dall'implementazione, tranne che se un'implementazione supporta le conversioni in in entrambe le direzioni, convertendo un valore di un tipo nell'altro tipo e viceversa, eventualmente con una qualifica diversa, deve fornire il valore del puntatore originale.

Quindi, supponendo che ciò che fa dlsym internamente sta lanciando un puntatore a funzione a un void*, io credo che tu sei ok se solo lanci di nuovo ad un puntatore a funzione.

+0

http://en.cppreference.com/w/cpp/language/reinterpret_cast In particolare il punto 8 copre questo – Niall

+1

E penso che questo fosse l'intento http://www.open-std.org/jtc1/sc22/wg21 /docs/cwg_defects.html#195 – Niall

0

È possibile effettuare una struct per avere il vostro puntatore a funzione e gestire alla libreria:

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

non ho compilarlo, ma penso che sia sufficiente a dimostrare l'idea.

1

Qualcosa di simile?

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

Dubito che sia necessaria una modifica. Come notato da @sbabbi, il round-trip su void* è supportato in modo condizionale. Su un sistema che utilizza dlsym per restituire i puntatori di funzione, è meglio supportarlo.

+0

Non si eredita da 'std :: enable_shared_from_this'. Penso che scambiate l'ordine degli argomenti 'shared_ptr' (per il costruttore * aliasing *). – Jarod42

+0

@ Jarod42 oops, la versione precedente ha fatto. Il risultato di archiviare un 'handle' era più intelligente. – Yakk