创建插件界面

cf *_*ica 6 c++ dll plugins interface

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

如何从dll(dll中的构造函数)创建一些类?(с++)建议我只需要创建一个由完全虚函数组成的类,让DLL在自定义类中实现它并通过GetPluginObject()方法返回该自定义对象或类似.但是,C++ DLL插件接口表示这还不够,并且正确(跨多个编译器兼容)方法将需要以下内容:

  • 只有基本数据类型可用
  • 必须公开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();
Run Code Online (Sandbox Code Playgroud)

鉴于上面列表中的限制和要求,并使用此项目中的另一个接口作为模板,我似乎需要通过以下方式定义它:

#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
Run Code Online (Sandbox Code Playgroud)

然后,在主机端,我将使用GetProcAddress插件DLL来调用DLL QueryInterface,然后使用指针QueryInterface返回与插件一起工作.

这似乎是一个很多矫枉过正的和大量的丑恶,虽然.例如,我不认为我可以正确地传入std :: vector,因此我坚持使用单项返回GetEmergeInternalCommandInformation()和总计数函数,GetEmergeInternalCommandCount()因此我可以逐个遍历插件的命令.有没有不同的方法我可以安全地获取结构数组作为返回值而不违反规则?

另外,我完全不确定我是否正确地定义了结构,包括wchar_t数组(我是否仅限于单个wchar_t?)和对齐值.

我也不完全确定插件DLL应该如何实现它.我认为它只需要#include接口定义头,然后创建一个继承自接口的类,对吧?

#include "EmergeInternalCommandInterface.h"
class EmergeInternalCommands : public IEmergeInternalCommandProvider
//class definition goes here
Run Code Online (Sandbox Code Playgroud)

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

最后但绝对不是最不重要的 - 我这样做的方式比它需要的更复杂吗?

Joh*_*ela 5

https://github.com/jbandela/cppcomponents 上查看我的项目 cppcomponents 。我专门为像您这样的场景创建了这个库,因为我发现目前缺乏可用的解决方案。

它是一个仅适用于 Windows 和 Linux 的头文件 c++11 库。

它需要一个相当兼容的 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;
Run Code Online (Sandbox Code Playgroud)

然后在将被编译成 CommandProviderDll.dll 的ImplementCommandProvider.cpp

#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()
Run Code Online (Sandbox Code Playgroud)

这是你将如何使用它

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

    }


}
Run Code Online (Sandbox Code Playgroud)

以下是从命令行构建它的方法 我假设您位于包含 3 个文件的目录中,并且 MSVC 编译器在您的路径中

这是如何构建主程序

cl MainProgram.cpp /I c:\Users\jrb\Source\Repos\cppcomponents /EHsc
Run Code Online (Sandbox Code Playgroud)

这是构建 Dll 的方法

cl ImplementCommandProvider.cpp  /I c:\Users\jrb\Source\Repos\cppcomponents /EHsc /link /dll /OUT:CommandProviderDll.dll
Run Code Online (Sandbox Code Playgroud)

然后当你运行程序时,输入CommandProviderDll你的dllname

如果你想定义一个自定义结构是可能的,我可以帮你。

该库目前缺乏文档(正在处理它:(),但我可以帮助您解决有关该库的任何问题。该库是在 Boost 许可下发布的,因此您可以根据需要将其用于商业应用。