klasy i zmienne statyczne w bibliotekach współdzielonych

Próbuję napisać coś w c++ o architekturze typu:

App -- > Core (. so)

Dla Linuksa, mac i windows. Rdzeń jest pośrednio połączony z aplikacją, a wtyczki są bezpośrednio połączone z dlopen / LoadLibrary do aplikacji. Problem jaki mam:

  • zmienne statyczne w Core są duplikowane w czasie wykonywania -- wtyczki i aplikacje mają różne kopie.
  • przynajmniej na Macu, gdy wtyczka zwraca wskaźnik do aplikacji, dynamiczny oddanie tego wskaźnika w aplikacji zawsze skutkuje NULL. Czy ktoś może mi podać jakieś wyjaśnienia i instrukcje dotyczące różnych platform? Wiem, że to może wydawać się leniwe, aby zadać je wszystkie tutaj, ale naprawdę nie mogę znaleźć systematycznej odpowiedzi na to pytanie.

    Co zrobiłem w entry_point.cpp dla wtyczki:

    #include "raw_space.hpp"
    
    #include <gamustard/gamustard.hpp>
    
    using namespace Gamustard;
    using namespace std;
    
    namespace
    {
      struct GAMUSTARD_PUBLIC_API RawSpacePlugin : public Plugin
      {
        RawSpacePlugin(void):identifier_("com.gamustard.engine.space.RawSpacePlugin")
        {
        }
    
        virtual string const& getIdentifier(void) const
        {
          return identifier_;
        }
    
        virtual SmartPtr<Object> createObject(std::string const& name) const
        {
          if(name == "RawSpace")
          {
            Object* obj = NEW_EX RawSpaceImp::RawSpace;
            Space* space = dynamic_cast<Space*>(obj);
            Log::instance().log(Log::LOG_DEBUG, "createObject: %x -> %x.", obj, space);
            return SmartPtr<Object>(obj);
          }
          return SmartPtr<Object>();
        }
    
      private:
        string identifier_;
      };
    
      SmartPtr<Plugin> __plugin__;
    }
    
    extern "C"
    {
      int GAMUSTARD_PUBLIC_API gamustardDLLStart(void) throw()
      {
        Log::instance().log(Log::LOG_DEBUG, "gamustardDLLStart");
        __plugin__.reset(NEW_EX RawSpacePlugin);
        PluginManager::instance().install(weaken(__plugin__));
        return 0;
      }
    
      int GAMUSTARD_PUBLIC_API gamustardDLLStop(void) throw()
      {
        PluginManager::instance().uninstall(weaken(__plugin__));
        __plugin__.reset();
        Log::instance().log(Log::LOG_DEBUG, "gamustardDLLStop");
        return 0;
      }
    }
    
  • Author: Deanie, 2010-03-24

    1 answers

    Jakieś Tło

    Biblioteki współdzielone w C++ są dość trudne, ponieważ standard nic o nich nie mówi. Oznacza to, że każda platforma ma inny sposób ich wykonywania. Jeśli ograniczymy się do Windows i jakiegoś wariantu * nix (cokolwiek ELF), różnice są subtelne. Pierwsza różnica to widoczność obiektów współdzielonych . Zaleca się przeczytanie tego artykułu, aby uzyskać dobry przegląd tego, jakie atrybuty widoczności są i co robią dla Ciebie, co pomoże Ci uchronić się przed błędami linkera.

    W każdym razie, skończysz z czymś, co wygląda tak (dla kompilacji z wieloma systemami):

    #if defined(_MSC_VER)
    #   define DLL_EXPORT __declspec(dllexport)
    #   define DLL_IMPORT __declspec(dllimport)
    #elif defined(__GNUC__)
    #   define DLL_EXPORT __attribute__((visibility("default")))
    #   define DLL_IMPORT
    #   if __GNUC__ > 4
    #       define DLL_LOCAL __attribute__((visibility("hidden")))
    #   else
    #       define DLL_LOCAL
    #   endif
    #else
    #   error("Don't know how to export shared object libraries")
    #endif
    

    Następnie będziesz chciał zrobić wspólny nagłówek (standard.h?) i umieścić w nim ładną małą #ifdef rzecz:

    #ifdef MY_LIBRARY_COMPILE
    #   define MY_LIBRARY_PUBLIC DLL_EXPORT
    #else
    #   define MY_LIBRARY_PUBLIC DLL_IMPORT
    #endif
    

    To pozwala Ci oznaczyć klasy, funkcje i cokolwiek w ten sposób:

    class MY_LIBRARY_PUBLIC MyClass
    {
        // ...
    }
    
    MY_LIBRARY_PUBLIC int32_t MyFunction();
    

    To powie systemowi budowania, gdzie szukać funkcji, gdy je wywoła.

    Teraz: do sedna!

    Jeśli udostępniasz stałe między bibliotekami, więc nie powinieneś przejmować się, czy są duplikowane, ponieważ twoje stałe powinny być małe, a duplikacja pozwala na dużą optymalizację (co jest dobre). Jednak, ponieważ wydaje się, że pracujesz z niestałymi, sytuacja jest trochę inna. Istnieje miliard szablonów do stworzenia singletonu w C++, ale naturalnie najbardziej podoba mi się mój sposób.

    W jakimś pliku nagłówkowym, Załóżmy, że chcesz udostępnić liczbę całkowitą, więc masz w myfuncts.h:

    #ifndef MY_FUNCTS_H__
    #define MY_FUNCTS_H__
    // include the standard header, which has the MY_LIBRARY_PUBLIC definition
    #include "standard.h"
    
    // Notice that it is a reference
    MY_LIBRARY_PUBLIC int& GetSingleInt();
    
    #endif//MY_FUNCTS_H__
    

    Wtedy w pliku myfuncts.cpp będziesz miał:

    #include "myfuncs.h"
    
    int& GetSingleInt()
    {
        // keep the actual value as static to this function
        static int s_value(0);
        // but return a reference so that everybody can use it
        return s_value;
    }
    

    Radzenie sobie z szablonami

    C++ ma super potężne szablony, co jest świetne. Jednak przesuwanie szablonów po bibliotekach może być naprawdę bolesne. Gdy kompilator widzi szablon, jest to komunikat, aby "wypełnić co chcesz, aby to działało", co jest całkowicie w porządku, jeśli masz tylko jeden cel końcowy. Jednak może to stać się problemem podczas pracy z wieloma dynamicznymi współdzielonymi obiekty, ponieważ teoretycznie wszystkie mogą być skompilowane z różnymi wersjami różnych kompilatorów, z których wszystkie sądzą, że ich różne metody wypełniania pustych szablonów są poprawne (i kim my jesteśmy, aby się spierać-nie jest to zdefiniowane w standardzie). Oznacza to, że szablony mogą być ogromnym bólem, ale masz kilka opcji.

    Nie zezwalaj na różne Kompilatory.

    Wybierz jeden kompilator (na system operacyjny) i trzymaj się go. Obsługuje tylko ten kompilator i wymaga aby wszystkie biblioteki były kompilowane tym samym kompilatorem. Jest to rzeczywiście bardzo zgrabne rozwiązanie (które całkowicie działa).

    Nie używaj szablonów w eksportowanych funkcjach / klasach

    Używaj funkcji i klas szablonu tylko wtedy, gdy pracujesz wewnętrznie. To oszczędza wiele kłopotów, ale ogólnie jest dość restrykcyjne. Osobiście lubię używać szablonów.

    Wymuś eksport szablonów i miej nadzieję na najlepsze

    [18]}to działa zaskakująco dobrze (zwłaszcza gdy sparowane z nie dopuszczaniem różnych kompilatorów).

    Dodaj to do standard.h:

    #ifdef MY_LIBRARY_COMPILE
    #define MY_LIBRARY_EXTERN
    #else
    #define MY_LIBRARY_EXTERN extern
    #endif
    

    I w jakiejś definicji klasy (przed zadeklarowaniem samej klasy):

    //    force exporting of templates
    MY_LIBRARY_EXTERN template class MY_LIBRARY_PUBLIC std::allocator<int>;
    MY_LIBRARY_EXTERN template class MY_LIBRARY_PUBLIC std::vector<int, std::allocator<int> >;
    
    class MY_LIBRARY_PUBLIC MyObject
    {
    private:
        std::vector<int> m_vector;
    };
    
    To jest prawie całkowicie idealne...kompilator nie będzie na Ciebie krzyczał i życie będzie dobre, chyba że Twój kompilator zacznie zmieniać sposób wypełniania szablonów i przekompilujesz jedną z bibliotek, a nie drugą (i nawet wtedy może nadal działać...czasami).

    Pamiętaj, że jeśli używasz rzeczy takie jak częściowa specjalizacja szablonów (lub cechy typu lub dowolna z bardziej zaawansowanych rzeczy do metaprogramowania szablonów), wszyscy producenci i wszyscy jego konsumenci widzą te same specjalizacje szablonów. Jeśli masz wyspecjalizowaną implementację vector<T> dla intS lub cokolwiek innego, jeśli producent widzi tę Dla int, ale konsument nie, konsument chętnie stworzy niewłaściwy typ vector<T>, co spowoduje wszelkiego rodzaju naprawdę pokręcone błędy. Więc bądź Bardzo ostrożnie.

     43
    Author: Travis Gockel,
    Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
    2020-06-20 09:12:55