繁体   English   中英

共享库中的类和静态变量

[英]classes and static variables in shared libraries

我正在尝试使用以下架构在c ++中编写内容:

App - > Core(.so)< - 插件(.so)

对于linux,mac和windows。 Core隐式链接到App,而插件显式链接到dlopen / LoadLibrary到App。 我遇到的问题:

  • Core中的静态变量在运行时被复制 - 插件和应用程序具有不同的副本。
  • 至少在mac上,当Plugin返回指向App的指针时,动态转换App中的指针总是导致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; } } 
  • 一些背景

    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.

     
    粤ICP备18138465号  © 2020-2024 STACKOOM.COM