[英]classes and static variables in shared libraries
我正在尝试使用以下架构在c ++中编写内容:
App - > Core(.so)< - 插件(.so)
对于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; } }
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>
for int
s或者其他什么,如果生产者看到一个用于int
而消费者没有,那么消费者将很乐意创建错误类型的vector<T>
,这将是导致各种各样的错误。 所以要非常小心。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.