Подтвердить что ты не робот

Классы и статические переменные в разделяемых библиотеках

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

App → Core (.so) < - Плагины (.so's)

для linux, mac и windows. Ядро неявно связано с App и Plugins, которые явно связаны с dlopen/LoadLibrary для App. У меня проблема:

статические переменные в Core дублируются во время выполнения. Плагины и приложение имеют разные копии. по крайней мере на Mac, когда плагин возвращает указатель на приложение, динамическое кастинг, указатель в приложении всегда приводит к NULL.

Может кто-нибудь дать мне несколько объяснений и инструкций для разных платформ, пожалуйста? Я знаю, что это может показаться ленивым, чтобы спросить их всех здесь, но я действительно не могу найти систематический ответ на этот вопрос.

Что я сделал в 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;
  }
}
4b9b3361

Ответ 1

Некорректный фон

Общие библиотеки на С++ довольно сложны, потому что стандарт ничего не говорит о них. Это означает, что каждая платформа имеет другой способ сделать это. Если мы ограничимся 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();

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

Теперь: к фактической точке!

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

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

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

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

Не разрешать разные компиляторы.

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

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

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

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

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

Добавьте это в 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<T> для int или что-то еще, если производитель видит одно для int, но потребитель этого не делает, потребитель с радостью создаст неправильный тип vector<T>, который вызовет всевозможные ошибки. Поэтому будьте очень осторожны.