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?