创建插件接口 [英] Creating a plugin interface
问题描述
我正在开发一个应用程序,需要支持一个插件架构。这是我第一次这样做,所以我不完全确定我需要怎么做。
/ a>建议我只需要创建一个完全由虚函数组成的类,并让DLL在自定义类中实现,并通过 GetPluginObject()
方法返回该自定义对象或类似物。但是, C ++ DLL插件界面说这将不够,而且一个正确的(兼容多个编译器)方法将需要以下:
- 只有基本数据类型可用
- 需要某种形式的引用计数
- 任何结构必须给定固定对齐
<
li>所有方法最好标记为stdcall
我需要一个插件做的是相当简单:我只需要一个结构体从一个函数返回的数组。
struct InternalCommand
{
int commandValue;
std :: wstring commandName;
std :: wstring commandHandlerFunctionName; //我计划使用GetProcAddress与提供的函数名称来获取单个命令处理程序
}
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
externC
{
#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
返回使用插件。
这看起来像一个 of overkill and a lot of unugly,though。例如,我不认为我可以正确传递一个std :: vector in或out,所以我坚持使用单项返回 GetEmergeInternalCommandInformation()
和一个总计数函数 GetEmergeInternalCommandCount()
所以我可以循环遍历插件的命令一个接一个。有没有不同的方式,我可以安全地得到一个结构数组作为一个返回值,而不破坏规则?
此外,我不能确定我定义了正确的结构,这两个方面都有 wchar_t
数组(我限制为单个 wchar_t
s?对齐值。
我也不完全确定插件DLL应该如何实现这一点。我认为它只需要 #include
接口定义头,然后创建一个从接口继承的类,对吧?
#includeEmergeInternalCommandInterface.h
/ pre>
class EmergeInternalCommands:public IEmergeInternalCommandProvider
//类定义在这里
我也不知道如果我需要注册这个接口与COM或者如果我可以使用它。我用作模板的接口是一个完整的COM接口,并注册为这样,但我不知道是否需要任何高级的基本插件系统。
最后但绝对不是最不重要的 - 我是否使这种方式比需要的更复杂?
解决方案请查看我的项目cppcomponents在 https://github.com/jbandela/cppcomponents 。
这是一个只有头文件的c ++ 11库,适用于Windows和Linux。
它需要一个相当兼容的C ++ 11编译器,如MSVC 2013,Gcc 4.7.2或Clang 3.2
<
< b
<$> c $ c> #include< cppcomponents / cppcomponents.hpp>
#include< tuple>
#include< vector>
typedef std :: tuple< int,std :: wstring,std :: wstring>命令;
struct ICommandProvider:cppcomponents :: define_interface< cppcomponents :: uuid< 0xf4b4056d,0x37a8,0x4f32,0x9eea,0x03a31ed55dfa>
{
std :: vector< Command> GetEmergeInternalCommands();
CPPCOMPONENTS_CONSTRUCT(ICommandProvider,GetEmergeInternalCommands)
};
inline std :: string CommandProviderId(){returnCommandProvider; }
typedef cppcomponents :: runtime_class< CommandProviderId,cppcomponents :: object_interfaces< ICommandProvider>> CommandProvider_t;
typedef cppcomponents :: use_runtime_class< CommandProvider_t> CommandProvider;
然后在ImplementCommandProvider.cpp中编译成CommandProviderDll.dll
#includeCommandProvider.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,LTest,LTestFunction));
vec.push_back(std :: make_tuple(2,LTest2,LTestFunction2));
vec.push_back(std :: make_tuple(3,LTest3,LTestFunction3));
return vec;
}
};
CPPCOMPONENTS_REGISTER(ImplementCommandProvider)
CPPCOMPONENTS_DEFINE_FACTORY()
如何使用它
#includeCommandProvider.h
#include< iostream>
int main(){
std :: string dllName;
std :: cout<< 输入dll名称,不带.dll扩展名\\\
;
std :: cin>> dllName;
auto p = CommandProvider :: dynamic_creator(dllName,CommandProvider)();
for(auto& c:p.GetEmergeInternalCommands()){
std :: wcout< LValue< std :: get< 0>(c)< LName<< std :: get< 1>(c)<< LFunction< std :: get< 2>(c)< L\\\
;
}
}
这是你将如何从命令行构建
我假设你在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许可证发布,所以你可以使用它如果你想要商业应用。
I'm working on an application that will need to support a plugin architecture. This is the first time I've done this, so I'm not entirely sure how I need to go about it.
How to create some class from dll(constructor in dll)?(с++) suggests I just need to create a class consisting of entirely virtual functions and let the DLL implement that in a custom class and return that custom object via a GetPluginObject()
method or the like. However, C++ DLL plugin interface says that won't be enough, and that a proper (compatible across multiple compilers) approach will require the following:
- Only basic datatypes are usable
- Something like COM's QueryInterface must be exposed so the plugin DLL can properly identify which interface(s) it implements
- Some form of reference counting is required
- All methods are preferably to be marked as stdcall
- Any structs must be given fixed alignment
What I need a plugin to do is fairly simple: I just need one array of structs returned from one 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 restrictions and requirements in the list above, and using another interface from this project as a template, it seems I need to define this in the following way:
#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'd use GetProcAddress
on the plugin DLL to call the DLL's QueryInterface
, then use the pointer QueryInterface
returns to work with the plugin.
This seems like a lot of overkill and a lot of ugly, though. For example, I don't think I can properly pass a std::vector in or out, so I'm stuck using a single-item return for GetEmergeInternalCommandInformation()
and a total-count function GetEmergeInternalCommandCount()
so I can loop through the plugin's commands one by one. Is there a different way I can safely get a struct array as a return value without breaking the rules?
Also, I'm not at all sure I defined the struct correctly, both in terms of having the wchar_t
arrays (am I restricted to single wchar_t
s?) and in terms of the alignment value.
I'm also not entirely certain how the plugin DLL is supposed to implement this. I think it just needs to #include
the interface-definition header and then create a class inheriting from the interface, right?
#include "EmergeInternalCommandInterface.h"
class EmergeInternalCommands : public IEmergeInternalCommandProvider
//class definition goes here
I'm also not certain if I need to register this interface with COM or if I can just use it. The interface I used as a template is a full-fledged COM interface and is registered as such, but I don't know if I need anything that advanced for a basic plugin system.
And last but definitely not least - am I making this way more complicated than it needs to be?
Take a look at my project cppcomponents at https://github.com/jbandela/cppcomponents. I created this library specifically for scenarios such as yours as I found the currently available solutions lacking.
It is a header-only c++11 library that works on Windows and Linux.
It requires a fairly compliant C++11 compiler like MSVC 2013, Gcc 4.7.2, or Clang 3.2
- It will automatically handle implementation of QueryInterface, AddRef, and Release.
- When you use it, reference counting is handled automatically
- It allows you to return std::string, vector, tuple, as well as other standard types
- It can handle exceptions
- You can use multiple 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 that will 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()
Here is how you would 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 would build it from the command line I am assuming you are in the directory with the 3 files and that the MSVC compiler is in your path
This is how to build the main program
cl MainProgram.cpp /I c:\Users\jrb\Source\Repos\cppcomponents /EHsc
This is how to build the Dll
cl ImplementCommandProvider.cpp /I c:\Users\jrb\Source\Repos\cppcomponents /EHsc /link /dll /OUT:CommandProviderDll.dll
Then when you run the program, Enter in CommandProviderDll
for your dllname
If you want to define a custom struct it is possible, and I can help you with it.
The library is lacking documentation currently (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.
这篇关于创建插件接口的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!