8

私は非常にオープンなプラグイン フレームワークを C++ で作成しようとしていますが、その方法を思いついたように思えますが、しつこい考えが、私がやっていることには非常に、非常に間違っていることがあると言い続けています。 、動作しないか、問題が発生します。

私のフレームワークの設計は、各プラグインのinit関数を呼び出すカーネルで構成されています。その後、init 関数は向きを変え、カーネルのregisterPluginandregisterFunctionを使用して一意の ID を取得し、プラグインがその ID を使用してアクセスできるようにする各関数をそれぞれ登録します。

関数 registerPlugin は一意の ID を返します。関数 registerFunction は、次のように、その id、関数名、および汎用関数ポインターを受け取ります。

bool registerFunction(int plugin_id, string function_name, plugin_function func){}

plugin_function の場所

typedef void (*plugin_function)();

次に、カーネルは関数ポインターを取得し、それを と を使用してマップに配置しfunction_nameますplugin_id。関数を登録するすべてのプラグインは、関数を type にキャストする必要がありますplugin_function

関数を取得するために、別のプラグインがカーネルの

plugin_function getFunction(string plugin_name, string function_name);

次に、そのプラグインplugin_functionを元の型にキャストして、使用できるようにする必要があります。.hプラグインが利用可能にするすべての機能の概要を説明するファイルにアクセスすることで、(理論的には) 正しい型が何であるかを認識します。プラグインは、動的ライブラリとして実装されます。

これは、異なるプラグインが相互に接続できるようにするというタスクを達成するためのスマートな方法ですか? それとも、これはクレイジーで本当にひどいプログラミング手法ですか? もしそうなら、これを達成するための正しい方法を教えてください。

編集: 説明が必要な場合は、質問してください。提供されます。

4

7 に答える 7

19

関数ポインターは奇妙な生き物です。void*それらは必ずしもデータ ポインターと同じサイズであるとは限らないため、安全にキャストしたり戻したりすることはできません。ただし、C++ (および C) 仕様では、任意の関数ポインターを別の関数ポインター型に安全にキャストできます (ただし、定義済みの動作が必要な場合は、呼び出す前に、後で以前の型にキャストする必要があります)。これは、任意のデータ ポインタを安全にキャストしvoid*たり戻したりする機能に似ています。

メソッドへのポインターは、非常に厄介な場所です。メソッド ポインターは、アプリケーションが 32 ビットであるか 64 ビットであるかなど、コンパイラーによっては、通常の関数ポインターよりも大きい場合があります。しかし、さらに興味深いのは、同じコンパイラ/プラットフォーム、すべてのメソッド ポインターが同じサイズであるとは限りません。仮想関数へのメソッド ポインターは、通常のメソッド ポインターよりも大きい場合があります。多重継承 (ダイヤモンド パターンの仮想継承など) が関係している場合、メソッド ポインターはさらに大きくなる可能性があります。これは、コンパイラとプラットフォームによっても異なります。これは、特にヒープにメモリを割り当てずに関数オブジェクト (任意のメソッドとフリー関数をラップする) を作成することが難しい理由でもあります (テンプレート sorceryを使用することで可能です)。

そのため、インターフェイスで関数ポインターを使用すると、プラグインの作成者が同じコンパイラーを使用していても、メソッド ポインターをフレームワークに戻すことは現実的ではなくなります。これは許容できる制約かもしれません。これについては後で詳しく説明します。

関数ポインタがコンパイラ間で同じサイズであるという保証はないため、関数ポインタを登録することにより、プラグインの作成者を、コンパイラと同じサイズの関数ポインタを実装するコンパイラに限定することになります。関数ポインターのサイズはコンパイラーのバージョン間で安定している傾向があるため (複数のコンパイラーで同じになる場合もあります)、これは実際には必ずしもそれほど悪いことではありません。

関数ポインターが指す関数を呼び出すときに、実際の問題が発生し始めます。関数の真の署名がわからない場合、関数を安全に呼び出すことはまったくできません (「機能しない」からセグメンテーション違反に至るまで、悪い結果が得られます) したがって、プラグインの作成者は、voidパラメーターをとらない関数のみを登録するようにさらに制限されます。

さらに悪いことに、関数呼び出しがアセンブラー レベルで実際に機能する方法は、シグネチャと関数ポインターのサイズだけに依存するわけではありません。呼び出し規則、例外の処理方法 (例外がスローされたときにスタックを適切に巻き戻す必要がある)、関数ポインターのバイトの実際の解釈 (データ ポインターよりも大きい場合、余分なバイトはどうするか) もあります。どのような順序で?) この時点で、プラグインの作成者は、あなたと同じコンパイラ (およびバージョン!) を使用することにほとんど制限されており、呼び出し規約と例外処理オプションを一致させるように注意する必要があります (たとえば、例外処理などの MSVC++ コンパイラを使用)。オプションで明示的に有効にするだけ/EHscです)、定義した正確なシグネチャを持つ通常の関数ポインターのみを使用します。

これまでのすべての制限は、多少の制限があるとしても、合理的であると見なすことができます。しかし、まだ終わりではありません。

std::stringただし、同じコンパイラ (およびバージョン) を使用しても、STL を制御するいくつかの異なるフラグ/マクロがあるため、(または STL のほぼすべての部分)を投入すると、事態はさらに悪化します。これらのフラグは、文字列オブジェクトを表すバイトのサイズと意味に影響を与える可能性があります。実際には、2 つの異なるstruct 宣言が別々のファイルにあり、それぞれが同じ名前であり、それらが交換可能であることを望んでいるようなものです。明らかに、これは機能しません。フラグの例は_HAS_ITERATOR_DEBUGGING. これらのオプションは、デバッグ モードとリリース モードの間でも変更できることに注意してください。これらのタイプのエラーは、常にすぐに/一貫して現れるとは限らず、追跡するのが非常に難しい場合があります。

newまた、あるプロジェクトでは別のプロジェクトとは異なる方法で定義されnewている可能性があるため (たとえば、オーバーロードされている可能性があるため)、モジュール間の動的メモリ管理には十分注意する必要があります。削除するとき、仮想デストラクタを持つインターフェイスへのポインタがある場合があります。つまり、オブジェクトvtableを適切に処理するために必要deleteであり、さまざまなコンパイラがすべて異なる方法で実装しvtableます。一般に、オブジェクトを割り当てるモジュールを割り当て解除するモジュールが必要です。より具体的には、オブジェクトの割り当てを解除するコードを、オブジェクトを割り当てたコードとまったく同じ条件下でコンパイルする必要があります。これが一つの理由ですstd::shared_ptr構築時に「deleter」引数を取ることができます-同じコンパイラとフラグ(shared_ptrモジュール間で s を共有する唯一の保証された安全な方法)を使用しても、どこでも同じnewではない可能性があるため、破壊される可能性があります。デリーターを使用すると、共有ポインターを作成するコードが、共有ポインターが最終的にどのように破棄されるかを制御します。(私はこの段落を適切な方法で挿入しました。モジュールの境界を越えてオブジェクトを共有しているようには見えません。)deleteshared_ptr

これはすべて、C++ に標準バイナリ インターフェイス ( ABI ) がないためです。それは自由に参加できる場所であり、自分の足を撃つことは非常に簡単です (時には気付かずに)。

それで、希望はありますか?もちろんです!代わりに C API をプラグインに公開し、プラグインにも C API を公開させることができます。C API は事実上あらゆる言語と相互運用できるため、これは非常に優れています。例外がプラグイン関数の上にバブルアップしないことを確認することを除いて (これは作成者の懸念です)、例外について心配する必要はありません。また、コンパイラ/オプションに関係なく安定しています (STL コンテナーを渡さないと仮定します)。など)。cdecl宣言された関数のデフォルトである標準呼び出し規約 ( ) は 1 つだけですextern "C"void*、実際には、同じプラットフォーム上のすべてのコンパイラで同じになります (たとえば、x64 では 8 バイト)。

あなた (およびプラグイン作成者) は、2 つの間のすべての外部通信が C API を使用する (つまり、相互運用のために C モジュールのふりをする) 限り、C++ でコードを書くことができます。

C 関数ポインタも実際にはコンパイラ間で互換性がある可能性がありますが、これに依存したくない場合は、プラグインにアドレスの代わりに関数( ) を登録させることができます。 Windows の場合 (同様に、Linux と Mac OS X には と があります)。これは、で宣言された関数の名前マングリングが無効になっているため機能します。const char*LoadLibraryGetProcAddressdlopendlsymextern "C"

登録された関数を単一のプロトタイプ型に制限する直接的な方法はないことに注意してください (そうしないと、既に述べたように、適切に呼び出すことができません)。プラグイン関数に特定のパラメーターを与える (または値を取得する) 必要がある場合は、異なるプロトタイプで異なる関数を個別に登録して呼び出す必要があります (ただし、すべての関数ポインターを共通の関数ポインターに折りたたむことができます)。内部で入力し、最後にのみキャストバックします)。

最後に、メソッド ポインターを直接サポートすることはできませんが (C API には存在しませんが、C++ API でも可変サイズであるため、簡単に格納できません)、プラグインが「user- data" 関数を登録するときの不透明なポインター。これは、関数が呼び出されるたびに関数に渡されます。これにより、プラグインの作成者は、メソッドの周りに関数ラッパーを記述し、メソッドを適用するオブジェクトをユーザー データ パラメーターに格納する簡単な方法が得られます。user-data パラメーターは、プラグインの作成者が必要とする他のものにも使用できます。これにより、プラグイン システムとのインターフェースと拡張がはるかに簡単になります。もう 1 つの使用例は、ラッパーとユーザー データに格納された追加の引数を使用して、異なる関数プロトタイプ間で適応させることです。

これらの提案は、次のようなコードにつながります (Windows の場合 -- コードは他のプラットフォームでも非常に似ています)。

// Shared header
extern "C" {
    typedef void (*plugin_function)(void*);

    bool registerFunction(int plugin_id, const char* function_name, void* user_data);
}

// Your plugin registration code
hModule = LoadLibrary(pluginDLLPath);

// Your plugin function registration code
auto pluginFunc = (plugin_function)GetProcAddress(hModule, function_name);
// Store pluginFunc and user_data in a map keyed to function_name

// Calling a plugin function
pluginFunc(user_data);

// Declaring a plugin function
extern "C" void aPluginFunction(void*);
class Foo { void doSomething() { } };

// Defining a plugin function
void aPluginFunction(void* user_data)
{
    static_cast<Foo*>(user_data)->doSomething();
}

この返信が長くなって申し訳ありません。そのほとんどは、「C++ 標準は相互運用性に拡張されていません。少なくとも事実上の標準があるため、代わりに C を使用してください」と要約できます。


注: プラグインがまったく同じ状況でコンパイルされるという前提の下で、通常の C++ API (関数ポインターまたはインターフェイスなどを使用して) を設計するのが最も簡単な場合があります。これは、すべてのプラグインを自分で開発する必要がある場合 (つまり、DLL がプロジェクトのコアの一部である場合) には妥当です。これは、プロジェクトがオープンソースの場合にも機能します。この場合、誰もがプロジェクトとプラグインをコンパイルするまとまりのある環境を個別に選択できますが、ソース コード以外でプラグインを配布することが難しくなります。


更新: コメントで ern0 が指摘したように、モジュールの相互運用の詳細を (C API を介して) 抽象化して、メイン プロジェクトとプラグインの両方がより単純な C++ API を処理するようにすることができます。以下は、そのような実装の概要です。

// iplugin.h -- shared between the project and all the plugins
class IPlugin {
public:
    virtual void register() { }
    virtual void initialize() = 0;

    // Your application-specific functionality here:
    virtual void onCheeseburgerEatenEvent() { }
};

// C API:
extern "C" {
    // Returns the number of plugins in this module
    int getPluginCount();

    // Called to register the nth plugin of this module.
    // A user-data pointer is expected in return (may be null).
    void* registerPlugin(int pluginIndex);

    // Called to initialize the nth plugin of this module
    void initializePlugin(int pluginIndex, void* userData);

    void onCheeseBurgerEatenEvent(int pluginIndex, void* userData);
}


// pluginimplementation.h -- plugin authors inherit from this abstract base class
#include "iplugin.h"
class PluginImplementation {
public:
    PluginImplementation();
};


// pluginimplementation.cpp -- implements C API of plugin too
#include <vector>

struct LocalPluginRegistry {
    static std::vector<PluginImplementation*> plugins;
};

PluginImplementation::PluginImplementation() {
    LocalPluginRegistry::plugins.push_back(this);
}

extern "C" {
    int getPluginCount() {
        return static_cast<int>(LocalPluginRegistry::plugins.size());
    }

    void* registerPlugin(int pluginIndex) {
        auto plugin = LocalPluginRegistry::plugins[pluginIndex];
        plugin->register();
        return (void*)plugin;
    }

    void initializePlugin(int pluginIndex, void* userData) {
        auto plugin = static_cast<PluginImplementation*>(userData);
        plugin->initialize();
    }

    void onCheeseBurgerEatenEvent(int pluginIndex, void* userData) {
        auto plugin = static_cast<PluginImplementation*>(userData);
        plugin->onCheeseBurgerEatenEvent();
    }
}


// To declare a plugin in the DLL, just make a static instance:
class SomePlugin : public PluginImplementation {
    virtual void initialize() {  }
};
SomePlugin plugin;    // Will be created when the DLL is first loaded by a process


// plugin.h -- part of the main project source only
#include "iplugin.h"
#include <string>
#include <vector>
#include <windows.h>

class PluginRegistry;

class Plugin : public IPlugin {
public:
    Plugin(PluginRegistry* registry, int index, int moduleIndex)
        : registry(registry), index(index), moduleIndex(moduleIndex)
    {
    }

    virtual void register();
    virtual void initialize();

    virtual void onCheeseBurgerEatenEvent();

private:
    PluginRegistry* registry;
    int index;
    int moduleIndex;
    void* userData;
};

class PluginRegistry {
public:
    registerPluginsInModule(std::string const& modulePath);
    ~PluginRegistry();

public:
    std::vector<Plugin*> plugins;

private:
    extern "C" {
        typedef int (*getPluginCountFunc)();
        typedef void* (*registerPluginFunc)(int);
        typedef void (*initializePluginFunc)(int, void*);
        typedef void (*onCheeseBurgerEatenEventFunc)(int, void*);
    }

    struct Module {
        getPluginCountFunc getPluginCount;
        registerPluginFunc registerPlugin;
        initializePluginFunc initializePlugin;
        onCheeseBurgerEatenEventFunc onCheeseBurgerEatenEvent;

        HMODULE handle;
    };

    friend class Plugin;
    std::vector<Module> registeredModules;
}


// plugin.cpp
void Plugin::register() {
    auto func = registry->registeredModules[moduleIndex].registerPlugin;
    userData = func(index);
}

void Plugin::initialize() {
    auto func = registry->registeredModules[moduleIndex].initializePlugin;
    func(index, userData);
}

void Plugin::onCheeseBurgerEatenEvent() {
    auto func = registry->registeredModules[moduleIndex].onCheeseBurgerEatenEvent;
    func(index, userData);
}

PluginRegistry::registerPluginsInModule(std::string const& modulePath) {
    // For Windows:
    HMODULE handle = LoadLibrary(modulePath.c_str());

    Module module;
    module.handle = handle;
    module.getPluginCount = (getPluginCountFunc)GetProcAddr(handle, "getPluginCount");
    module.registerPlugin = (registerPluginFunc)GetProcAddr(handle, "registerPlugin");
    module.initializePlugin = (initializePluginFunc)GetProcAddr(handle, "initializePlugin");
    module.onCheeseBurgerEatenEvent = (onCheeseBurgerEatenEventFunc)GetProcAddr(handle, "onCheeseBurgerEatenEvent");

    int moduleIndex = registeredModules.size();
    registeredModules.push_back(module);

    int pluginCount = module.getPluginCount();
    for (int i = 0; i < pluginCount; ++i) {
        auto plugin = new Plugin(this, i, moduleIndex);
        plugins.push_back(plugin);
    }
}

PluginRegistry::~PluginRegistry() {
    for (auto it = plugins.begin(); it != plugins.end(); ++it) {
        delete *it;
    }

    for (auto it = registeredModules.begin(); it != registeredModules.end(); ++it) {
        FreeLibrary(it->handle);
    }
}



// When discovering plugins (e.g. by loading all DLLs in a "plugins" folder):
PluginRegistry registry;
registry.registerPluginsInModule("plugins/cheeseburgerwatcher.dll");
for (auto it = registry.plugins.begin(); it != registry.plugins.end(); ++it) {
    (*it)->register();
}
for (auto it = registry.plugins.begin(); it != registry.plugins.end(); ++it) {
    (*it)->initialize();
}

// And then, when a cheeseburger is actually eaten:
for (auto it = registry.plugins.begin(); it != registry.plugins.end(); ++it) {
    auto plugin = *it;
    plugin->onCheeseBurgerEatenEvent();
}

これには、互換性のために C API を使用するという利点がありますが、C++ で記述されたプラグイン (および C++ であるメイン プロジェクト コード) に対してより高いレベルの抽象化も提供します。複数のプラグインを 1 つの DLL で定義できることに注意してください。マクロを使用して関数名の重複を一部除去することもできますが、この単純な例ではそうしませんでした。


ちなみに、これはすべて、相互依存関係のないプラグインを前提としています。プラグイン A がプラグイン B に影響を与える (または必要とする) 場合、必要に応じて依存関係を注入/構築するための安全な方法を考案する必要があります。プラグインがロードされる (または初期化される) 順序。その場合は、2 段階のプロセスがうまく機能します。すべてのプラグインをロードして登録します。各プラグインの登録時に、提供するサービスを登録させます。初期化中に、登録されたサービス テーブルを参照して、必要に応じて要求されたサービスを構築します。これにより、プラグインが登録または初期化される順序に関係なく、すべてのプラグインによって提供されるすべてのサービスが、使用が試行される前に登録されます。

于 2013-02-11T07:10:50.840 に答える
2

あなたが取ったアプローチは一般的には正気ですが、いくつかの改善点があると思います.

  • カーネルは、プラグインと関数の登録のために、従来の呼び出し規則 (cdecl、または Windows の場合は stdcall) を使用して C 関数をエクスポートする必要があります。C++ 関数を使用する場合、C++ 関数名マングリング、STL 実装、呼び出し規約などの多くのことがコンパイラ固有であるため、すべてのプラグイン作成者に、使用するのと同じコンパイラとコンパイラ バージョンを使用するように強制することになります。

  • プラグインは、カーネルなどの C 関数のみをエクスポートする必要があります。

  • の定義から、getFunction各プラグインには名前があり、他のプラグインがその機能を取得するために使用できるようです。これは安全な方法ではありません。2 人の開発者が同じ名前で 2 つの異なるプラグインを作成できるため、プラグインが別のプラグインを名前で要求すると、予期したものとは異なるプラグインを取得する可能性があります。より良い解決策は、プラグインにパブリックGUIDを持たせることです。この GUID は、他のプラグインが参照できるように、各プラグインのヘッダー ファイルに表示できます。

  • バージョン管理を実装していません。理想的には、将来必ずカーネルを変更するため、カーネルをバージョン管理する必要があります。プラグインがカーネルに登録されると、コンパイルされたカーネル API のバージョンが渡されます。その後、カーネルはプラグインをロードできるかどうかを判断できます。たとえば、カーネル バージョン 1 がカーネル バージョン 2 を必要とするプラグインの登録要求を受信した場合、問題に対処する最善の方法は、プラグインのロードを許可しないことです。古いバージョン。逆のケースも可能です。カーネル v2 は、カーネル v1 用に作成されたプラグインをロードする場合としない場合があり、許可する場合は、古い API に適応する必要がある場合があります。

  • プラグインが別のプラグインを見つけてその関数を直接呼び出すことができるというアイデアが好きかどうかはわかりません。これはカプセル化を破るためです。他のプラグインが名前や GUID で他のプラグインをアドレス指定するのではなく、機能によって必要なサービスを見つけることができるように、プラグインがその機能をカーネルに通知する方が良いように思えます。

  • メモリを割り当てるプラグインは、そのメモリの割り当て解除機能を提供する必要があることに注意してください。各プラグインは異なるランタイム ライブラリを使用している可能性があるため、プラグインによって割り当てられたメモリは、他のプラグインやカーネルに認識されない場合があります。同じモジュールで割り当てと割り当て解除を行うと、問題が回避されます。

于 2013-02-17T08:42:26.017 に答える
1

C++にはABIがありません。したがって、実行したいことには制限があります。プラグインとフレームワークは、同じOS内の同じパラメーターを使用して、同じコンパイラーとリンカーによってコンパイルおよびリンクする必要があります。フレームワーク用に開発された各プラグインは、異なるOS上の異なるコンパイラを対象とする多くのバージョンを準備する必要があるため、成果がバイナリ配布の形式での相互運用である場合、それは無意味です。したがって、ソースコードの配布はこれよりも実用的であり、それがGNUの方法です(srcをダウンロードし、構成して作成します)

COMが選ばれましたが、複雑すぎて時代遅れです。または、.NetランタイムでC++を管理します。しかし、それらはmsos上にのみあります。普遍的な解決策が必要な場合は、別の言語に変更することをお勧めします。

于 2013-02-11T05:16:30.657 に答える
1

jean が言及しているように、標準の C++ ABI と標準の名前マングリング規則がないため、同じコンパイラとリンカーでコンパイルする必要があります。共有ライブラリ/dll の種類のプラグインが必要な場合は、C っぽいものを使用する必要があります。

すべてが同じコンパイラとリンカーでコンパイルされる場合は、std::function も考慮する必要があります。

typedef std::function<void ()> plugin_function;

std::map<std::string, plugin_function> fncMap;

void register_func(std::string name, plugin_function fnc)
{
   fncMap[name] = fnc;
}

void call(std::string name)
{
   auto it = fncMap.find(name);
   if (it != fncMap.end())
      (it->second)();   // it->second is a function object
}


///////////////

void func()
{
   std::cout << "plain" << std::endl;
}

class T
{
public:
   void method()
   {
     std::cout << "method" << std::endl;
   }

   void method2(int i)
   {
     std::cout << "method2 : " << i << std::endl;
   }
};


T t; // of course "t" needs to outlive the map, you could just as well use shared_ptr

register_func("plain", func);
register_func("method", std::bind(&T::method, &t));
register_func("method2_5", std::bind(&T::method2, &t, 5));
register_func("method2_15", std::bind(&T::method2, &t, 15));

call("plain");
call("method");
call("method2_5");
call("method2_15");

引数を取るプラグイン関数を使用することもできます。これは std::bind のプレースホルダーを使用しますが、すぐに、boost::bind の後ろにいくらか欠けていることがわかります。Boost bind には、優れたドキュメントと例があります。

于 2013-02-12T20:15:54.553 に答える
0

上記のすべての優れた回答を踏まえて、このプラクティスは実際にはかなり広く配布されていることを付け加えておきます。私の実践では、商用プロジェクトとフリーウェア/オープンソースプロジェクトの両方でそれを見てきました。

つまり、そうです、それは優れた実績のあるアーキテクチャです。

于 2013-02-16T21:14:37.337 に答える
0

これをすべきではない理由はありません。C ++では、このスタイルのポインターを使用するのが最適です。これは単なるポインターだからです。私は、通常のポインターのように関数ポインターを作成しないのと同じくらい脳死するような人気のあるコンパイラーを知りません。誰かがこんなに恐ろしいことをするのは理にかなったことではありません。

Vstプラグイン標準も同様に動作します。.dllの関数ポインタを使用するだけで、クラスを直接呼び出す方法はありません。Vstは非常に人気のある標準であり、Windowsでは、パスカルベースでC++とは関係のないDelphiを含むVstプラグインを実行するためにほぼすべてのコンパイラを使用します。

だから私はあなたが個人的に提案することを正確に行います。一般的なよく知られているプラ​​グインの場合、文字列名ではなく整数インデックスを使用します。これにより、はるかに高速に検索できます。

別の方法はインターフェースを使用することですが、あなたの考えがすでに関数ポインターに基づいているかどうかはわかりません。

インターフェイスを使用する場合、他の言語から関数を呼び出すのはそれほど簡単ではありません。Delphiから実行できますが、.NETについてはどうでしょうか。

関数ポインタスタイルの提案を使用すると、たとえば.NETを使用してプラグインの1つを作成できます。明らかに、Monoをロードするには、プログラムでMonoをホストする必要がありますが、架空の目的のために、Monoの単純さを示しています。

さらに、インターフェイスを使用する場合は、参照カウントを行う必要がありますが、これは厄介です。あなたが提案するように関数ポインタにロジックを貼り付けてから、いくつかのC ++クラスでコントロールをラップして、呼び出しなどを行います。次に、他の人がDelphi Pascal、Free Pascal、C、他のC++コンパイラなどの他の言語でプラグインを作成できます...

ただし、いつものように、何をするにしても、コンパイラ間の例外処理は引き続き問題になるため、エラー処理について考える必要があります。最善の方法は、プラグイン独自のメソッドが独自のプラグイン例外をキャッチし、カーネルなどにエラーコードを返すことです...

于 2013-02-15T17:51:44.230 に答える
0

関数を手動で登録する必要はありません。本当に?本当。

使用できるのは、プラグイン インターフェースのプロキシ実装です。各関数は、オンデマンドで透過的に共有ライブラリからオリジナルをロードし、それを呼び出します。そのインターフェイス定義のプロキシ オブジェクトに到達した人は誰でも関数を呼び出すことができます。それらはオンデマンドでロードされます。

プラグインがシングルトンの場合、手動バインディングはまったく必要ありません (それ以外の場合は、最初に正しいインスタンスを選択する必要があります)。

新しいプラグインの開発者にとってのアイデアは、最初にインターフェイスを記述し、次に共有ライブラリの実装用のスタブを生成するジェネレーターを用意し、さらに同じ署名を持つがオンデマンドで自動ロードするプラグイン プロキシ クラスを用意することです。その後、クライアント ソフトウェアで使用されます。どちらも同じインターフェースを満たす必要があります (C++ では純粋な抽象クラス)。

于 2013-02-17T15:04:41.327 に答える