2

プラグイン情報を保持するために使用される次の構造体があります。私はこれが時間とともに変化する(おそらく追加される)と確信しています。このファイルが修正されると仮定して私が行ったことよりも、ここで行うべきことはありますか?

struct PluginInfo
{
    public:
        std::string s_Author;
        std::string s_Process;
        std::string s_ReleaseDate;
        //And so on...

        struct PluginVersion
        {
            public:
                std::string s_MajorVersion;
                std::string s_MinorVersion;
                //And so on...
        };
        PluginVersion o_Version;

        //For things we aren't prepared for yet.
        void* p_Future;
};

さらに、このシステムの共有オブジェクトを構築するときに取るべき注意事項はありますか。私の勘は、多くのライブラリの非互換性に遭遇することです。助けてください。ありがとう

4

6 に答える 6

6

これはどうですか、それとも私は単純すぎると思っていますか?

struct PluginInfo2: public PluginInfo
{
    public:
        std::string s_License;
};

アプリケーションでは、おそらくPluginInfosへのポインタのみを渡すため、バージョン2はバージョン1と互換性があります。バージョン2のメンバーにアクセスする必要がある場合はdynamic_cast<PluginInfo2 *>、明示的なpluginAPIVersionメンバーのいずれかを使用してバージョンをテストできます。

于 2010-10-14T07:17:30.857 に答える
6

プラグインが同じバージョンのC++コンパイラとstdライブラリソースでコンパイルされている(またはそのstd :: string実装に互換性がなく、すべての文字列フィールドが壊れている可能性があります)。この場合、プラグインを再コンパイルする必要があります。構造体にフィールドを追加することは重要ではありません

または、以前のプラグインとのバイナリ互換性が必要な場合は、プレーンデータと固定サイズのchar配列に固執します(または、サイズに基づいて文字列にメモリを割り当てるか、const char *を渡すAPIを提供します)。この場合、そうではありません。構造体にいくつかの未使用のフィールドがあり、必要に応じてこれらを便利な名前のアイテムに変更することは前代未聞です。このような場合、構造体にフィールドがあり、それがどのバージョンを表しているかを示すことも一般的です。

ただし、バイナリ互換性を期待してstd::stringを使用することは非常にまれです。コンパイラをアップグレードまたは変更することはできません。

于 2010-10-14T07:35:39.507 に答える
2

1つの恐ろしいアイデア:

std::map<std::string, std::string> m_otherKeyValuePairs;次の500年間はAで十分です。

編集:

一方、この提案は誤用されやすいため、TDWTFの対象となる可能性があります。

もう1つの同様に恐ろしい考え:実際
std::string m_everythingInAnXmlBlob;ソフトウェアで見られるように。

(恐ろしい==非推奨)

編集3:

  • 利点:メンバーはオブジェクトスライス
    の対象ではありません。古いソースコードがプロパティバッグに新しいキーを含むPluginInfoオブジェクトをコピーすると、プロパティバッグ全体がコピーされます。std::map
  • 短所:
    多くのプログラマーは、プロパティバッグに無関係なものを追加し始め、プロパティバッグの値を処理するコードを書き始め、メンテナンスの悪夢につながります。
于 2010-10-14T07:03:26.093 に答える
2

rwongが提案すること(std::map<std::string, std::string>)は良い方向です。これにより、意図的な文字列フィールドを追加できます。より柔軟性が必要な場合は、抽象基本クラスを宣言できます

class AbstractPluginInfoElement { public: virtual std::string toString() = 0;};

class StringPluginInfoElement : public AbstractPluginInfoElement 
{ 
  std::string m_value;
  public: 
   StringPluginInfoElement (std::string value) { m_value = value; }
   virtual std::string toString() { return m_value;}
};

次に、PluginVersionなどのより複雑なクラスを派生させて、を保存することができmap<std::string, AbstractPluginInfoElement*>ます。

于 2010-10-14T07:11:04.333 に答える
2

他の誰かが言ったように、バイナリ互換性のために、おそらくCAPIに制限するでしょう。

size多くの場所のWindowsAPIは、構造体にメンバーを配置することでバイナリ互換性を維持しています。

struct PluginInfo
{
    std::size_t size; // should be sizeof(PluginInfo)

    const char* s_Author;
    const char* s_Process;
    const char* s_ReleaseDate;
    //And so on...

    struct PluginVersion
    {
        const char* s_MajorVersion;
        const char* s_MinorVersion;
        //And so on...
    };
    PluginVersion o_Version;
};

sizeそのような獣を作成するときは、それに応じてメンバーを設定する必要があります。

PluginInfo pluginInfo;
pluginInfo.size = sizeof(pluginInfo);
// set other members

に追加のメンバーがある新しいバージョンのAPIに対してコードをコンパイルするとstruct、そのサイズが変更され、そのsizeメンバーに記録されます。API関数は、そのようなものが渡されると、structおそらく最初にそのメンバーを読み取り、そのサイズに応じて、sizeを処理するためのさまざまな方法に分岐します。struct

もちろん、これは進化が線形であり、新しいデータが常にの最後にのみ追加されることを前提としていstructます。つまり、同じサイズのこのようなタイプのバージョンが異なることはありません。


ただし、このような獣を使用することは、ユーザーがコードにエラーを導入することを確実にするための優れた方法です。新しいAPIに対してコードを再コンパイルすると、sizeof(pluginInfo)自動的に適応されますが、追加のメンバーは自動的に設定されません。structCの方法を「初期化」することで、かなりの安全性が得られます。

PluginInfo pluginInfo;
std::memset( &pluginInfo, 0, sizeof(pluginInfo) );
pluginInfo.size = sizeof(pluginInfo);

ただし、技術的には、メモリをゼロ化しても各メンバーに妥当な値が設定されない可能性があるという事実は別として(たとえば、ゼロに設定されたすべてのビットが浮動小数点型の有効な値ではないアーキテクチャが存在する可能性があります)、これは厄介です。 3段階の構築が必要なため、エラーが発生しやすくなります。

解決策は、そのCAPIの周りに小さくインライン化されたC++ラッパーを設計することです。何かのようなもの:

class CPPPluginInfo : PluginInfo {
public:
  CPPPluginInfo()
   : PluginInfo() // initializes all values to 0
  {
    size = sizeof(PluginInfo);
  }

  CPPPluginInfo(const char* author /* other data */)
   : PluginInfo() // initializes all values to 0
  {
    size = sizeof(PluginInfo);
    s_Author = author;
    // set other data
  }
};

クラスは、Cstructのメンバーが指す文字列をバッファに格納することもできるので、クラスのユーザーはそれについて心配する必要さえありません。


編集:これは私が思っていたほど明確ではないように思われるので、ここに例を示します。APIの新しいバージョンで、
まったく同じstruct

struct PluginInfo
{
    std::size_t size; // should be sizeof(PluginInfo)

    const char* s_Author;
    const char* s_Process;
    const char* s_ReleaseDate;
    //And so on...

    struct PluginVersion
    {
        const char* s_MajorVersion;
        const char* s_MinorVersion;
        //And so on...
    };
    PluginVersion o_Version;

    int fancy_API_version2_member;
};

古いバージョンのAPIにリンクされたプラグインstructがこのように初期化されると

PluginInfo pluginInfo;
pluginInfo.size = sizeof(pluginInfo);
// set other members

これstructは古いバージョンになり、APIのバージョン2の新しい光沢のあるデータメンバーが欠落しています。へのポインタを受け入れる2番目のAPIの関数を呼び出すとPluginInfo、古いPluginInfo、短い1つのデータメンバーのアドレスが新しいAPIの関数に渡されます。ただし、バージョン2 API関数の場合、pluginInfo->sizeはよりも小さいためsizeof(PluginInfo)、それをキャッチし、ポインターを。を持たないオブジェクトを指しているものとして扱うことができますfancy_API_version2_member。(おそらく、ホストアプリのAPIの内部は、PluginInfoが付いた新しくて光沢のあるものでありfancy_API_version2_memberPluginInfoVersion1古いタイプの新しい名前です。したがって、新しいAPIが行う必要がPluginInfo*あるのは、プラグインとして渡されたものをキャストすることPluginInfoVersion1*だけです。そのほこりっぽい古いものを処理できるコードに分岐します。)

もう1つの方法は、新しいバージョンのAPIに対してコンパイルされたプラグインで、がPluginInfo含まれ、fancy_API_version2_memberそれについて何も知らない古いバージョンのホストアプリにプラグインされます。繰り返しになりますが、ホストアプリのAPI関数は、自身のAPI関数よりもpluginInfo->size大きいどうかをチェックすることでそれをキャッチできます。その場合、プラグインはおそらく、ホストアプリが認識しているよりも新しいバージョンのAPIに対してコンパイルされています。(または、プラグインの書き込みでメンバーを適切に初期化できませんでした。このやや脆弱なスキームの処理を簡素化する方法については、以下を参照してください。) これに対処する方法は2つあります。最も簡単な方法は、プラグインのロードを拒否することです。または、可能であれば、ホストアプリはとにかくこれで動作し、最後のバイナリのものを単に無視することができますsizeofPluginInfosize
PluginInfo渡されたオブジェクトで、解釈方法がわかりません。ただし、新しいAPIがどのようになるかを正確に知らずに、古いAPIを実装するときに
これを決定する必要があるため、後者は注意が必要です。

于 2010-10-14T09:46:05.097 に答える
1

これがアイデアです。クラスで機能するかどうかはわかりませんが、構造体で確実に機能します。次のように、構造体を将来使用するスペースを「予約」することができます。

struct Foo
{
  // Instance variables here.
  int bar;

  char _reserved[128]; // Make the class 128 bytes bigger.
}

初期化子は、構造体を埋める前に構造体全体をゼロにするため、「予約済み」領域内にあるフィールドにアクセスするクラスの新しいバージョンは、正常なデフォルト値です。

_reservedの前にフィールドを追加するだけで、それに応じてサイズを縮小し、他のフィールドを変更/再配置しない場合は、問題ありません。魔法は必要ありません。古いソフトウェアは新しいフィールドについて知らないため、新しいフィールドに影響を与えることはなく、メモリフットプリントは同じままです。

于 2010-10-14T07:33:55.750 に答える