Создание интерфейса плагина

я работаю над приложением, которое должно будет поддерживать архитектуру плагина. Это первый раз, когда я делаю это, поэтому я не совсем уверен, как мне нужно это сделать.

как создать некоторый класс из dll (конструктор в dll)?(с++) предполагает, что мне просто нужно создать класс, состоящий из полностью виртуальных функций, и пусть DLL реализует это в пользовательском классе и возвращает этот пользовательский объект через GetPluginObject() метод или как. Однако,C++ Интерфейс плагина DLL говорит, что этого будет недостаточно, и что правильный (совместимый с несколькими компиляторами) подход потребует следующего:

  • используются только базовые типы данных
  • что-то вроде QueryInterface COM должно быть выставлено, чтобы DLL плагина могла правильно определить, какой интерфейс(Ы) он реализует
  • требуется некоторая форма подсчета ссылок
  • все методы предпочтительно маркировать как нарушением соглашения о стандартном
  • любые структуры должны иметь фиксированное выравнивание

то, что мне нужен плагин, довольно просто: мне просто нужен один массив структур, возвращаемых из одной функции.

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

и затем, на стороне хоста, я бы использовал GetProcAddress на DLL плагина для вызовите DLL QueryInterface, затем используйте указатель QueryInterface возвращается к работе с плагином.

это похоже на много перебор и много хотя некрасивой. Например, я не думаю, что могу правильно передать std:: vector in или out, поэтому я застрял, используя возврат одного элемента для GetEmergeInternalCommandInformation() и функция total-count GetEmergeInternalCommandCount() поэтому я могу перебирать команды плагина один за другим. Есть ли другой способ, которым я могу безопасно получить массив struct в качестве возвращаемого значения не нарушая правил?

кроме того, я совсем не уверен, что правильно определил структуру, как с точки зрения наличия wchar_t массивы (я ограничен одним wchar_ts?) и с точки зрения значения выравнивания.

я также не совсем уверен, как плагин DLL должен реализовать это. Я думаю, что это просто нужно #include заголовок определения интерфейса, а затем создайте класс, наследующий от интерфейса, верно?

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

я тоже не уверен, если мне нужно зарегистрировать этот интерфейс с COM или если я могу просто использовать его. Интерфейс, который я использовал в качестве шаблона, является полноценным com-интерфейсом и зарегистрирован как таковой, но я не знаю, нужно ли мне что-то продвинутое для базовой системы плагинов.

и последнее, но определенно не менее важное - я делаю этот путь более сложным, чем это должно быть?

1 ответов


взгляните на мой проект cppcomponents в https://github.com/jbandela/cppcomponents. Я создал эту библиотеку специально для таких сценариев, как ваш, поскольку я обнаружил, что в настоящее время доступных решений не хватает.

это только заголовок C++11 библиотека, которая работает на Windows и Linux.

для этого требуется довольно совместимый компилятор C++11, такой как MSVC 2013, Gcc 4.7.2 или Clang 3.2

  • он автоматически будет обрабатывать реализацию 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;

затем в ImplementCommandProvider.cpp, который будет скомпилирован в CommandProviderDll.dll файлы

#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, поэтому вы можете использовать ее для коммерческих приложений, если хотите.