类和共享库中的静态变量 [英] classes and static variables in shared libraries

查看:160
本文介绍了类和共享库中的静态变量的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述



应用程序 - > Core(.so)< - 插件(.so's) / p>

用于linux,mac和windows。 Core隐式链接到应用程序和插件显式链接到dlopen / LoadLibrary到应用程序。我有的问题:

  • Core中的静态变量在运行时是重复的 - 插件和应用程序有不同的副本。
  • 至少在mac上,当插件返回指向App的指针时,在App中动态转换指针总是导致NULL。



    任何人都可以给我一些不同平台的解释和说明吗?我知道这可能看起来很懒,问他们这里,但我真的找不到系统的答案这个问题。



    我在插件的entry_point.cpp中做了什么:

      #includeraw_space.hpp

    #include< gamustard / gamustard.hpp>

    使用命名空间Gamustard;
    using namespace std;

    namespace
    {
    struct GAMUSTARD_PUBLIC_API RawSpacePlugin:public Plugin
    {
    RawSpacePlugin(void):identifier _(com.gamustard.engine.space.RawSpacePlugin )
    {
    }

    虚拟字符串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> __插入__;
    }

    externC
    {
    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 __ ))
    #define DLL_IMPORT
    #if __GNUC__> 4
    #define DLL_LOCAL __attribute __((visibility(hidden)))
    #else
    #define DLL_LOCAL
    #endif
    #else
    #error (不知道如何导出共享对象库)
    #endif

    下一页,你会想要一些共享头( standard.h ?),并放一个很好的小 #ifdef in:

      #ifdef MY_LIBRARY_COMPILE 
    #define MY_LIBRARY_PUBLIC DLL_EXPORT
    #else
    #define MY_LIBRARY_PUBLIC DLL_IMPORT
    #endif

    这样可以标记类,函数等: / p>

      class MY_LIBRARY_PUBLIC MyClass 
    {
    // ...
    }

    MY_LIBRARY_PUBLIC int32_t MyFunction();

    这将告诉构建系统在调用函数时要在哪里查找函数。



    现在:到实际点!



    如果你要跨库共享常量,那么你实际上不应该是重复的,因为你的常量应该小,重复允许大量的优化(这是好的)。但是,由于你似乎使用非常量,情况有点不同。在C ++中有十亿种模式可以创建一个跨库的单例,但我自然喜欢我最好的方式。



    在一些头文件中,让我们假设你想分享一个整数,所以你会在 myfuncts.h

      #ifndef MY_FUNCTS_H__ 
    #define MY_FUNCTS_H__
    //包括标准头,其中有MY_LIBRARY_PUBLIC定义
    #includestandard.h

    //注意它是一个引用
    MY_LIBRARY_PUBLIC int& GetSingleInt();

    #endif // MY_FUNCTS_H__

    然后,在 myfuncts.cpp 文件,您将拥有:

      #includemyfuncs.h

    int& GetSingleInt()
    {
    //保持实际值为该函数的静态值
    static int s_value(0);
    //但返回一个引用,以便每个人都可以使用
    return s_value;
    }



    处理模板



    C ++有超强的模板,这是伟大的。但是,跨图书馆推送模板可能真的很痛苦。当编译器看到一个模板时,它是填充任何你想要使这个工作,这是完全没有,如果你只有一个最终目标的消息。然而,当你使用多个动态共享对象时,它可能会成为一个问题,因为它们理论上可以使用不同版本的不同编译器进行编译,所有这些编译器都认为它们不同的模板填充空白方法是正确的(和我们谁是争论 - 它没有在标准中定义)。



    不允许使用不同的编译器。



    选择一个编译器(每个操作系统)并坚持下去。只支持那个编译器,并要求所有的库都用同一个编译器编译。



    不要在导出的函数/类中使用模板



    这是一个非常整齐的解决方案

    在内部工作时,仅使用模板函数和类。这确实节省了很多麻烦,但总体来说是相当限制。



    强制导出模板并希望获得最佳效果





    将此添加到 standard.h

      #ifdef MY_LIBRARY_COMPILE 
    #define MY_LIBRARY_EXTERN
    #else
    #define MY_LIBRARY_EXTERN extern
    #endif

    在一些消费类定义中(在声明类本身之前):

      //强制导出模板
    MY_LIBRARY_EXTERN模板类MY_LIBRARY_PUBLIC std :: allocator< int>
    MY_LIBRARY_EXTERN模板类MY_LIBRARY_PUBLIC std :: vector< int,std :: allocator< int> > ;;

    class MY_LIBRARY_PUBLIC MyObject
    {
    private:
    std :: vector< int> m_vector;
    };

    这几乎完全完美...编译器不会嘲笑你,生活会很好,除非你的编译器开始改变它填充模板的方式,你重新编译的库之一,而不是其他(甚至,然后,它可能仍然工作...有时)。



    请记住,如果您使用部分模板专门化(或类型traits或任何更高级的模板元编程),所有的生产者和所有的消费者都看到相同的模板专业化。如果你有一个专门的实现向量< T> int int 但是消费者不会,消费者将会愉快地创建错误类型的向量< T> 这将导致各种各样的真正烂掉的错误。所以要非常小心。


    I am trying to write something in c++ with an architecture like:

    App --> Core (.so) <-- Plugins (.so's)

    for linux, mac and windows. The Core is implicitly linked to App and Plugins are explicitly linked with dlopen/LoadLibrary to App. The problem I have:

  • static variables in Core are duplicated at run-time -- Plugins and App have different copys of them.
  • at least on mac, when a Plugin returns a pointer to App, dynamic casting that pointer in App always result in NULL.

    Can anyone give me some explanations and instructions for different platforms please? I know this may seem lazy to ask them all here but I really cannot find a systematic answer to this question.

    What I did in the entry_point.cpp for a plugin:

    #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;
      }
    }
    

    解决方案

    Some Background

    Shared libraries in C++ are quite difficult because the standard says nothing about them. This means that every platform has a different way of doing them. If we restrict ourselves to Windows and some *nix variant (anything ELF), the differences are subtle. The first difference is Shared Object Visibility. It is highly recommended that you read that article so you get a good overview of what visibility attributes are and what they do for you, which will help save you from linker errors.

    Anyway, you'll end up with something that looks like this (for compiling with many systems):

    #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
    

    Next, you'll want to make some shared header (standard.h?) and put a nice little #ifdef thing in it:

    #ifdef MY_LIBRARY_COMPILE
    #   define MY_LIBRARY_PUBLIC DLL_EXPORT
    #else
    #   define MY_LIBRARY_PUBLIC DLL_IMPORT
    #endif
    

    This lets you mark classes, functions and whatever like this:

    class MY_LIBRARY_PUBLIC MyClass
    {
        // ...
    }
    
    MY_LIBRARY_PUBLIC int32_t MyFunction();
    

    This will tell the build system where to look for the functions when it calls them.

    Now: To the actual point!

    If you're sharing constants across libraries, then you actually should not care if they are duplicated, since your constants should be small and duplication allows for much optimization (which is good). However, since you appear to be working with non-constants, the situation is a little different. There are a billion patterns to make a cross-library singleton in C++, but I naturally like my way the best.

    In some header file, let's assume you want to share an integer, so you would do have in 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__
    

    Then, in the myfuncts.cpp file, you would have:

    #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;
    }
    

    Dealing with templates

    C++ has super-powerful templates, which is great. However, pushing templates across libraries can be really painful. When a compiler sees a template, it is the message to "fill in whatever you want to make this work," which is perfectly fine if you only have one final target. However, it can become an issue when you're working with mutliple dynamic shared objects, since they could theoretically all be compiled with different versions of different compilers, all of which think that their different template fill-in-the-blanks methods is correct (and who are we to argue -- it's not defined in the standard). This means that templates can be a huge pain, but you do have some options.

    Don't allow different compilers.

    Pick one compiler (per operating system) and stick to it. Only support that compiler and require that all libraries be compiled with that same compiler. This is actually a really neat solution (that totally works).

    Don't use templates in exported functions/classes

    Only use template functions and classes when you're working internally. This does save a lot of hassle, but overall is quite restrictive. Personally, I like using templates.

    Force exporting of templates and hope for the best

    This works surprisingly well (especially when paired with not allowing different compilers).

    Add this to standard.h:

    #ifdef MY_LIBRARY_COMPILE
    #define MY_LIBRARY_EXTERN
    #else
    #define MY_LIBRARY_EXTERN extern
    #endif
    

    And in some consuming class definition (before you declare the class itself):

    //    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;
    };
    

    This is almost completely perfect...the compiler won't yell at you and life will be good, unless your compiler starts changing the way it fills in templates and you recompile one of the libraries and not the other (and even then, it might still work...sometimes).

    Keep in mind that if you're using things like partial template specialization (or type traits or any of the more advanced template metaprogramming stuff), all the producer and all its consumers are seeing the same template specializations. As in, if you have a specialized implementation of vector<T> for ints or whatever, if the producer sees the one for int but the consumer does not, the consumer will happily create the wrong type of vector<T>, which will cause all sorts of really screwed up bugs. So be very careful.

    这篇关于类和共享库中的静态变量的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

  • 查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆