6

こんばんは、私は現在、Plux.netモデルに基づく C++/Linux のプラグイン システムに取り組んでいます。

簡単にするために、基本的には extern C を使用してシンボルを宣言し (pluginInformation と呼びましょう)、プラグイン マネージャーは事前に構成されたインポート (.so) でそのシンボルを探します。

問題は、メイン アプリケーションが同じシンボルを宣言していることです。それだけでなく、それが持つ依存関係にもシンボルがある可能性があります。(この pluginInformation では、モジュールはプラグおよび/またはスロットを公開できるため)。

そのため、私の PluginManager が起動すると、最初にメイン プログラムでシンボルを見つけようとし ( dlopenに NULL を渡します)、次にその依存関係のいずれかでシンボルを見つけようとします ( dl_iterate_phdrを使用)。そして最後に一連の構成インポートをdlopenします (ユーザーが構成した .so のパスを読み取り、それらをdlopenし、最後にpluginInformationシンボルを dlsym します)。

すべてのモジュールで見つかった pluginInformation のコレクションは、拡張機能 3 を構築するために使用されます。

メイン プログラムでシンボルを宣言し、dlopenを使用してインポートをロードすると、機能します (インポートを dlopen するときにフラグ RTLD_DEEPBIND を渡す限り)。

しかし、アプリケーションの依存関係については、アプリケーションの起動時にこの .sos がロードされたため、フラグを渡すオプションがありません (できるが、何もしません)。

依存関係 (起動時にロードされたもの) から取得したシンボルを使用しようとすると、セグメンテーション違反が発生します。問題は、シンボルテーブルに同じ名前のシンボルがいくつかあることだと思います。奇妙なことは、いくつかのシンボルがあることを正しく識別しているように見え、シンボルがある .so の正しいパスさえ教えてくれることです。宣言されていますが、シンボルにアクセスするとすぐにセグメンテーション違反が発生します。メインプログラムまたは依存関係の1つでのみシンボルを宣言すると、すべてが正しく機能します。

メイン プログラムと dlsym を使用したスト​​ラト アップ インポートの間で重複するシンボルを管理するにはどうすればよいですか?

私はマングリングを維持することを考えていて、シンボルテーブルを通過するシンボルを見つけようとしましたが、これが可能かどうかはわかりません(モジュール内のすべてのシンボルをプログラムでリストします)。

PD: 申し訳ありませんがコードを投稿しませんでしたが、今家にいません。何をしようとしているのかについての説明が十分に明確であることを願っています。そうでない場合は、明日コードを投稿できます。

4

1 に答える 1

5

別のアプローチを次に示します。

アプリケーション自体は、1 つまたは複数のプラグイン アイテム登録関数をエクスポートします。例えば:

int register_plugin_item(const char *const text,
                         const char *const icon,
                         void (*enter)(void *),
                         void (*click)(void *),
                         void (*leave)(void *),
                         void *data);

登録された項目ごとに、2 つの文字列スロット (textおよびicon)、3 つの関数スロット ( enterclick、およびleave)、および呼び出されたときにパラメーターとして関数に与えられる不透明な参照があります。

-rdynamic(メイン アプリケーション (上記の関数を実装するオブジェクト ファイル) をコンパイルするときに、コンパイラ オプションを使用して、リンカーがregister_plugin_itemシンボルを動的シンボル テーブルに確実に追加するようにする必要があることに注意してください。)

各プラグインは、register_plugin_item()必要な項目ごとにコンストラクター関数で関数を呼び出します (ライブラリーのロード時に自動的に実行されます)。関数が実行される環境を最初に調べて、どの機能を登録するか、または各プラグイン項目に使用する関数の最適化されたバリアントを決定することが可能であり、多くの場合便利です。

これは簡単なプラグインの例です。staticプラグインが動的シンボル テーブルを汚染したり、シンボルの競合を引き起こしたりしないように、すべてのシンボルがどのようになっているかに注意してください。

#include <stdlib.h>
#include <stdio.h>

extern int register_plugin_item(const char *const,
                                const char *const,
                                void (*enter)(void *),
                                void (*click)(void *),
                                void (*leave)(void *),
                                void *);

static void enter(void *msg)
{
    fprintf(stderr, "Plugin: Enter '%s'\n", (char *)msg);
}

static void leave(void *msg)
{
    fprintf(stderr, "Plugin: Leave '%s'\n", (char *)msg);
}

static void click(void *msg)
{
    fprintf(stderr, "Plugin: Click '%s'\n", (char *)msg);
}

static void init(void) __attribute__((constructor));
static void init(void)
{
    register_plugin_item("one", "icon-one.gif",
                         enter, leave, click,
                         "1");

    register_plugin_item("two", "icon-two.gif",
                         enter, leave, click,
                         "2");
}

上記のプラグインは 2 つのアイテムをエクスポートします。テストのために、上記のバリアントを少なくとも 2 つ作成します。プラグインが同じ (静的) 変数と関数名を使用している場合でも、シンボルの競合がないことがわかります。

指定されたプラグインをロードし、登録された各項目をテストするアプリケーションの例を次に示します。

#include <stdlib.h>
#include <dlfcn.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>

struct item {
    struct item *next;
    const char  *text;
    const char  *icon;
    void        *data;
    void       (*enter)(void *);
    void       (*leave)(void *);
    void       (*click)(void *);
};

static struct item *list = NULL;

int register_plugin_item(const char *const text,
                         const char *const icon,
                         void (*enter)(void *),
                         void (*click)(void *),
                         void (*leave)(void *),
                         void *data)
{
    struct item *curr;

    curr = malloc(sizeof *curr);
    if (!curr)
        return ENOMEM;

    curr->text = text;
    curr->icon = icon;
    curr->data = data;
    curr->enter = enter;
    curr->leave = leave;
    curr->click = click;

    /* Prepend to list */
    curr->next = list;
    list = curr;

    return 0;
}

int main(int argc, char *argv[])
{
    int          arg;
    void        *handle;
    struct item *curr;

    if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
        fprintf(stderr, "\n");
        fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
        fprintf(stderr, "       %s PLUGIN.so ... \n", argv[0]);
        fprintf(stderr, "\n");
        fprintf(stderr, "Please supply full plugin paths, unless\n");
        fprintf(stderr, "the plugins reside in a standard library directory,\n");
        fprintf(stderr, "or in a directory listed in LD_LIBRARY_PATH.\n");
        fprintf(stderr, "\n");
        return 1;
    }

    for (arg = 1; arg < argc; arg++) {

        handle = dlopen(argv[arg], RTLD_NOW);
        if (handle != NULL)
            fprintf(stderr, "%s: Loaded.\n", argv[arg]);
        else
            fprintf(stderr, "%s.\n", dlerror());

        /* Note: We deliberately "leak" the handle,
         *       so that the plugin is not unloaded. */
    }

    for (curr = list; curr != NULL; curr = curr->next) {
        if (curr->text)
            printf("Item '%s':\n", curr->text);
        else
            printf("Unnamed item:\n");

        if (curr->icon)
            printf("\tIcon is '%s'\n", curr->icon);
        else
            printf("\tNo icon\n");

        if (curr->data)
            printf("\tCustom data at %p\n", curr->data);
        else
            printf("\tNo custom data\n");

        if (curr->enter)
            printf("\tEnter handler at %p\n", curr->enter);
        else
            printf("\tNo enter handler\n");

        if (curr->click)
            printf("\tClick handler at %p\n", curr->click);
        else
            printf("\tNo click handler\n");

        if (curr->leave)
            printf("\tLeave handler at %p\n", curr->leave);
        else
            printf("\tNo leave handler\n");

        if (curr->enter || curr->click || curr->leave) {
            printf("\tTest calls:\n");
            if (curr->enter)
                curr->enter(curr->data);
            if (curr->click)
                curr->click(curr->data);
            if (curr->leave)
                curr->leave(curr->data);
            printf("\tTest calls done.\n");
        }
    }

    return 0;
}

アプリケーションがapp.cで、プラグインplugin-foo.cとがある場合、plugin-bar.cたとえば次のようにコンパイルできます。

gcc -W -Wall -rdynamic app.c -ldl -o app

gcc -W -Wall -fpic -c plugin-foo.c
gcc -shared -Wl,-soname,plugin-foo.so plugin-foo.o -o plugin-foo.so

gcc -W -Wall -fpic -c plugin-bar.c
gcc -shared -Wl,-soname,plugin-bar.so plugin-bar.o -o plugin-bar.so

そして、例えばを使って実行します

./app --help

./app ./plugin-foo.so

./app ./plugin-foo.so ./plugin-bar.so

同じプラグインが複数回定義されている場合、コンストラクターはそのライブラリに対して 1 回だけ実行されることに注意してください。重複登録は行いません。


プラグインとアプリケーション間のインターフェースは完全にあなた次第です。この例では、関数は 1 つだけです。実際のアプリケーションでは、おそらくもっと多いでしょう。アプリケーションは、プラグインがアプリケーション構成を照会するなど、他の機能をエクスポートすることもできます。

優れたインターフェイスを設計することは、まったく別のトピックであり、少なくとも実装に費やすのと同じくらい十分に検討する価値があります。

Plux.NET プラグイン プラットフォームでは、プラグインが独自のスロットをエクスポートすることもできます。この代替アプローチは、多くの点でそれを可能にします。そのうちの 1 つは、プラグイン登録関数をエクスポートすることです。つまり、個々のアイテムではなくプラグインを登録するため のもので、関数ポインターを受け取ります。

int register_plugin(const char *const name,
                    int (*extend)(const char *const, ...));

プラグインがスロットを提供する場合、extend関数ポインターとして独自の登録関数を提供します。アプリケーションは関数もエクスポートします。たとえば、

int plugin_extend(const char *const name, ...);

プラグインが他のプラグインの登録関数を呼び出すために使用できるもの。(メイン アプリケーションでの の実装には、既に登録されplugin_extend()ている適切な関数を検索しextend、それを呼び出すことが含まれます。)

実装に関しては、プラグインがスロットをエクスポートできるようにすると、実装がかなり複雑になります。特に、プラグインによってエクスポートされたスロットは、いつ、どの順序で利用可能になるべきですか? すべての可能なスロットが確実にエクスポートされるようにするために、プラグインをロードする特定の順序はありますか? 循環依存があるとどうなりますか? プラグインは、登録を開始する前に、依存する他のプラグインを指定する必要がありますか?

各プラグインが、独自のスロットをエクスポートせず、メイン アプリケーション スロットにのみプラグインする個別のエンティティである場合、実装の複雑さの大部分を回避できます。

ただし、登録されたアイテムが検査される順序は、おそらく考慮する必要がある詳細です。上記のプログラム例では、項目が登録順序と比較して逆の順序で終了する連結リストを使用しており、登録順序はプラグイン ファイル名がコマンド ラインで最初に指定された順序と同じです。自動的にスキャンされるプラグイン ディレクトリがある場合 (例: // ループを使用) 、opendir()プラグインの登録順序は半ランダムです (ファイルシステムによって異なります。通常、プラグインが追加または削除された場合にのみ変更されます)。readdir()dlopen()closedir()

修正?質問?コメント?

于 2013-09-14T13:37:57.723 に答える