Создание интерфейса плагина
я работаю над приложением, которое должно будет поддерживать архитектуру плагина. Это первый раз, когда я делаю это, поэтому я не совсем уверен, как мне нужно это сделать.
как создать некоторый класс из 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_t
s?) и с точки зрения значения выравнивания.
я также не совсем уверен, как плагин 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, поэтому вы можете использовать ее для коммерческих приложений, если хотите.