web-dev-qa-db-ja.com

共有ライブラリのクラスと静的変数

私は次のようなアーキテクチャでC++で何かを書き込もうとしています。

アプリ->コア(.so)<-プラグイン(.so's)

linux、Mac、Windows用。コアは暗黙的にアプリにリンクされ、プラグインはdlopen/LoadLibraryを使用してアプリに明示的にリンクされます。私が抱えている問題:

  • coreの静的変数は実行時に複製されます-プラグインとアプリはそれらの異なるコピーを持っています。
    • 少なくともMacでは、プラグインが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;
        }
      }
      
  • 28
    abel

    いくつかの背景

    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++でクロスライブラリシングルトンを作成するためのパターンは10億ありますが、私は当然自分のやり方が一番好きです。

    いくつかのヘッダーファイルで、整数を共有したいとします。そのため、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++には非常に強力なテンプレートがあり、これはすばらしいことです。ただし、ライブラリ間でテンプレートをプッシュするのは非常に面倒です。コンパイラーがテンプレートを見ると、「これを機能させるために必要なものをすべて入力してください」というメッセージが表示されます。これは、最終的なターゲットが1つしかない場合はまったく問題ありません。ただし、複数の動的共有オブジェクトを使用している場合は、理論的にはすべて異なるコンパイラの異なるバージョンでコンパイルできるため、問題になる可能性があります。これらはすべて、異なるテンプレートの空白を埋める方法が正しいと考えています。 (そして誰が主張するのか-それは標準で定義されていません)。これは、テンプレートが巨大な苦痛になる可能性があることを意味しますが、いくつかのオプションがあります。

    異なるコンパイラを許可しないでください。

    (オペレーティングシステムごとに)1つのコンパイラを選択し、それに固執します。そのコンパイラのみをサポートし、すべてのライブラリを同じコンパイラでコンパイルする必要があります。これは実際には本当に素晴らしい解決策です(それは完全に機能します)。

    エクスポートされた関数/クラスでテンプレートを使用しないでください

    内部で作業している場合にのみ、テンプレート関数とクラスを使用してください。これは多くの手間を省きますが、全体的にはかなり制限されます。個人的には、テンプレートを使うのが好きです。

    テンプレートのエクスポートを強制し、最高のものを期待します

    これは驚くほどうまく機能します(特に、異なるコンパイラを許可しないことと組み合わせた場合)。

    これを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;
    };
    

    これはほぼ完全に完璧です...コンパイラがテンプレートへの入力方法を変更し始め、ライブラリの1つを再コンパイルし、もう1つを再コンパイルしない限り、コンパイラはあなたに怒鳴らず、人生は良くなります(そしてそれでもまだ動作するかもしれません...時々)。

    部分的なテンプレートの特殊化(またはタイプ特性やより高度なテンプレートメタプログラミングのもの)などを使用している場合、すべてのプロデューサーとそのすべてのコンシューマーに同じテンプレートの特殊化が表示されることに注意してください。のように、intsなどのvector<T>の特殊な実装がある場合、プロデューサーがintの実装を表示し、コンシューマーは表示しない場合、コンシューマーは喜んで作成します。 vector<T>のタイプが間違っていると、あらゆる種類の本当にめちゃくちゃなバグが発生します。したがって、非常に注意してください。

    40
    Travis Gockel