2016-03-16 36 views
8

dlopen() to funkcja języka C używana do dynamicznego ładowania bibliotek współdzielonych w środowisku wykonawczym. Wzór, w przypadku gdy nie jesteś zaznajomiony jest tak:std :: shared_ptr i dlopen(), unikając niezdefiniowanych zachowań

  • połączeń dlopen("libpath", flag) dostać void *handle do biblioteki
  • połączeń dlsym(handle, "object_name") dostać void *object z rzeczą, którą chcesz z biblioteki
  • Do czego chcesz, aby object
  • wyładować biblioteki.

To jest w C++, doskonałej użytkowej przypadku do tzw aliasing konstruktora z std::shared_ptr. Wzór przyjmuje postać:

  • Construct std::shared_ptr<void> handle od dlopen("libpath", flag) że wezwie dlclose() gdy jego destruktora nazywa
  • Narysuj std::shared_ptr<void> object z handle i dlsym(handle, "object_name")
  • Teraz możemy przejść object gdziekolwiek chcemy i całkowicie zapomnieć o handle; gdy destruktor object „s nazywa, ilekroć że zdarza się, dlclose() zostanie wywołany automagicznie

Brilliant wzór i działa pięknie. Ale mały problem. Powyższy wzór wymaga obsady od void* do whatever_type_object_is*. Jeśli "object_name" odnosi się do funkcji (która w większości przypadków robi to, biorąc pod uwagę przypadek użycia), jest to niezdefiniowane zachowanie.

W języku C jest hack do obejścia tego. Od strony 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"); 
// ... 

co oczywiście działa dobrze, w C. Ale czy istnieje prosty sposób to zrobić z std::shared_ptr?

+0

dlaczego potrzebujesz 'std :: shared_ptr' do poointer, zwrócony przez dlsym? – Slava

+1

@Slava: aby zagwarantować całe życie (nie wywołuj 'dlclose' gdy jest tam ten wskaźnik). – Jarod42

+0

Konstruktor aliasów 'std :: shared_ptr' umożliwia dwóm' std :: shared_ptr's współdzielenie tego samego "warunku zamknięcia" (mój termin, nie oficjalny) bez wskazania tego samego obiektu lub nawet tego samego typu. Użycie 'std :: shared_ptr' dla wartości zwróconej przez' dlsym() 'zyskuje mi tę korzyść: okres istnienia biblioteki jest związany z czasem życia obiektu. – Arandur

Odpowiedz

4

Powyższy wzór wymaga rzutu z pustki * na whatever_type_object_is *. Jeśli "nazwa_obiektu" odnosi się do funkcji (która przez większość czasu to robi, biorąc pod uwagę przypadek użycia), jest to niezdefiniowane zachowanie.

To nie jest do końca prawdą, przynajmniej w C++ jest po prostu warunkowo obsługiwana.

5.2.10.8 mówi:

Konwersja wskaźnik funkcji do wskaźnika typu obiektu lub odwrotnie jest warunkowo obsługiwane.Znaczenie takiej konwersji jest zdefiniowane przez implementację, z tą różnicą, że jeśli implementacja obsługuje konwersje w w obu kierunkach, konwersja wartości jednego rodzaju na inny i z powrotem, możliwe, że przy innym zakwalifikowaniu, przyniesie oryginalną wartość wskaźnika.

Więc zakładając, że to, co robi dlsym wewnętrznie rzuca wskaźnik funkcji do void*, wierzę, że jesteś w porządku, jeśli tylko rzucić go z powrotem do wskaźnika funkcji.

+0

http://en.cppreference.com/w/cpp/language/reinterpret_cast W szczególności punkt 8 obejmuje tę – Niall

+1

I myślę, że to była intencja http://www.open-std.org/jtc1/sc22/wg21 /docs/cwg_defects.html#195 – Niall

0

Można dokonać struct mieć swój wskaźnik na funkcję i uchwyt do biblioteki:

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

nie skompilować, ale myślę, że to wystarczy, aby pokazać pomysł.

1

Coś takiego?

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

Wątpię, czy hak jest potrzebny. Jak zauważył @sbabbi, podróż w obie strony do void* jest warunkowo obsługiwana. W systemie używającym dlsym do zwracania wskaźników funkcji lepiej jest je wspierać.

+0

Nie dziedziczysz po 'std :: enable_shared_from_this'. Myślę, że zamieniasz kolejność argumentów 'shared_ptr' (dla konstruktora * aliasing *). – Jarod42

+0

@ Jarod42 ups, wcześniejsza wersja miała. Okazało się, że przechowywanie "uchwytu" było mądrzejsze. – Yakk