Я пытаюсь записать что-то в C++ с архитектурой как:
Приложение-> Ядро (.so) <-Плагины (.so's)
для Linux, Mac и окон. Ядро неявно связано с Приложением, и Плагины явно связаны с dlopen/LoadLibrary к Приложению. Проблема я имею:
Кто-либо может дать мне некоторые объяснения и инструкции для различных платформ? Я знаю, что это может казаться ленивым для выяснения у них всех здесь, но я действительно не могу найти систематический ответ на этот вопрос.
Что я сделал в 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;
}
}
Совместно используемые библиотеки в 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
, что вызовет всевозможные действительно испорченные ошибки.Так что будьте очень осторожны.