创建插件接口 [英] Creating a plugin interface

查看:107
本文介绍了创建插件接口的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在开发一个应用程序,需要支持一个插件架构。这是我第一次这样做,所以我不完全确定我需要怎么做。



建议我只需要创建一个完全由虚函数组成的类,并让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
class EmergeInternalCommands:public IEmergeInternalCommandProvider
//类定义在这里
/ pre>

我也不知道如果我需要注册这个接口与COM或者如果我可以使用它。我用作模板的接口是一个完整的COM接口,并注册为这样,但我不知道是否需要任何高级的基本插件系统。



最后但绝对不是最不重要的 - 我是否使这种方式比需要的更复杂?

解决方案

请查看我的项目cppcomponents在 https://github.com/jbandela/cppcomponents



这是一个只有头文件的c ++ 11库,适用于Windows和Linux。



它需要一个相当兼容的C ++ 11编译器,如MSVC 2013,Gcc 4.7.2或Clang 3.2



<
  • 当您使用它时,会自动处理引用计数
  • $ b $
    < b
  • 它允许您返回std :: string,vector,tuple以及其他标准类型

  • 它可以处理异常

  • 您可以使用多个编译器,例如,您可以在Visual C ++中编写您的程序,并将您的插件写入GCC



  •  <$> 

    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_ts?) 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屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆