我正在尝试使用以下架构在c ++中编写内容:
App - >核心(.so)< - 插件(.so's)
用于linux,mac和windows。 Core隐式链接到App,而插件显式链接到dlopen / LoadLibrary到App。我遇到的问题:
有人可以给我一些针对不同平台的解释和说明吗?我知道这可能看起来很懒,但是我真的找不到这个问题的系统答案。
我在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;
}
}
答案 0 :(得分:40)
C ++中的共享库非常困难,因为标准对它们一无所知。这意味着每个平台都有不同的方式。如果我们将自己限制在Windows和某些* nix变体(任何ELF),那么差异是微妙的。第一个区别是Shared Object Visibility。强烈建议您阅读该文章,以便更好地了解可见性属性及其为您做的事情,这将有助于避免链接器错误。
无论如何,你最终会看到这样的东西(用于编译许多系统):
#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
的{{1}}的专门实现,如果生产者看到int
的那个,但消费者没有,那么消费者会愉快地创造错误vector<T>
的类型,会导致各种各样的错误。所以非常小心。