8

プラグイン アーキテクチャをサポートする必要があるアプリケーションに取り組んでいます。これは初めてのことなので、どうすればいいのかよくわかりません。

How to create some class from dll(constructor in dll)?(с++)は、完全に仮想関数で構成されるクラスを作成し、DLL にそれをカスタム クラスに実装させ、そのカスタム オブジェクトをGetPluginObject()メソッドまたはなど。ただし、C++ DLL プラグイン インターフェイスは、それだけでは不十分であり、適切な (複数のコンパイラ間で互換性のある) アプローチには次のものが必要であると述べています。

  • 基本データ型のみ使用可能
  • プラグイン DLL が実装するインターフェイスを適切に識別できるように、COM の QueryInterface のようなものを公開する必要があります。
  • 何らかの形式の参照カウントが必要です
  • すべてのメソッドは stdcall としてマークすることが望ましい
  • 構造体には固定アラインメントを指定する必要があります

プラグインに必要なことは非常に単純です。必要なのは、1 つの関数から返される構造体の 1 つの配列だけです。

struct InternalCommand
{
    int commandValue;
    std::wstring commandName;
    std::wstring commandHandlerFunctionName; //I'm planning on using GetProcAddress with the provided function name to get the individual command handler
}

std::vector<InternalCommand> GetEmergeInternalCommands();

上記のリストの制限と要件を考慮し、このプロジェクトの別のインターフェイスをテンプレートとして使用すると、次の方法でこれを定義する必要があるようです。

#define MAX_LINE_LENGTH 4096

#ifdef __GNUC__
#define ALIGNOF(type) __alignof__(type)
#else
#define ALIGNOF(type) __alignof(type)
#endif

#ifdef __GNUC__
#define ALIGNED(size) __attribute__((aligned (size)))
#else
#define ALIGNED(size) __declspec(align(size))
#endif

#include <windows.h>

// {b78285af-c62f-4cff-9e15-f790a4a219ee}
const IID IID_IEmergeInternalCommand = {0xB78285AF, 0xC62F, 0x4CFF, {0x9E, 0x15, 0xF7, 0x90, 0xA4, 0xA2, 0x19, 0xEE}};

#ifdef __cplusplus
extern "C"
{
#endif

struct ALIGNED((ALIGNOF(int) + ALIGNOF(wchar_t) + ALIGNOF(wchar_t))) EmergeInternalCommandInformation
{
  int commandValue;
  wchar_t commandName[MAX_LINE_LENGTH];
  wchar_t commandHandlerFunctionName[MAX_LINE_LENGTH];
};

#undef INTERFACE
#define INTERFACE IEmergeInternalCommandProvider
DECLARE_INTERFACE_(IEmergeInternalCommandProvider, IUnknown)
{
  STDMETHOD(QueryInterface)(THIS_ REFIID, LPVOID*) PURE;
  STDMETHOD_(ULONG, AddRef)(THIS) PURE;
  STDMETHOD_(ULONG, Release)(THIS) PURE;

  STDMETHOD_(int, GetEmergeInternalCommandCount)(THIS) PURE;
  STDMETHOD_(EmergeInternalCommandInformation, GetEmergeInternalCommandInformation)(THIS_ int) PURE;
};
#undef INTERFACE
typedef IEmergeInternalCommandProvider* LPEMERGEINTERNALCOMMANDPROVIDER;

#ifdef __cplusplus
}
#endif

次に、ホスト側では、プラグイン DLL で を使用GetProcAddressして DLL を呼び出し、QueryInterfaceポインタQueryInterfaceリターンを使用してプラグインを操作します。

ただし、これはやり過ぎで醜いように思えます。たとえば、std::vector を適切に入出力できるとは思えないため、プラグインのコマンドを 1 つずつループできるように、単一項目の returnと total-count 関数を使用して行き詰まっています。 . ルールを破ることなく構造体配列を戻り値として安全に取得できる別の方法はありますか?GetEmergeInternalCommandInformation()GetEmergeInternalCommandCount()

wchar_tまた、配列を持つという点 (単一wchar_tの s に制限されていますか?) とアライメント値の両方に関して、構造体を正しく定義したかどうかはまったくわかりません。

また、プラグイン DLL がこれをどのように実装するのか、完全にはわかりません。#includeインターフェイス定義ヘッダーが必要で、インターフェイスから継承するクラスを作成するだけだと思いますよね?

#include "EmergeInternalCommandInterface.h"
class EmergeInternalCommands : public IEmergeInternalCommandProvider
//class definition goes here

また、このインターフェイスを COM に登録する必要があるのか​​、それとも単に使用できるのかについてもわかりません。テンプレートとして使用したインターフェイスは本格的な COM インターフェイスであり、そのように登録されていますが、基本的なプラグイン システムに高度なものが必要かどうかはわかりません。

そして最後になりましたが、間違いなく重要なことです-私はこの方法を必要以上に複雑にしていますか?

4

1 に答える 1

5

https://github.com/jbandela/cppcomponentsで私のプロジェクト cppcomponents を見てください。現在利用可能なソリューションが不足していることがわかったので、このライブラリをあなたのようなシナリオ専用に作成しました。

これは、Windows および Linux で動作するヘッダーのみの C++11 ライブラリです。

MSVC 2013、Gcc 4.7.2、Clang 3.2 など、かなり準拠した C++11 コンパイラが必要です。

  • QueryInterface、AddRef、および Release の実装を自動的に処理します。
  • 使用すると、参照カウントが自動的に処理されます
  • std::string、vector、tuple、およびその他の標準型を返すことができます
  • 例外を処理できる
  • 複数のコンパイラを使用できます。たとえば、Visual C++ でプログラムを記述し、GCC でプラグインを記述できます。

これがあなたが望むものを書く最も簡単な方法です

最初に CommandProvider.h でインターフェイスとプラグインを定義します

#include <cppcomponents/cppcomponents.hpp>
#include <tuple>
#include <vector>

typedef std::tuple<int, std::wstring, std::wstring> Command;

struct ICommandProvider:cppcomponents::define_interface<cppcomponents::uuid<0xf4b4056d, 0x37a8, 0x4f32, 0x9eea, 0x03a31ed55dfa>>
{
    std::vector<Command>GetEmergeInternalCommands();

    CPPCOMPONENTS_CONSTRUCT(ICommandProvider, GetEmergeInternalCommands)
};

inline std::string CommandProviderId(){ return "CommandProvider"; }
typedef cppcomponents::runtime_class<CommandProviderId, cppcomponents::object_interfaces<ICommandProvider>> CommandProvider_t;
typedef cppcomponents::use_runtime_class<CommandProvider_t> CommandProvider;

次に、CommandProviderDll.dll にコンパイルされる ImplementCommandProvider.cpp で

#include "CommandProvider.h"

struct ImplementCommandProvider :cppcomponents::implement_runtime_class<ImplementCommandProvider, CommandProvider_t>
{
ImplementCommandProvider(){}


std::vector<Command>GetEmergeInternalCommands(){
    std::vector<Command> vec;
    vec.push_back(std::make_tuple(1, L"Test", L"TestFunction"));
    vec.push_back(std::make_tuple(2, L"Test2", L"TestFunction2"));
    vec.push_back(std::make_tuple(3, L"Test3", L"TestFunction3"));

    return vec;
}


};

CPPCOMPONENTS_REGISTER(ImplementCommandProvider)
CPPCOMPONENTS_DEFINE_FACTORY()

使用方法は次のとおりです

#include "CommandProvider.h"
#include <iostream>


int main(){

    std::string dllName;

    std::cout << "Enter dll name without the .dll extension\n";
    std::cin >> dllName;

    auto p = CommandProvider::dynamic_creator(dllName, "CommandProvider")();

    for (auto& c : p.GetEmergeInternalCommands()){
        std::wcout << L"Value " << std::get<0>(c) << L" Name " << std::get<1>(c) << L" Function " << std::get<2>(c) << L"\n";

    }


}

コマンドラインからビルドする方法は次のとおりです。3つのファイルがあるディレクトリにいて、MSVCコンパイラがパスにあると仮定しています

これがメインプログラムのビルド方法です

cl MainProgram.cpp /I c:\Users\jrb\Source\Repos\cppcomponents /EHsc

これはDLLをビルドする方法です

cl ImplementCommandProvider.cpp  /I c:\Users\jrb\Source\Repos\cppcomponents /EHsc /link /dll /OUT:CommandProviderDll.dll

次に、プログラムを実行するときにCommandProviderDll、dllname を入力します。

カスタム構造体を定義したい場合は可能です。私がお手伝いします。

現在、ライブラリにはドキュメントが不足しています (作業中 :( )。ただし、ライブラリについて質問がある場合は、私がお手伝いします。ライブラリは Boost License の下でリリースされているため、必要に応じて商用アプリケーションに使用できます。

于 2013-11-13T14:12:17.907 に答える