Creating a plugin interface

I am working on an application that should support the plugin architecture. This is the first time I have done this, so I'm not quite sure how I need to do this.

How to create some class from dll (constructor in dll)? (c ++) suggests that I simply create a class consisting of fully virtual functions, and let the DLL implement this in a user class and return this user object using the GetPluginObject() method or the like. However, the C ++ DLL plugin interface says that this will not be enough, and that for a suitable (multi-compiler compatible) approach, the following will be required:

  • Only basic data types are available.
  • Something like COM QueryInterface should be open, so the DLL plugin can correctly determine which interfaces it implements.
  • Some form of reference counting required
  • All methods are preferably marked as stdcall
  • Any structures must have fixed alignment

I need a plugin to do this quite simply: I only need one array of structures returned from a single function.

 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(); 

Given the limitations and requirements in the above list and using a different interface from this project as a template, it seems I need to define this as follows:

 #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 

And then, on the host side, I would use GetProcAddress in the plugin DLL to call the QueryInterface DLL, and then use the QueryInterface pointer to work with the plugin.

It looks like a lot of kink and a lot of ugly ones. For example, I don’t think I can correctly pass std :: vector to or from, so I got stuck using the return of one element for GetEmergeInternalCommandInformation() and the count-count GetEmergeInternalCommandCount() so that I can GetEmergeInternalCommandCount() over the plugin commands one at a time. Is there any other way that I can safely get a struct array as a return value without breaking a rule?

In addition, I'm not at all sure that I correctly defined the structure, both in terms of the wchar_t array (am I limited to one wchar_t s syntax?) And in terms of alignment value.

I'm also not quite sure how the plugin DLL module is supposed to implement this. I think that you just need to #include interface header and then create a class that inherits from the interface, right?

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

I am also not sure if I need to register this interface using COM or if I can just use it. The interface that I used as a template is a full-featured COM interface and is registered as such, but I don’t know if I need something that was designed for the base plug-in system.

Last but not least, am I making it more complicated than it should be?

+6
source share
1 answer

Take a look at my cppcomponents projects at https://github.com/jbandela/cppcomponents . I created this library specifically for scenarios like yours, as I found solutions currently available.

This is a C ++ 11 library designed for headers only, which runs on Windows and Linux.

This requires a fairly compatible C ++ 11 compiler, such as MSVC 2013, Gcc 4.7.2, or Clang 3.2

  • It automatically processes the implementation of QueryInterface, AddRef, and Release.
  • When you use it, reference counting is processed automatically
  • It allows you to return std :: string, vector, tuple, as well as other standard types
  • It can handle exceptions
  • You can use several compilers, for example, you can write your program in Visual C ++ and write your plugins in GCC

Here is the easiest way to write what you want

First define the interface and plugin in 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; 

Then in ImplementCommandProvider.cpp to be compiled into 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() 

This is how you use it

 #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"; } } 

Here is how you could build it from the command line. I assume that you are in a directory with 3 files and that the MSVC compiler is on your way.

Here's how to create a core program.

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

Here's How to Build a Dll

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

Then, when you run the program, enter CommandProviderDll for your dllname

If you want to define a custom structure, this is possible, and I can help you with it.

The library currently does not have documentation (working on it :(), but I can help you with any questions you have about the library. The library is released under the Boost license, so you can use it for commercial applications if you want.

+4
source

Source: https://habr.com/ru/post/957973/


All Articles