8

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?

4

1 回答 1

5

在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;

然后在将编译成 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()

这是您将如何使用它的方法

#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 许可证下发布的,因此您可以根据需要将其用于商业应用程序。

于 2013-11-13T14:12:17.907 回答