классы и статические переменные в общих библиотеках - PullRequest
27 голосов
/ 24 марта 2010

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

Приложение -> Core (.so) <- Плагины (.so) </p>

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

статические переменные в 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;
  }
}

1 Ответ

40 голосов
/ 24 марта 2010

Немного фона

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...