共享库中的类和静态变量

时间:2010-03-24 04:44:39

标签: c++ shared-libraries

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

App - >核心(.so)< - 插件(.so's)

用于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;
      }
    }
    
  • 1 个答案:

    答案 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>的类型,会导致各种各样的错误。所以非常小心。