классы и статические переменные в общих библиотеках

Я пытаюсь записать что-то в C++ с архитектурой как:

Приложение-> Ядро (.so) <-Плагины (.so's)

для Linux, Mac и окон. Ядро неявно связано с Приложением, и Плагины явно связаны с dlopen/LoadLibrary к Приложению. Проблема я имею:

  • статические переменные в Ядре дублированы во времени выполнения - Плагины и Приложение имеют различные копии их.
  • по крайней мере, на Mac, когда Плагин возвращает указатель на Приложение, динамический кастинг, что указатель в Приложении всегда приводит к ПУСТОМУ УКАЗАТЕЛЮ.
  • Кто-либо может дать мне некоторые объяснения и инструкции для различных платформ? Я знаю, что это может казаться ленивым для выяснения у них всех здесь, но я действительно не могу найти систематический ответ на этот вопрос.

    Что я сделал в entry_point.cpp для плагина:

    #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;
      }
    }
    
    28
    задан Deanie 27 May 2016 в 18:15
    поделиться

    1 ответ

    Некоторые предыстории

    Совместно используемые библиотеки в C ++ довольно сложны, потому что в стандарте о них ничего не говорится. Это означает, что на каждой платформе они выполняются по-разному. Если мы ограничимся Windows и некоторым вариантом * nix (чем-либо ELF), различия будут незначительными. Первое отличие - Видимость общих объектов . Настоятельно рекомендуется прочитать эту статью, чтобы получить хорошее представление о том, что такое атрибуты видимости и что они делают для вас, что поможет спасти вас от ошибок компоновщика.

    В любом случае, вы получите что-то вроде этого (для компиляции со многими системами):

    #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
    

    Затем вы захотите создать общий заголовок ( standard.h ?) и поместите в него небольшую симпатичную #ifdef штуку:

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

    Это позволит вам отмечать классы, функции и тому подобное:

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

    Это сообщит системе сборки, где искать функции при вызове их.

    Теперь: к делу!

    Если вы разделяете константы между библиотеками, то вам на самом деле не нужно беспокоиться о том, дублируются ли они, поскольку ваши константы должны быть небольшими, а дублирование позволяет значительно оптимизировать (что хорошо). Однако, поскольку вы работаете с неконстантными, ситуация немного иная. Существует миллиард шаблонов для создания кросс-библиотечного синглтона на C ++, но, естественно, мне больше всего нравится мой способ.

    Предположим, что в каком-то заголовочном файле вы хотите поделиться целым числом, поэтому в файле 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__
    

    Затем в файле myfuncts.cpp , у вас будет:

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

    Работа с шаблонами

    В C ++ есть сверхмощные шаблоны, и это здорово. Однако распространение шаблонов между библиотеками может быть очень болезненным. Когда компилятор видит шаблон, это сообщение «заполните все, что вы хотите, чтобы эта работа работала», что совершенно нормально, если у вас есть только одна конечная цель.Однако это может стать проблемой, когда вы работаете с несколькими динамическими общими объектами, поскольку теоретически все они могут быть скомпилированы с разными версиями разных компиляторов, каждый из которых считает, что их различные методы заполнения пробелов верны. (а кто мы такие, чтобы спорить - в стандарте это не определено). Это означает, что шаблоны могут быть огромной головной болью, но у вас есть некоторые варианты.

    Не допускайте использования разных компиляторов.

    Выберите один компилятор (для каждой операционной системы) и придерживайтесь его. Поддерживайте только этот компилятор и требуйте, чтобы все библиотеки были скомпилированы с одним и тем же компилятором. Это действительно отличное решение (которое полностью работает).

    Не используйте шаблоны в экспортируемых функциях / классах

    Используйте шаблонные функции и классы только при внутренней работе. Это избавляет от лишних хлопот, но в целом является довольно ограничительным. Лично мне нравится использовать шаблоны.

    Принудительный экспорт шаблонов и надежда на лучшее

    Это работает на удивление хорошо (особенно в сочетании с запретом использования разных компиляторов).

    Добавьте это в standard.h :

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

    И в определении некоторого потребляющего класса (до объявления самого класса):

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

    Это почти полностью идеально ... компилятор не будет кричите на вас, и жизнь будет хорошей, если ваш компилятор не начнет изменять способ заполнения шаблонов, и вы не перекомпилируете одну из библиотек, а не другую (и даже тогда это может работать ... иногда).

    Имейте в виду, что если вы используете такие вещи, как частичная специализация шаблона (или черты типов, или любой из более сложных элементов метапрограммирования шаблонов), все производители и все его потребители видят одни и те же специализации шаблонов. Например, если у вас есть специализированная реализация vector для int s или чего-то еще, если производитель видит вариант для int , но потребитель видит нет, потребитель с радостью создаст неправильный тип vector , что вызовет всевозможные действительно испорченные ошибки.Так что будьте очень осторожны.

    41
    ответ дан 28 November 2019 в 03:30
    поделиться
    Другие вопросы по тегам:

    Похожие вопросы: