【问题标题】:Creating a plugin interface创建插件接口
【发布时间】:2013-11-25 18:00:57
【问题描述】:

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

How to create some class from dll(constructor in dll)?(с++) 建议我只需要创建一个由完全虚函数组成的类,然后让 DLL 在自定义类中实现它,并通过GetPluginObject() 方法或类似方法返回该自定义对象。但是,C++ DLL plugin interface 表示这还不够,正确的(跨多个编译器兼容)方法将需要以下内容:

  • 只有基本数据类型可用
  • 必须公开诸如 COM 的 QueryInterface 之类的东西,这样插件 DLL 才能正确识别它实现的接口
  • 需要某种形式的引用计数
  • 最好将所有方法标记为 stdcall
  • 任何结构都必须有固定的对齐方式

我需要一个插件来做的事情相当简单:我只需要从一个函数返回的一组结构体。

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

然后,在主机端,我会在插件 DLL 上使用 GetProcAddress 来调用 DLL 的 QueryInterface,然后使用指针 QueryInterface 返回以使用插件。

不过,这似乎是 很多 的矫枉过正和丑陋的很多。例如,我认为我不能正确地将 std::vector 传入或传出,所以我被困在使用单项返回 GetEmergeInternalCommandInformation() 和总计数函数 GetEmergeInternalCommandCount() 所以我可以循环插件的命令一一对应。有没有其他方法可以安全地将结构数组作为返回值而不违反规则?

另外,我不确定我是否正确定义了结构,无论是在拥有wchar_t 数组(我是否仅限于单个wchar_ts?)和对齐值方面。

我也不完全确定插件 DLL 应该如何实现这一点。我想它只需要#includeinterface-definition 头然后创建一个继承自该接口的类,对吧?

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

我也不确定我是否需要向 COM 注册这个接口,或者我是否可以使用它。我用作模板的接口是一个成熟的 COM 接口,并已注册,但我不知道我是否需要任何高级插件系统。

最后但同样重要的是 - 我是否让这种方式变得比它需要的更复杂?

【问题讨论】:

  • 您可以考虑的另一种方法是根本不使用 C++ 实现插件,而是使用像 lua 这样的嵌入式脚本语言。这个想法是您将绑定并公开基本构建块或原语到 vm,以便插件可以访问它们。插件本身可以是纯 lua 脚本,也可以是用 C 或 C++ 编写的 lua 扩展模块。通常要求不要求您的应用程序和插件必须用相同的语言编写。
  • 虽然我一直在玩弄使用 Lua 的想法,但我最终将不得不在 C++ 中实现一个插件系统,因为我的应用程序及其未来的插件大量使用了 Windows API .我不确定 Lua 能否在这里站稳脚跟。
  • 看看 LuaJIT 的 ffi——它允许您直接从 lua 轻松绑定 winapi 函数。通常您只需要提供包含感兴趣的 win32 函数声明的预处理标头。
  • 看起来令人印象深刻;我得更深入地看看。我仍然希望能回答我最初的问题,但我为你 +1 了一个有趣的、显然比我最初想象的更可行的替代方案。

标签: c++ plugins dll interface


【解决方案1】:

看看我在https://github.com/jbandela/cppcomponents 的项目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 许可证下发布的,因此您可以根据需要将其用于商业应用程序.

【讨论】:

  • 这正是我所需要的,非常感谢!用你的库设置了我的项目,它运行良好。只是给你一个建议,将你的 VS 专有 #pragmas 包装在 #ifdef _MSC_VER 块中,这样 GCC 和其他非 VS 编译器就不会发出警告。
  • 如果你碰巧有一个使用自定义结构的例子,我肯定会很感激。我预见到将来这个项目需要第二个更复杂的接口,我完全不确定std::tuple 是否足以满足此要求。
  • @computerfreaker 感谢您的反馈。这是有关如何进行自定义结构的链接。我以您的初始命令结构为例。 github.com/jbandela/cppcomponents/wiki/Custom-types
  • @computerfreaker 合并了您关于包装 VS 专有编译指示的建议。 github 中的最新提交反映了这一点。
  • 太棒了,谢谢!我看到你也修复了其他 GCC 特定的问题,非常感谢。设置自定义结构似乎还不错。琐碎的struct 及其其他设置(cross_compiler_interface::cross_conversion&lt;&gt;cross_compiler_interface::type_name_getter&lt;&gt;cppcomponents::uuid_of&lt;&gt;)都在接口定义文件中,对吧?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-02-11
  • 2019-03-31
  • 2021-01-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多