別のアプローチを次に示します。
アプリケーション自体は、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 つの関数スロット ( enter
、click
、および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()
修正?質問?コメント?